diff --git a/WordPress/src/main/java/org/wordpress/android/models/SiteSettingsModel.java b/WordPress/src/main/java/org/wordpress/android/models/SiteSettingsModel.java index 71f15d8be34d..e37db5973dd7 100644 --- a/WordPress/src/main/java/org/wordpress/android/models/SiteSettingsModel.java +++ b/WordPress/src/main/java/org/wordpress/android/models/SiteSettingsModel.java @@ -13,6 +13,7 @@ /** * Holds blog settings and provides methods to (de)serialize .com and self-hosted network calls. */ + public class SiteSettingsModel { public static final int RELATED_POSTS_ENABLED_FLAG = 0x1; public static final int RELATED_POST_HEADER_FLAG = 0x2; @@ -37,9 +38,12 @@ public class SiteSettingsModel { public static final String ALLOW_COMMENTS_COLUMN_NAME = "allowComments"; public static final String SEND_PINGBACKS_COLUMN_NAME = "sendPingbacks"; public static final String RECEIVE_PINGBACKS_COLUMN_NAME = "receivePingbacks"; + public static final String SHOULD_CLOSE_AFTER_COLUMN_NAME = "shouldCloseAfter"; public static final String CLOSE_AFTER_COLUMN_NAME = "closeAfter"; public static final String SORT_BY_COLUMN_NAME = "sortBy"; + public static final String SHOULD_THREAD_COLUMN_NAME = "shouldThread"; public static final String THREADING_COLUMN_NAME = "threading"; + public static final String SHOULD_PAGE_COLUMN_NAME = "shouldPage"; public static final String PAGING_COLUMN_NAME = "paging"; public static final String MANUAL_APPROVAL_COLUMN_NAME = "manualApproval"; public static final String IDENTITY_REQUIRED_COLUMN_NAME = "identityRequired"; @@ -71,9 +75,12 @@ public class SiteSettingsModel { ALLOW_COMMENTS_COLUMN_NAME + " BOOLEAN, " + SEND_PINGBACKS_COLUMN_NAME + " BOOLEAN, " + RECEIVE_PINGBACKS_COLUMN_NAME + " BOOLEAN, " + + SHOULD_CLOSE_AFTER_COLUMN_NAME + " BOOLEAN, " + CLOSE_AFTER_COLUMN_NAME + " INTEGER, " + SORT_BY_COLUMN_NAME + " INTEGER, " + + SHOULD_THREAD_COLUMN_NAME + " BOOLEAN, " + THREADING_COLUMN_NAME + " INTEGER, " + + SHOULD_PAGE_COLUMN_NAME + " BOOLEAN, " + PAGING_COLUMN_NAME + " INTEGER, " + MANUAL_APPROVAL_COLUMN_NAME + " BOOLEAN, " + IDENTITY_REQUIRED_COLUMN_NAME + " BOOLEAN, " + @@ -106,9 +113,12 @@ public class SiteSettingsModel { public boolean allowComments; public boolean sendPingbacks; public boolean receivePingbacks; + public boolean shouldCloseAfter; public int closeCommentAfter; public int sortCommentsBy; + public boolean shouldThreadComments; public int threadingLevels; + public boolean shouldPageComments; public int commentsPerPage; public boolean commentApprovalRequired; public boolean commentsRequireIdentity; @@ -181,9 +191,12 @@ public void copyFrom(SiteSettingsModel other) { allowComments = other.allowComments; sendPingbacks = other.sendPingbacks; receivePingbacks = other.receivePingbacks; + shouldCloseAfter = other.shouldCloseAfter; closeCommentAfter = other.closeCommentAfter; sortCommentsBy = other.sortCommentsBy; + shouldThreadComments = other.shouldThreadComments; threadingLevels = other.threadingLevels; + shouldPageComments = other.shouldPageComments; commentsPerPage = other.commentsPerPage; commentApprovalRequired = other.commentApprovalRequired; commentsRequireIdentity = other.commentsRequireIdentity; @@ -215,9 +228,12 @@ public void deserializeOptionsDatabaseCursor(Cursor cursor, Map getMaxValue()) value = getMaxValue(); + super.setValue(value); + EditText view = (EditText) getChildAt(0); + WPPrefUtils.layoutAsNumberPickerSelected(view); } @Override - public void addView(View child, android.view.ViewGroup.LayoutParams params) { - super.addView(child, params); - updateView(child); + protected void onDraw(Canvas canvas) { + int[] selectorIndices = getIndices(); + setIndices(new int[0]); + setIndices(selectorIndices); + + // Draw the middle number with a different font + setDisplayValues(); + float elementHeight = getSelectorElementHeight(); + float x = ((getRight() - getLeft()) / 2.0f); + float y = getScrollOffset(); + Paint paint = mInputView.getPaint(); + paint.setTextAlign(Paint.Align.CENTER); + //noinspection deprecation + paint.setColor(getResources().getColor(R.color.blue_medium)); + int alpha = isEnabled() ? 255 : 96; + paint.setAlpha(alpha); + mPaint.setAlpha(alpha); + + int offset = getResources().getDimensionPixelSize(R.dimen.margin_medium); + // Draw the visible values + for (int i = 0; i < DISPLAY_COUNT; ++i) { + String scrollSelectorValue; + if (mFormatter != null) { + scrollSelectorValue = mFormatter.format(mDisplayValues[i]); + } else { + scrollSelectorValue = String.valueOf(mDisplayValues[i]); + } + if (i == MIDDLE_INDEX) { + canvas.drawText(scrollSelectorValue, x, y - ((paint.descent() + paint.ascent()) / 2) - offset, paint); + } else { + canvas.drawText(scrollSelectorValue, x, y - ((mPaint.descent() + mPaint.ascent()) / 2) - offset, mPaint); + } + y += elementHeight; + } + } + + @Override + public void setFormatter(Formatter formatter) { + super.setFormatter(formatter); + mFormatter = formatter; + } + + private void setDisplayValues() { + int value = getValue(); + for (int i = 0; i < DISPLAY_COUNT; ++i) { + mDisplayValues[i] = value - MIDDLE_INDEX + i; + if (mDisplayValues[i] < getMinValue()) { + mDisplayValues[i] = getMaxValue() + (mDisplayValues[i] + 1 - getMinValue()); + } else if (mDisplayValues[i] > getMaxValue()) { + mDisplayValues[i] = getMinValue() + (mDisplayValues[i] - getMaxValue() - 1); + } + } + } + + private void setIndices(int[] indices) { + if (mSelectorIndices != null) { + try { + mSelectorIndices.set(this, indices); + } catch (IllegalArgumentException | IllegalAccessException e) { + e.printStackTrace(); + } + } + } + + private int[] getIndices() { + if (mSelectorIndices != null) { + try { + return (int[]) mSelectorIndices.get(this); + } catch (IllegalArgumentException | IllegalAccessException e) { + e.printStackTrace(); + } + } + + return null; } - private void updateView(View view) { - if (view instanceof TextView) { - Typeface type = TypefaceCache.getTypeface(getContext(), - TypefaceCache.FAMILY_OPEN_SANS, - Typeface.NORMAL, - TypefaceCache.VARIATION_NORMAL); - ((TextView) view).setTypeface(type); - ((TextView) view).setTextSize(24); - ((TextView) view).setTextColor(getResources().getColor(R.color.wp_blue)); + private int getScrollOffset() { + if (mOffsetField != null) { + try { + return (Integer) mOffsetField.get(this); + } catch (IllegalArgumentException | IllegalAccessException e) { + e.printStackTrace(); + } } + + return 0; + } + + private int getSelectorElementHeight() { + if (mSelectorHeight != null) { + try { + return (Integer) mSelectorHeight.get(this); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + return 0; + } + + private void updateIntitialOffset() { + if (mInitialOffset != null) { + try { + int offset = (Integer) mInitialOffset.get(this) - getSelectorElementHeight(); + mInitialOffset.set(this, offset); + // Only do this once + mInitialOffset = null; + + if (mCurrentOffset != null) { + mCurrentOffset.set(this, offset); + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + + /** + * From https://www.snip2code.com/Snippet/67740/NumberPicker-with-transparent-selection- + */ + private void removeDividers(Class clazz) { + Field selectionDivider = getFieldAndSetAccessible(clazz, DIVIDER_FIELD); + if (selectionDivider != null) { + try { + selectionDivider.set(this, null); + } catch (IllegalArgumentException | IllegalAccessException e) { + e.printStackTrace(); + } + } + } + + private void getTextPaint(Class clazz) { + Field paint = getFieldAndSetAccessible(clazz, PAINT_FIELD); + if (paint != null) { + try { + mPaint = (Paint) paint.get(this); + } catch (IllegalArgumentException | IllegalAccessException e) { + e.printStackTrace(); + } + } + } + + private void getInputField(Class clazz) { + Field inputField = getFieldAndSetAccessible(clazz, INPUT_FIELD); + if (inputField != null) { + try { + mInputView = ((EditText) inputField.get(this)); + } catch (IllegalArgumentException | IllegalAccessException e) { + e.printStackTrace(); + } + } + } + + /** + * Gets a class field using reflection and makes it accessible. + */ + private Field getFieldAndSetAccessible(Class clazz, String fieldName) { + Field field = null; + try { + field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } + + return field; + } + + private void getFieldsViaReflection() { + Class numberPickerClass = null; + try { + numberPickerClass = Class.forName(NumberPicker.class.getName()); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + if (numberPickerClass == null) return; + + mSelectorHeight = getFieldAndSetAccessible(numberPickerClass, SELECTOR_HEIGHT_FIELD); + mOffsetField = getFieldAndSetAccessible(numberPickerClass, CUR_OFFSET_FIELD); + mSelectorIndices = getFieldAndSetAccessible(numberPickerClass, INDICES_FIELD); + mInitialOffset = getFieldAndSetAccessible(numberPickerClass, INITIAL_OFFSET_FIELD); + mCurrentOffset = getFieldAndSetAccessible(numberPickerClass, CURRENT_OFFSET_FIELD); + + getTextPaint(numberPickerClass); + getInputField(numberPickerClass); + removeDividers(numberPickerClass); + setIndices(new int[DISPLAY_COUNT]); } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/AbstractFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/accounts/AbstractFragment.java index 2fe69bb94c59..a1ffdca60af8 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/AbstractFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/AbstractFragment.java @@ -10,7 +10,6 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.ImageView; @@ -25,6 +24,7 @@ import org.wordpress.android.networking.RestClientUtils; import org.wordpress.android.util.AppLog; import org.wordpress.android.util.AppLog.T; +import org.wordpress.android.util.WPActivityUtils; /** * A fragment representing a single step in a wizard. The fragment shows a dummy title indicating @@ -75,12 +75,8 @@ protected boolean onDoneEvent(int actionId, KeyEvent event) { } // hide keyboard before calling the done action - InputMethodManager inputManager = (InputMethodManager) getActivity().getSystemService( - Context.INPUT_METHOD_SERVICE); View view = getActivity().getCurrentFocus(); - if (view != null) { - inputManager.hideSoftInputFromWindow(view.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); - } + if (view != null) WPActivityUtils.hideKeyboard(view); // call child action onDoneAction(); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/MySiteFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/main/MySiteFragment.java index 8dd87e51ce31..54b4a7e46acb 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/main/MySiteFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/main/MySiteFragment.java @@ -48,6 +48,8 @@ public class MySiteFragment extends Fragment private WPTextView mBlogSubtitleTextView; private LinearLayout mLookAndFeelHeader; private RelativeLayout mThemesContainer; + private View mConfigurationHeader; + private View mSettingsView; private View mFabView; private LinearLayout mNoSiteView; private ScrollView mScrollView; @@ -116,6 +118,8 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, mBlogSubtitleTextView = (WPTextView) rootView.findViewById(R.id.my_site_subtitle_label); mLookAndFeelHeader = (LinearLayout) rootView.findViewById(R.id.my_site_look_and_feel_header); mThemesContainer = (RelativeLayout) rootView.findViewById(R.id.row_themes); + mConfigurationHeader = rootView.findViewById(R.id.row_configuration); + mSettingsView = rootView.findViewById(R.id.row_settings); mScrollView = (ScrollView) rootView.findViewById(R.id.scroll_view); mNoSiteView = (LinearLayout) rootView.findViewById(R.id.no_site_view); mNoSiteDrakeImageView = (ImageView) rootView.findViewById(R.id.my_site_no_site_view_drake); @@ -186,7 +190,7 @@ public void onClick(View v) { } }); - rootView.findViewById(R.id.row_settings).setOnClickListener(new View.OnClickListener() { + mSettingsView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ActivityLauncher.viewBlogSettingsForResult(getActivity(), mBlog); @@ -304,6 +308,11 @@ private void refreshBlogDetails() { mLookAndFeelHeader.setVisibility(themesVisibility); mThemesContainer.setVisibility(themesVisibility); + // show settings for all self-hosted to expose Delete Site + int settingsVisibility = mBlog.isAdmin() || !mBlog.isDotcomFlag() ? View.VISIBLE : View.GONE; + mConfigurationHeader.setVisibility(settingsVisibility); + mSettingsView.setVisibility(settingsVisibility); + mBlavatarImageView.setImageUrl(GravatarUtils.blavatarFromUrl(mBlog.getUrl(), mBlavatarSz), WPNetworkImageView.ImageType.BLAVATAR); String blogName = StringUtils.unescapeHTML(mBlog.getBlogName()); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/SitePickerActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/main/SitePickerActivity.java index b7f76ae5416e..c40ab4356230 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/main/SitePickerActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/main/SitePickerActivity.java @@ -35,6 +35,7 @@ import org.wordpress.android.ui.stats.datasets.StatsTable; import org.wordpress.android.util.CoreEvents; import org.wordpress.android.util.ToastUtils; +import org.wordpress.android.util.WPActivityUtils; import org.xmlrpc.android.ApiHelper; import de.greenrobot.event.EventBus; @@ -318,8 +319,7 @@ private void disableSearchMode() { private void hideSoftKeyboard() { if (!hasHardwareKeyboard()) { - InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - inputMethodManager.hideSoftInputFromWindow(mSearchView.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); + WPActivityUtils.hideKeyboard(mSearchView); } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java index e79628baa9cb..9fd6f4c3274c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/main/WPMainActivity.java @@ -33,8 +33,8 @@ import org.wordpress.android.ui.notifications.utils.NotificationsUtils; import org.wordpress.android.ui.notifications.utils.SimperiumUtils; import org.wordpress.android.ui.prefs.AppPrefs; -import org.wordpress.android.ui.prefs.BlogPreferencesActivity; import org.wordpress.android.ui.prefs.SettingsFragment; +import org.wordpress.android.ui.prefs.SiteSettingsFragment; import org.wordpress.android.ui.reader.ReaderPostListFragment; import org.wordpress.android.util.AnalyticsUtils; import org.wordpress.android.util.AniUtils; @@ -385,7 +385,7 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { } break; case RequestCodes.BLOG_SETTINGS: - if (resultCode == BlogPreferencesActivity.RESULT_BLOG_REMOVED) { + if (resultCode == SiteSettingsFragment.RESULT_BLOG_REMOVED) { // user removed the current (self-hosted) blog from blog settings if (!AccountHelper.isSignedIn()) { ActivityLauncher.showSignInForResult(this); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/BlogPreferencesActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/prefs/BlogPreferencesActivity.java index f06074a84d15..7e283a3ad823 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/BlogPreferencesActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/BlogPreferencesActivity.java @@ -2,18 +2,35 @@ import android.app.Fragment; import android.app.FragmentManager; +import android.app.AlertDialog; +import android.content.DialogInterface; import android.os.Bundle; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.Spinner; +import android.widget.TextView; import android.widget.Toast; import org.wordpress.android.R; import org.wordpress.android.WordPress; +import org.wordpress.android.models.AccountHelper; import org.wordpress.android.models.Blog; -import org.wordpress.android.networking.ConnectionChangeReceiver; import org.wordpress.android.ui.ActivityLauncher; +import org.wordpress.android.ui.stats.StatsWidgetProvider; +import org.wordpress.android.ui.stats.datasets.StatsTable; +import org.wordpress.android.util.AnalyticsUtils; +import org.wordpress.android.util.CoreEvents.UserSignedOutCompletely; +import org.wordpress.android.util.StringUtils; import org.wordpress.android.util.ToastUtils; +import org.wordpress.android.networking.ConnectionChangeReceiver; import de.greenrobot.event.EventBus; @@ -27,37 +44,82 @@ public class BlogPreferencesActivity extends AppCompatActivity { private static final String KEY_SETTINGS_FRAGMENT = "settings-fragment"; // The blog this activity is managing settings for. - private Blog mBlog; + private Blog blog; + private boolean mBlogDeleted; + private EditText mUsernameET; + private EditText mPasswordET; + private EditText mHttpUsernameET; + private EditText mHttpPasswordET; + private CheckBox mFullSizeCB; + private CheckBox mScaledCB; + private Spinner mImageWidthSpinner; + private EditText mScaledImageWidthET; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Integer id = getIntent().getIntExtra(ARG_LOCAL_BLOG_ID, -1); - mBlog = WordPress.getBlog(id); - - if (mBlog == null) { + blog = WordPress.getBlog(id); + if (WordPress.getBlog(id) == null) { Toast.makeText(this, getString(R.string.blog_not_found), Toast.LENGTH_SHORT).show(); finish(); return; } - ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { - actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); - actionBar.setDisplayHomeAsUpEnabled(true); - actionBar.setCustomView(R.layout.site_settings_actionbar); - } + if (blog.isDotcomFlag()) { + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setCustomView(R.layout.site_settings_actionbar); + } - FragmentManager fragmentManager = getFragmentManager(); - Fragment siteSettingsFragment = fragmentManager.findFragmentByTag(KEY_SETTINGS_FRAGMENT); - - if (siteSettingsFragment == null) { - siteSettingsFragment = new SiteSettingsFragment(); - siteSettingsFragment.setArguments(getIntent().getExtras()); - getFragmentManager().beginTransaction() - .replace(android.R.id.content, siteSettingsFragment, KEY_SETTINGS_FRAGMENT) - .commit(); + FragmentManager fragmentManager = getFragmentManager(); + Fragment siteSettingsFragment = fragmentManager.findFragmentByTag(KEY_SETTINGS_FRAGMENT); + + if (siteSettingsFragment == null) { + siteSettingsFragment = new SiteSettingsFragment(); + siteSettingsFragment.setArguments(getIntent().getExtras()); + getFragmentManager().beginTransaction() + .replace(android.R.id.content, siteSettingsFragment, KEY_SETTINGS_FRAGMENT) + .commit(); + } + } else { + setContentView(R.layout.blog_preferences); + + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setTitle(StringUtils.unescapeHTML(blog.getNameOrHostUrl())); + actionBar.setDisplayHomeAsUpEnabled(true); + } + + mUsernameET = (EditText) findViewById(R.id.username); + mPasswordET = (EditText) findViewById(R.id.password); + mHttpUsernameET = (EditText) findViewById(R.id.httpuser); + mHttpPasswordET = (EditText) findViewById(R.id.httppassword); + mScaledImageWidthET = (EditText) findViewById(R.id.scaledImageWidth); + mFullSizeCB = (CheckBox) findViewById(R.id.fullSizeImage); + mScaledCB = (CheckBox) findViewById(R.id.scaledImage); + mImageWidthSpinner = (Spinner) findViewById(R.id.maxImageWidth); + Button removeBlogButton = (Button) findViewById(R.id.remove_account); + + // remove blog & credentials apply only to dot org + if (blog.isDotcomFlag()) { + View credentialsRL = findViewById(R.id.sectionContent); + credentialsRL.setVisibility(View.GONE); + removeBlogButton.setVisibility(View.GONE); + } else { + removeBlogButton.setVisibility(View.VISIBLE); + removeBlogButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + removeBlogWithConfirmation(); + } + }); + } + + loadSettingsForBlog(); } } @@ -67,6 +129,61 @@ public void finish() { ActivityLauncher.slideOutToRight(this); } + @Override + protected void onPause() { + super.onPause(); + + if (blog.isDotcomFlag() || mBlogDeleted) { + return; + } + + blog.setUsername(mUsernameET.getText().toString()); + blog.setPassword(mPasswordET.getText().toString()); + blog.setHttpuser(mHttpUsernameET.getText().toString()); + blog.setHttppassword(mHttpPasswordET.getText().toString()); + + blog.setFullSizeImage(mFullSizeCB.isChecked()); + blog.setScaledImage(mScaledCB.isChecked()); + if (blog.isScaledImage()) { + EditText scaledImgWidth = (EditText) findViewById(R.id.scaledImageWidth); + + boolean error = false; + int width = 0; + try { + width = Integer.parseInt(scaledImgWidth.getText().toString().trim()); + } catch (NumberFormatException e) { + error = true; + } + + if (width == 0) { + error = true; + } + + if (error) { + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(BlogPreferencesActivity.this); + dialogBuilder.setTitle(getResources().getText(R.string.error)); + dialogBuilder.setMessage(getResources().getText(R.string.scaled_image_error)); + dialogBuilder.setPositiveButton("OK", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + } + }); + dialogBuilder.setCancelable(true); + dialogBuilder.create().show(); + return; + } else { + blog.setScaledImageWidth(width); + } + } + + blog.setMaxImageWidth(mImageWidthSpinner.getSelectedItem().toString()); + + WordPress.wpDB.saveBlog(blog); + + if (WordPress.getCurrentBlog().getLocalTableBlogId() == blog.getLocalTableBlogId()) { + WordPress.currentBlog = blog; + } + } + @Override protected void onStart() { super.onStart(); @@ -79,12 +196,6 @@ protected void onStop() { super.onStop(); } - @Override - protected void onPause() { - super.onPause(); - WordPress.wpDB.saveBlog(mBlog); - } - @Override public boolean onOptionsItemSelected(MenuItem item) { int itemID = item.getItemId(); @@ -115,4 +226,138 @@ public void onEventMainThread(ConnectionChangeReceiver.ConnectionChangeEvent eve // StatsWidgetProvider.updateWidgetsOnLogout(this); } } + + private void loadSettingsForBlog() { + ArrayAdapter spinnerArrayAdapter = new ArrayAdapter(this, + R.layout.simple_spinner_item, new String[]{ + "Original Size", "100", "200", "300", "400", "500", "600", "700", "800", + "900", "1000", "1100", "1200", "1300", "1400", "1500", "1600", "1700", + "1800", "1900", "2000" + }); + spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + mImageWidthSpinner.setAdapter(spinnerArrayAdapter); + mImageWidthSpinner.setOnItemSelectedListener(new OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + CheckBox fullSizeImageCheckBox = (CheckBox) findViewById(R.id.fullSizeImage); + // Original size selected. Do not show the link to full image. + if (id == 0) { + fullSizeImageCheckBox.setVisibility(View.GONE); + } else { + fullSizeImageCheckBox.setVisibility(View.VISIBLE); + } + } + + @Override + public void onNothingSelected(AdapterView arg0) { + } + }); + + mUsernameET.setText(blog.getUsername()); + mPasswordET.setText(blog.getPassword()); + mHttpUsernameET.setText(blog.getHttpuser()); + mHttpPasswordET.setText(blog.getHttppassword()); + TextView httpUserLabel = (TextView) findViewById(R.id.l_httpuser); + if (blog.isDotcomFlag()) { + mHttpUsernameET.setVisibility(View.GONE); + mHttpPasswordET.setVisibility(View.GONE); + httpUserLabel.setVisibility(View.GONE); + } else { + mHttpUsernameET.setVisibility(View.VISIBLE); + mHttpPasswordET.setVisibility(View.VISIBLE); + httpUserLabel.setVisibility(View.VISIBLE); + } + + mFullSizeCB.setChecked(blog.isFullSizeImage()); + mScaledCB.setChecked(blog.isScaledImage()); + + this.mScaledImageWidthET.setText("" + blog.getScaledImageWidth()); + showScaledSetting(blog.isScaledImage()); + + CheckBox scaledImage = (CheckBox) findViewById(R.id.scaledImage); + scaledImage.setChecked(false); + scaledImage.setVisibility(View.GONE); + + // sets up a state listener for the full-size checkbox + CheckBox fullSizeImageCheckBox = (CheckBox) findViewById(R.id.fullSizeImage); + fullSizeImageCheckBox.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + CheckBox fullSize = (CheckBox) findViewById(R.id.fullSizeImage); + if (fullSize.isChecked()) { + CheckBox scaledImage = (CheckBox) findViewById(R.id.scaledImage); + if (scaledImage.isChecked()) { + scaledImage.setChecked(false); + showScaledSetting(false); + } + } + } + }); + + int imageWidthPosition = spinnerArrayAdapter.getPosition(blog.getMaxImageWidth()); + mImageWidthSpinner.setSelection((imageWidthPosition >= 0) ? imageWidthPosition : 0); + if (mImageWidthSpinner.getSelectedItemPosition() == + 0) //Original size selected. Do not show the link to full image. + { + fullSizeImageCheckBox.setVisibility(View.GONE); + } else { + fullSizeImageCheckBox.setVisibility(View.VISIBLE); + } + } + + /** + * Hides / shows the scaled image settings + */ + private void showScaledSetting(boolean show) { + TextView tw = (TextView) findViewById(R.id.l_scaledImage); + EditText et = (EditText) findViewById(R.id.scaledImageWidth); + tw.setVisibility(show ? View.VISIBLE : View.GONE); + et.setVisibility(show ? View.VISIBLE : View.GONE); + } + + /** + * Remove the blog this activity is managing settings for. + */ + private void removeBlogWithConfirmation() { + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); + dialogBuilder.setTitle(getResources().getText(R.string.remove_account)); + dialogBuilder.setMessage(getResources().getText(R.string.sure_to_remove_account)); + dialogBuilder.setPositiveButton(getResources().getText(R.string.yes), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + removeBlog(); + } + }); + dialogBuilder.setNegativeButton(getResources().getText(R.string.no), null); + dialogBuilder.setCancelable(false); + dialogBuilder.create().show(); + } + + private void removeBlog() { + if (WordPress.wpDB.deleteBlog(this, blog.getLocalTableBlogId())) { + StatsTable.deleteStatsForBlog(this,blog.getLocalTableBlogId()); // Remove stats data + AnalyticsUtils.refreshMetadata(); + ToastUtils.showToast(this, R.string.blog_removed_successfully); + WordPress.wpDB.deleteLastBlogId(); + WordPress.currentBlog = null; + mBlogDeleted = true; + setResult(RESULT_BLOG_REMOVED); + + // If the last blog is removed and the user is not signed in wpcom, broadcast a UserSignedOut event + if (!AccountHelper.isSignedIn()) { + EventBus.getDefault().post(new UserSignedOutCompletely()); + } + + // Checks for stats widgets that were synched with a blog that could be gone now. + StatsWidgetProvider.updateWidgetsOnLogout(this); + + finish(); + } else { + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); + dialogBuilder.setTitle(getResources().getText(R.string.error)); + dialogBuilder.setMessage(getResources().getText(R.string.could_not_remove_account)); + dialogBuilder.setPositiveButton("OK", null); + dialogBuilder.setCancelable(true); + dialogBuilder.create().show(); + } + } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/DetailListPreference.java b/WordPress/src/main/java/org/wordpress/android/ui/prefs/DetailListPreference.java index 21087f1c5e79..68a976fb6332 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/DetailListPreference.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/DetailListPreference.java @@ -131,8 +131,6 @@ public void onClick(DialogInterface dialog, int which) { if (listView != null) { listView.setDividerHeight(0); listView.setClipToPadding(true); - //noinspection deprecation - listView.setBackgroundColor(res.getColor(R.color.grey_lighten_30)); listView.setPadding(0, 0, 0, res.getDimensionPixelSize(R.dimen.site_settings_divider_height)); } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/DotComSiteSettings.java b/WordPress/src/main/java/org/wordpress/android/ui/prefs/DotComSiteSettings.java index 94fbc0cfea42..ecf13fb5c339 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/DotComSiteSettings.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/DotComSiteSettings.java @@ -9,14 +9,17 @@ import org.json.JSONException; import org.json.JSONObject; import org.wordpress.android.WordPress; +import org.wordpress.android.analytics.AnalyticsTracker; import org.wordpress.android.datasets.SiteSettingsTable; import org.wordpress.android.models.Blog; import org.wordpress.android.models.CategoryModel; +import org.wordpress.android.util.AnalyticsUtils; import org.wordpress.android.util.AppLog; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; class DotComSiteSettings extends SiteSettingsInterface { @@ -56,6 +59,10 @@ class DotComSiteSettings extends SiteSettingsInterface { private static final String SET_TITLE_KEY = "blogname"; private static final String SET_DESC_KEY = "blogdescription"; + // JSON response keys + private static final String SETTINGS_KEY = "settings"; + private static final String UPDATED_KEY = "updated"; + // WP.com REST keys used in response to a categories GET request private static final String CAT_ID_KEY = "ID"; private static final String CAT_NAME_KEY = "name"; @@ -87,6 +94,22 @@ public void onResponse(JSONObject response) { AppLog.d(AppLog.T.API, "Site Settings saved remotely"); notifySavedOnUiThread(null); mRemoteSettings.copyFrom(mSettings); + + if (response != null) { + JSONObject updated = response.optJSONObject(UPDATED_KEY); + if (updated == null) return; + HashMap properties = new HashMap<>(); + Iterator keys = updated.keys(); + while (keys.hasNext()) { + String currentKey = keys.next(); + Object currentValue = updated.opt(currentKey); + if (currentValue != null) { + properties.put(SAVED_ITEM_PREFIX + currentKey, currentValue); + } + } + AnalyticsUtils.trackWithCurrentBlogDetails( + AnalyticsTracker.Stat.SITE_SETTINGS_SAVED_REMOTELY, properties); + } } }, new RestRequest.ErrorListener() { @Override @@ -132,7 +155,7 @@ public void onErrorResponse(VolleyError error) { */ public void deserializeDotComRestResponse(Blog blog, JSONObject response) { if (blog == null || response == null) return; - JSONObject settingsObject = response.optJSONObject("settings"); + JSONObject settingsObject = response.optJSONObject(SETTINGS_KEY); mRemoteSettings.username = blog.getUsername(); mRemoteSettings.password = blog.getPassword(); @@ -147,6 +170,12 @@ public void deserializeDotComRestResponse(Blog blog, JSONObject response) { mRemoteSettings.allowComments = settingsObject.optBoolean(ALLOW_COMMENTS_KEY, true); mRemoteSettings.sendPingbacks = settingsObject.optBoolean(SEND_PINGBACKS_KEY, false); mRemoteSettings.receivePingbacks = settingsObject.optBoolean(RECEIVE_PINGBACKS_KEY, true); + mRemoteSettings.shouldCloseAfter = settingsObject.optBoolean(CLOSE_OLD_COMMENTS_KEY, false); + mRemoteSettings.closeCommentAfter = settingsObject.optInt(CLOSE_OLD_COMMENTS_DAYS_KEY, 0); + mRemoteSettings.shouldThreadComments = settingsObject.optBoolean(THREAD_COMMENTS_KEY, false); + mRemoteSettings.threadingLevels = settingsObject.optInt(THREAD_COMMENTS_DEPTH_KEY, 0); + mRemoteSettings.shouldPageComments = settingsObject.optBoolean(PAGE_COMMENTS_KEY, false); + mRemoteSettings.commentsPerPage = settingsObject.optInt(PAGE_COMMENT_COUNT_KEY, 0); mRemoteSettings.commentApprovalRequired = settingsObject.optBoolean(COMMENT_MODERATION_KEY, false); mRemoteSettings.commentsRequireIdentity = settingsObject.optBoolean(REQUIRE_IDENTITY_KEY, false); mRemoteSettings.commentsRequireUserAccount = settingsObject.optBoolean(REQUIRE_USER_ACCOUNT_KEY, true); @@ -164,24 +193,6 @@ public void deserializeDotComRestResponse(Blog blog, JSONObject response) { Collections.addAll(mRemoteSettings.blacklist, blacklistKeys.split("\n")); } - if (settingsObject.optBoolean(CLOSE_OLD_COMMENTS_KEY, false)) { - mRemoteSettings.closeCommentAfter = settingsObject.optInt(CLOSE_OLD_COMMENTS_DAYS_KEY, 0); - } else { - mRemoteSettings.closeCommentAfter = 0; - } - - if (settingsObject.optBoolean(THREAD_COMMENTS_KEY, false)) { - mRemoteSettings.threadingLevels = settingsObject.optInt(THREAD_COMMENTS_DEPTH_KEY, 0); - } else { - mRemoteSettings.threadingLevels = 0; - } - - if (settingsObject.optBoolean(PAGE_COMMENTS_KEY, false)) { - mRemoteSettings.commentsPerPage = settingsObject.optInt(PAGE_COMMENT_COUNT_KEY, 0); - } else { - mRemoteSettings.commentsPerPage = 0; - } - if (settingsObject.optString(COMMENT_SORT_ORDER_KEY, "").equals("asc")) { mRemoteSettings.sortCommentsBy = ASCENDING_SORT; } else { @@ -241,13 +252,10 @@ public Map serializeDotComParams() { if (mSettings.commentApprovalRequired != mRemoteSettings.commentApprovalRequired) { params.put(COMMENT_MODERATION_KEY, String.valueOf(mSettings.commentApprovalRequired)); } - if (mSettings.closeCommentAfter != mRemoteSettings.closeCommentAfter) { - if (mSettings.closeCommentAfter <= 0) { - params.put(CLOSE_OLD_COMMENTS_KEY, String.valueOf(false)); - } else { - params.put(CLOSE_OLD_COMMENTS_KEY, String.valueOf(true)); - params.put(CLOSE_OLD_COMMENTS_DAYS_KEY, String.valueOf(mSettings.closeCommentAfter)); - } + if (mSettings.closeCommentAfter != mRemoteSettings.closeCommentAfter + || mSettings.shouldCloseAfter != mRemoteSettings.shouldCloseAfter) { + params.put(CLOSE_OLD_COMMENTS_KEY, String.valueOf(mSettings.shouldCloseAfter)); + params.put(CLOSE_OLD_COMMENTS_DAYS_KEY, String.valueOf(mSettings.closeCommentAfter)); } if (mSettings.sortCommentsBy != mRemoteSettings.sortCommentsBy) { if (mSettings.sortCommentsBy == ASCENDING_SORT) { @@ -256,21 +264,15 @@ public Map serializeDotComParams() { params.put(COMMENT_SORT_ORDER_KEY, "desc"); } } - if (mSettings.threadingLevels != mRemoteSettings.threadingLevels) { - if (mSettings.threadingLevels <= 1) { - params.put(THREAD_COMMENTS_KEY, String.valueOf(false)); - } else { - params.put(PAGE_COMMENTS_KEY, String.valueOf(true)); - params.put(THREAD_COMMENTS_DEPTH_KEY, String.valueOf(mSettings.threadingLevels)); - } + if (mSettings.threadingLevels != mRemoteSettings.threadingLevels + || mSettings.shouldThreadComments != mRemoteSettings.shouldThreadComments) { + params.put(THREAD_COMMENTS_KEY, String.valueOf(mSettings.shouldThreadComments)); + params.put(THREAD_COMMENTS_DEPTH_KEY, String.valueOf(mSettings.threadingLevels)); } - if (mSettings.commentsPerPage != mRemoteSettings.commentsPerPage) { - if (mSettings.commentsPerPage <= 0) { - params.put(PAGE_COMMENTS_KEY, String.valueOf(false)); - } else{ - params.put(PAGE_COMMENTS_KEY, String.valueOf(true)); - params.put(PAGE_COMMENT_COUNT_KEY, String.valueOf(mSettings.commentsPerPage)); - } + if (mSettings.commentsPerPage != mRemoteSettings.commentsPerPage + || mSettings.shouldPageComments != mRemoteSettings.shouldPageComments) { + params.put(PAGE_COMMENTS_KEY, String.valueOf(mSettings.shouldPageComments)); + params.put(PAGE_COMMENT_COUNT_KEY, String.valueOf(mSettings.commentsPerPage)); } if (mSettings.commentsRequireIdentity != mRemoteSettings.commentsRequireIdentity) { params.put(REQUIRE_IDENTITY_KEY, String.valueOf(mSettings.commentsRequireIdentity)); @@ -290,7 +292,11 @@ public Map serializeDotComParams() { builder.append(key); builder.append("\n"); } - params.put(MODERATION_KEYS_KEY, builder.substring(0, builder.length() - 1)); + if (builder.length() > 1) { + params.put(MODERATION_KEYS_KEY, builder.substring(0, builder.length() - 1)); + } else { + params.put(MODERATION_KEYS_KEY, ""); + } } if (mSettings.blacklist != null && !mSettings.blacklist.equals(mRemoteSettings.blacklist)) { StringBuilder builder = new StringBuilder(); @@ -298,7 +304,11 @@ public Map serializeDotComParams() { builder.append(key); builder.append("\n"); } - params.put(BLACKLIST_KEYS_KEY, builder.substring(0, builder.length() - 1)); + if (builder.length() > 1) { + params.put(BLACKLIST_KEYS_KEY, builder.substring(0, builder.length() - 1)); + } else { + params.put(BLACKLIST_KEYS_KEY, ""); + } } return params; diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/LearnMorePreference.java b/WordPress/src/main/java/org/wordpress/android/ui/prefs/LearnMorePreference.java index c95b180e5b98..e9a98e049ad6 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/LearnMorePreference.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/LearnMorePreference.java @@ -14,6 +14,8 @@ import android.webkit.WebViewClient; import org.wordpress.android.R; +import org.wordpress.android.analytics.AnalyticsTracker; +import org.wordpress.android.util.AnalyticsUtils; public class LearnMorePreference extends Preference implements PreferenceHint, View.OnClickListener, DialogInterface.OnDismissListener { @@ -40,6 +42,9 @@ protected View onCreateView(@NonNull ViewGroup parent) { public void onClick(View v) { if (mDialog != null) return; + AnalyticsUtils.trackWithCurrentBlogDetails( + AnalyticsTracker.Stat.SITE_SETTINGS_LEARN_MORE_CLICKED); + Context context = getContext(); mDialog = new Dialog(context); mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); @@ -51,6 +56,8 @@ public void onClick(View v) { public void onPageFinished(WebView webView, String url) { super.onPageFinished(webView, url); if (mDialog != null) { + AnalyticsUtils.trackWithCurrentBlogDetails( + AnalyticsTracker.Stat.SITE_SETTINGS_LEARN_MORE_LOADED); mDialog.setContentView(webView); } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/MultiSelectListView.java b/WordPress/src/main/java/org/wordpress/android/ui/prefs/MultiSelectListView.java index c21af32f3030..5c03e54aab3e 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/MultiSelectListView.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/MultiSelectListView.java @@ -54,6 +54,8 @@ public void onItemClick(AdapterView parent, View view, int position, long id) if (getCheckedItemCount() <= 0) { mActionMode.finish(); } else { + int color = isItemChecked(position) ? R.color.white : R.color.transparent; + getChildAt(position).setBackgroundColor(getResources().getColor(color)); mActionMode.invalidate(); } } @@ -61,10 +63,11 @@ public void onItemClick(AdapterView parent, View view, int position, long id) @Override public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { if (mActionMode != null) return false; - if (mEnterListener != null) mEnterListener.onEnterMultiSelect(); setItemChecked(position, true); + getChildAt(position).setBackgroundColor(getResources().getColor(R.color.white)); mActionMode = startActionMode(this); + if (mEnterListener != null) mEnterListener.onEnterMultiSelect(); return true; } @@ -96,6 +99,11 @@ public boolean onActionItemClicked(ActionMode mode, MenuItem item) { @Override public void onDestroyActionMode(ActionMode mode) { + for (int i = 0; i < getChildCount(); ++i) { + getChildAt(i).setBackgroundColor(getResources().getColor(R.color.transparent)); + } + + clearChoices(); mActionMode = null; if (mExitListener != null) mExitListener.onExitMultiSelect(); } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/NumberPickerDialog.java b/WordPress/src/main/java/org/wordpress/android/ui/prefs/NumberPickerDialog.java new file mode 100644 index 000000000000..cc2977624aed --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/NumberPickerDialog.java @@ -0,0 +1,159 @@ +package org.wordpress.android.ui.prefs; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.Fragment; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.widget.SwitchCompat; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Button; +import android.widget.CompoundButton; +import android.widget.NumberPicker; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import org.wordpress.android.R; +import org.wordpress.android.util.WPPrefUtils; + +public class NumberPickerDialog extends DialogFragment + implements DialogInterface.OnClickListener, + CompoundButton.OnCheckedChangeListener { + + public static final String SHOW_SWITCH_KEY = "show-switch"; + public static final String SWITCH_ENABLED_KEY = "switch-enabled"; + public static final String SWITCH_TITLE_KEY = "switch-title"; + public static final String TITLE_KEY = "dialog-title"; + public static final String HEADER_TEXT_KEY = "header-text"; + public static final String MIN_VALUE_KEY = "min-value"; + public static final String MAX_VALUE_KEY = "max-value"; + public static final String CUR_VALUE_KEY = "cur-value"; + + private static final int DEFAULT_MIN_VALUE = 0; + private static final int DEFAULT_MAX_VALUE = 99; + + private SwitchCompat mSwitch; + private TextView mHeaderText; + private NumberPicker mNumberPicker; + private int mMinValue; + private int mMaxValue; + private boolean mConfirmed; + + public NumberPickerDialog() { + mMinValue = DEFAULT_MIN_VALUE; + mMaxValue = DEFAULT_MAX_VALUE; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.Calypso_AlertDialog); + View view = View.inflate(getActivity(), R.layout.number_picker_dialog, null); + TextView switchText = (TextView) view.findViewById(R.id.number_picker_text); + mSwitch = (SwitchCompat) view.findViewById(R.id.number_picker_switch); + mHeaderText = (TextView) view.findViewById(R.id.number_picker_header); + mNumberPicker = (NumberPicker) view.findViewById(R.id.number_picker); + int value = mMinValue; + + Bundle args = getArguments(); + if (args != null) { + if (args.getBoolean(SHOW_SWITCH_KEY, false)) { + mSwitch.setVisibility(View.VISIBLE); + mSwitch.setText(args.getString(SWITCH_TITLE_KEY, "")); + mSwitch.setChecked(args.getBoolean(SWITCH_ENABLED_KEY, false)); + final View toggleContainer = view.findViewById(R.id.number_picker_toggleable); + toggleContainer.setEnabled(mSwitch.isChecked()); + mNumberPicker.setEnabled(mSwitch.isChecked()); + } else { + mSwitch.setVisibility(View.GONE); + } + switchText.setText(args.getString(SWITCH_TITLE_KEY, "")); + mHeaderText.setText(args.getString(HEADER_TEXT_KEY, "")); + mMinValue = args.getInt(MIN_VALUE_KEY, DEFAULT_MIN_VALUE); + mMaxValue = args.getInt(MAX_VALUE_KEY, DEFAULT_MAX_VALUE); + value = args.getInt(CUR_VALUE_KEY, mMinValue); + + builder.setCustomTitle(getDialogTitleView(args.getString(TITLE_KEY, ""))); + } + + mNumberPicker.setMinValue(mMinValue); + mNumberPicker.setMaxValue(mMaxValue); + mNumberPicker.setValue(value); + + mSwitch.setOnCheckedChangeListener(this); + + // hide empty text views + if (TextUtils.isEmpty(switchText.getText())) { + switchText.setVisibility(View.GONE); + } + if (TextUtils.isEmpty(mHeaderText.getText())) { + mHeaderText.setVisibility(View.GONE); + } + + builder.setPositiveButton(R.string.ok, this); + builder.setNegativeButton(R.string.cancel, this); + builder.setView(view); + + return builder.create(); + } + + @Override + public void onStart() { + super.onStart(); + + AlertDialog dialog = (AlertDialog) getDialog(); + Button positive = dialog.getButton(DialogInterface.BUTTON_POSITIVE); + Button negative = dialog.getButton(DialogInterface.BUTTON_NEGATIVE); + if (positive != null) WPPrefUtils.layoutAsFlatButton(positive); + if (negative != null) WPPrefUtils.layoutAsFlatButton(negative); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + mConfirmed = which == DialogInterface.BUTTON_POSITIVE; + dismiss(); + } + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + mNumberPicker.setEnabled(isChecked); + mHeaderText.setEnabled(isChecked); + } + + @Override + public void onDismiss(DialogInterface dialog) { + Fragment target = getTargetFragment(); + if (target != null) { + target.onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, getResultIntent()); + } + + super.onDismiss(dialog); + } + + private View getDialogTitleView(String title) { + LayoutInflater inflater = LayoutInflater.from(getActivity()); + @SuppressLint("InflateParams") + View titleView = inflater.inflate(R.layout.detail_list_preference_title, null); + TextView titleText = ((TextView) titleView.findViewById(R.id.title)); + titleText.setText(title); + titleText.setLayoutParams(new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.MATCH_PARENT, + RelativeLayout.LayoutParams.WRAP_CONTENT)); + return titleView; + } + + private Intent getResultIntent() { + if (mConfirmed) { + return new Intent() + .putExtra(SWITCH_ENABLED_KEY, mSwitch.isChecked()) + .putExtra(CUR_VALUE_KEY, mNumberPicker.getValue()); + } + + return null; + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/RelatedPostsDialog.java b/WordPress/src/main/java/org/wordpress/android/ui/prefs/RelatedPostsDialog.java index 0544bc14aec7..6cfb2aaad905 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/RelatedPostsDialog.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/RelatedPostsDialog.java @@ -7,8 +7,6 @@ import android.app.Fragment; import android.content.DialogInterface; import android.content.Intent; -import android.content.res.Resources; -import android.graphics.Typeface; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -21,7 +19,7 @@ import android.widget.TextView; import org.wordpress.android.R; -import org.wordpress.android.widgets.TypefaceCache; +import org.wordpress.android.util.WPPrefUtils; import org.wordpress.android.widgets.WPSwitch; import java.util.ArrayList; @@ -78,6 +76,9 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { mPreviewImages.add((ImageView) v.findViewById(R.id.related_post_image2)); mPreviewImages.add((ImageView) v.findViewById(R.id.related_post_image3)); + WPPrefUtils.layoutAsSubhead(mShowHeader); + WPPrefUtils.layoutAsSubhead(mShowImages); + Bundle args = getArguments(); if (args != null) { mShowRelatedPosts.setChecked(args.getBoolean(SHOW_RELATED_POSTS_KEY)); @@ -112,24 +113,10 @@ public void onStart() { super.onStart(); AlertDialog dialog = (AlertDialog) getDialog(); - Resources res = getResources(); - Button positive = dialog.getButton(DialogInterface.BUTTON_POSITIVE); Button negative = dialog.getButton(DialogInterface.BUTTON_NEGATIVE); - Typeface typeface = TypefaceCache.getTypeface(getActivity(), - TypefaceCache.FAMILY_OPEN_SANS, - Typeface.BOLD, - TypefaceCache.VARIATION_LIGHT); - - if (positive != null) { - positive.setTextColor(res.getColor(R.color.blue_medium)); - positive.setTypeface(typeface); - } - - if (negative != null) { - negative.setTextColor(res.getColor(R.color.blue_medium)); - negative.setTypeface(typeface); - } + if (positive != null) WPPrefUtils.layoutAsFlatButton(positive); + if (negative != null) WPPrefUtils.layoutAsFlatButton(negative); } @Override diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/SelfHostedSiteSettings.java b/WordPress/src/main/java/org/wordpress/android/ui/prefs/SelfHostedSiteSettings.java index 5187f87fc0a4..932500ab9c79 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/SelfHostedSiteSettings.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/SelfHostedSiteSettings.java @@ -3,10 +3,13 @@ import android.app.Activity; import android.text.TextUtils; +import org.wordpress.android.R; +import org.wordpress.android.analytics.AnalyticsTracker; import org.wordpress.android.datasets.SiteSettingsTable; import org.wordpress.android.models.Blog; import org.wordpress.android.models.CategoryModel; import org.wordpress.android.models.SiteSettingsModel; +import org.wordpress.android.util.AnalyticsUtils; import org.wordpress.android.util.AppLog; import org.wordpress.android.util.MapUtils; import org.xmlrpc.android.ApiHelper; @@ -19,14 +22,10 @@ import java.util.HashMap; import java.util.Locale; import java.util.Map; +import java.util.Set; class SelfHostedSiteSettings extends SiteSettingsInterface { // XML-RPC wp.getOptions keys - private static final String BLOG_URL_KEY = "blog_url"; - private static final String BLOG_TITLE_KEY = "blog_title"; - private static final String BLOG_USERNAME_KEY = "username"; - private static final String BLOG_PASSWORD_KEY = "password"; - private static final String BLOG_TAGLINE_KEY = "blog_tagline"; public static final String PRIVACY_KEY = "blog_public"; public static final String DEF_CATEGORY_KEY = "default_category"; public static final String DEF_POST_FORMAT_KEY = "default_post_format"; @@ -47,11 +46,22 @@ class SelfHostedSiteSettings extends SiteSettingsInterface { public static final String MAX_LINKS_KEY = "comment_max_links"; public static final String MODERATION_KEYS_KEY = "moderation_keys"; public static final String BLACKLIST_KEYS_KEY = "blacklist_keys"; + public static final String SOFTWARE_VERSION_KEY = "software_version"; + + private static final String BLOG_URL_KEY = "blog_url"; + private static final String BLOG_TITLE_KEY = "blog_title"; + private static final String BLOG_USERNAME_KEY = "username"; + private static final String BLOG_PASSWORD_KEY = "password"; + private static final String BLOG_TAGLINE_KEY = "blog_tagline"; private static final String BLOG_CATEGORY_ID_KEY = "categoryId"; private static final String BLOG_CATEGORY_PARENT_ID_KEY = "parentId"; private static final String BLOG_CATEGORY_DESCRIPTION_KEY = "categoryDescription"; private static final String BLOG_CATEGORY_NAME_KEY = "categoryName"; + // Requires WordPress 4.5.x or higher + private static final int REQUIRED_MAJOR_VERSION = 4; + private static final int REQUIRED_MINOR_VERSION = 3; + private static final String OPTION_ALLOWED = "open"; private static final String OPTION_DISALLOWED = "closed"; @@ -86,6 +96,22 @@ public void saveSettings() { public void onSuccess(long id, final Object result) { notifySavedOnUiThread(null); mRemoteSettings.copyFrom(mSettings); + + if (result != null) { + HashMap properties = new HashMap<>(); + if (result instanceof Map) { + Map resultMap = (Map) result; + Set keys = resultMap.keySet(); + for (String key : keys) { + Object currentValue = resultMap.get(key); + if (currentValue != null) { + properties.put(SAVED_ITEM_PREFIX + key, currentValue); + } + } + } + AnalyticsUtils.trackWithCurrentBlogDetails( + AnalyticsTracker.Stat.SITE_SETTINGS_SAVED_REMOTELY, properties); + } } @Override @@ -107,11 +133,18 @@ public void onFailure(long id, final Exception error) { */ @Override protected void fetchRemoteData() { - Object[] params = {mBlog.getRemoteBlogId(), mBlog.getUsername(), mBlog.getPassword()}; - // Need two interfaces or the first call gets aborted - instantiateInterface().callAsync(mOptionsCallback, ApiHelper.Methods.GET_OPTIONS, params); - instantiateInterface().callAsync(mCategoriesCallback, ApiHelper.Methods.GET_CATEGORIES, params); + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + Object[] params = {mBlog.getRemoteBlogId(), mBlog.getUsername(), mBlog.getPassword()}; + + // Need two interfaces or the first call gets aborted + instantiateInterface().callAsync(mOptionsCallback, ApiHelper.Methods.GET_OPTIONS, params); + instantiateInterface().callAsync(mCategoriesCallback, ApiHelper.Methods.GET_CATEGORIES, params); + } + }); + thread.run(); } /** @@ -151,6 +184,12 @@ public void onFailure(long id, Exception error) { public void onSuccess(long id, final Object result) { if (result instanceof Map) { AppLog.d(AppLog.T.API, "Received Options XML-RPC response."); + + if (!versionSupported((Map) result) && mActivity != null) { + notifyUpdatedOnUiThread(new XMLRPCException(mActivity.getString(R.string.site_settings_unsupported_version_error))); + return; + } + credentialsVerified(true); deserializeOptionsResponse(mRemoteSettings, (Map) result); @@ -171,6 +210,15 @@ public void onFailure(long id, final Exception error) { } }; + private boolean versionSupported(Map map) { + String version = getNestedMapValue(map, SOFTWARE_VERSION_KEY); + if (TextUtils.isEmpty(version)) return false; + String[] split = version.split("\\."); + return split.length > 0 && + Integer.valueOf(split[0]) >= REQUIRED_MAJOR_VERSION && + Integer.valueOf(split[1]) >= REQUIRED_MINOR_VERSION; + } + private Map serializeSelfHostedParams() { Map params = new HashMap<>(); @@ -233,10 +281,10 @@ private Map serializeSelfHostedParams() { } } if (mSettings.commentsRequireIdentity != mRemoteSettings.commentsRequireIdentity) { - params.put(REQUIRE_IDENTITY_KEY, String.valueOf(mSettings.commentsRequireIdentity)); + params.put(REQUIRE_IDENTITY_KEY, String.valueOf(mSettings.commentsRequireIdentity ? 1 : 0)); } if (mSettings.commentsRequireUserAccount != mRemoteSettings.commentsRequireUserAccount) { - params.put(REQUIRE_USER_ACCOUNT_KEY, String.valueOf(mSettings.commentsRequireUserAccount)); + params.put(REQUIRE_USER_ACCOUNT_KEY, String.valueOf(mSettings.commentsRequireUserAccount ? 1 : 0)); } if (mSettings.commentAutoApprovalKnownUsers != mRemoteSettings.commentAutoApprovalKnownUsers) { params.put(WHITELIST_KNOWN_USERS_KEY, String.valueOf(mSettings.commentAutoApprovalKnownUsers)); @@ -250,7 +298,11 @@ private Map serializeSelfHostedParams() { builder.append(key); builder.append("\n"); } - params.put(MODERATION_KEYS_KEY, builder.substring(0, builder.length() - 1)); + if (builder.length() > 1) { + params.put(MODERATION_KEYS_KEY, builder.substring(0, builder.length() - 1)); + } else { + params.put(MODERATION_KEYS_KEY, ""); + } } if (mSettings.blacklist != null && !mSettings.blacklist.equals(mRemoteSettings.blacklist)) { StringBuilder builder = new StringBuilder(); @@ -258,7 +310,11 @@ private Map serializeSelfHostedParams() { builder.append(key); builder.append("\n"); } - params.put(BLACKLIST_KEYS_KEY, builder.substring(0, builder.length() - 1)); + if (builder.length() > 1) { + params.put(BLACKLIST_KEYS_KEY, builder.substring(0, builder.length() - 1)); + } else { + params.put(BLACKLIST_KEYS_KEY, ""); + } } return params; @@ -286,9 +342,9 @@ private void deserializeOptionsResponse(SiteSettingsModel model, Map response) { String accountRequired = getNestedMapValue(response, REQUIRE_USER_ACCOUNT_KEY); String knownUsers = getNestedMapValue(response, WHITELIST_KNOWN_USERS_KEY); model.sendPingbacks = !TextUtils.isEmpty(sendPingbacks) && Integer.valueOf(sendPingbacks) > 0; - model.commentApprovalRequired = !TextUtils.isEmpty(approvalRequired) && Integer.valueOf(approvalRequired) > 0; + model.commentApprovalRequired = !TextUtils.isEmpty(approvalRequired) && Boolean.valueOf(approvalRequired); model.commentsRequireIdentity = !TextUtils.isEmpty(identityRequired) && Integer.valueOf(identityRequired) > 0; - model.commentsRequireUserAccount = !TextUtils.isEmpty(accountRequired) && Integer.valueOf(accountRequired) > 0; + model.commentsRequireUserAccount = !TextUtils.isEmpty(accountRequired) && Integer.valueOf(identityRequired) > 0; model.commentAutoApprovalKnownUsers = !TextUtils.isEmpty(knownUsers) && Boolean.valueOf(knownUsers); model.maxLinks = Integer.valueOf(getNestedMapValue(response, MAX_LINKS_KEY)); mRemoteSettings.holdForModeration = new ArrayList<>(); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/SettingsFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/prefs/SettingsFragment.java index e1a138ec7f8d..7c8881dc80be 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/SettingsFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/SettingsFragment.java @@ -197,7 +197,7 @@ public void onItemClick(AdapterView parent, View view, int position, long id) if (position != 0) { Map properties = new HashMap(); properties.put("forced_app_locale", conf.locale.toString()); - AnalyticsTracker.track(Stat.SETTINGS_LANGUAGE_SELECTION_FORCED, properties); + AnalyticsTracker.track(Stat.ACCOUNT_SETTINGS_LANGUAGE_SELECTION_FORCED, properties); } // Language is now part of metadata, so we need to refresh them diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/SiteSettingsFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/prefs/SiteSettingsFragment.java index 69e707037920..d5284d2795af 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/SiteSettingsFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/SiteSettingsFragment.java @@ -1,6 +1,5 @@ package org.wordpress.android.ui.prefs; -import android.annotation.SuppressLint; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; @@ -9,36 +8,33 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.res.Resources; -import android.graphics.Typeface; import android.os.Bundle; +import android.os.Handler; import android.preference.EditTextPreference; import android.preference.Preference; -import android.preference.PreferenceCategory; import android.preference.PreferenceFragment; -import android.preference.PreferenceGroup; import android.preference.PreferenceScreen; import android.support.annotation.NonNull; -import android.support.v4.content.ContextCompat; import android.text.TextUtils; import android.util.SparseBooleanArray; -import android.util.TypedValue; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.Window; +import android.view.WindowManager; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.ArrayAdapter; +import android.widget.Button; import android.widget.EditText; import android.widget.ListAdapter; import android.widget.ListView; -import android.widget.NumberPicker; -import android.widget.RelativeLayout; import android.widget.TextView; import org.wordpress.android.R; import org.wordpress.android.WordPress; +import org.wordpress.android.analytics.AnalyticsTracker; import org.wordpress.android.models.AccountHelper; import org.wordpress.android.models.Blog; import org.wordpress.android.ui.stats.StatsWidgetProvider; @@ -49,9 +45,10 @@ import org.wordpress.android.util.StringUtils; import org.wordpress.android.util.ToastUtils; import org.wordpress.android.util.WPActivityUtils; -import org.wordpress.android.util.widgets.WPEditText; -import org.wordpress.android.widgets.TypefaceCache; +import org.wordpress.android.util.WPPrefUtils; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -59,94 +56,161 @@ import de.greenrobot.event.EventBus; /** - * Handles changes to WordPress site settings. Syncs with host automatically when user leaves. + * Allows interfacing with WordPress site settings. Works with WP.com and WP.org v4.5+ (pending). + * + * Settings are synced automatically when local changes are made. */ public class SiteSettingsFragment extends PreferenceFragment implements Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener, AdapterView.OnItemLongClickListener, + ViewGroup.OnHierarchyChangeListener, Dialog.OnDismissListener, SiteSettingsInterface.SiteSettingsListener { + /** + * Use this argument to pass the {@link Integer} local blog ID to this fragment. + */ public static final String ARG_LOCAL_BLOG_ID = "local_blog_id"; + + /** + * When the user removes a site (by selecting Delete Site) the parent {@link Activity} result + * is set to this value and {@link Activity#finish()} is invoked. + */ public static final int RESULT_BLOG_REMOVED = Activity.RESULT_FIRST_USER; + /** + * Provides the regex to identify domain HTTP(S) protocol and/or 'www' sub-domain. + * + * Used to format user-facing {@link String}'s in certain preferences. + */ private static final String ADDRESS_FORMAT_REGEX = "^(https?://(w{3})?|www\\.)"; + + /** + * Used to move the Uncategorized category to the beginning of the category list. + */ + private static final int UNCATEGORIZED_CATEGORY_ID = 1; + + /** + * Request code used when creating the {@link RelatedPostsDialog}. + */ private static final int RELATED_POSTS_REQUEST_CODE = 1; - private static final int NO_REGION_LANG_CODE_LEN = 2; - private static final int REGION_SUBSTRING_INDEX = 3; + private static final int THREADING_REQUEST_CODE = 2; + private static final int PAGING_REQUEST_CODE = 3; + private static final int CLOSE_AFTER_REQUEST_CODE = 4; + private static final int MULTIPLE_LINKS_REQUEST_CODE = 5; + + private static final long FETCH_DELAY = 1000; + + // Reference to blog obtained from passed ID (ARG_LOCAL_BLOG_ID) private Blog mBlog; + + // Can interface with WP.com or WP.org private SiteSettingsInterface mSiteSettings; + + // Reference to the list of items being edited in the current list editor private List mEditingList; - private boolean mBlogDeleted; + + // Used to ensure that settings are only fetched once throughout the lifecycle of the fragment private boolean mShouldFetch; + // General settings private EditTextPreference mTitlePref; private EditTextPreference mTaglinePref; private EditTextPreference mAddressPref; - private DetailListPreference mLanguagePref; private DetailListPreference mPrivacyPref; + private DetailListPreference mLanguagePref; + + // Account settings (NOTE: only for WP.org) private EditTextPreference mUsernamePref; private EditTextPreference mPasswordPref; + + // Writing settings private WPSwitchPreference mLocationPref; private DetailListPreference mCategoryPref; private DetailListPreference mFormatPref; private Preference mRelatedPostsPref; - private WPSwitchPreference mAllowCommentsNested; + + // Discussion settings preview private WPSwitchPreference mAllowCommentsPref; - private WPSwitchPreference mSendPingbacksNested; private WPSwitchPreference mSendPingbacksPref; - private WPSwitchPreference mReceivePingbacksNested; private WPSwitchPreference mReceivePingbacksPref; + + // Discussion settings -> Defaults for New Posts + private WPSwitchPreference mAllowCommentsNested; + private WPSwitchPreference mSendPingbacksNested; + private WPSwitchPreference mReceivePingbacksNested; + + // Discussion settings -> Comments private WPSwitchPreference mIdentityRequiredPreference; private WPSwitchPreference mUserAccountRequiredPref; - private DetailListPreference mCloseAfterPref; + private Preference mCloseAfterPref; private DetailListPreference mSortByPref; private DetailListPreference mThreadingPref; - private DetailListPreference mPagingPref; + private Preference mPagingPref; private DetailListPreference mWhitelistPref; private Preference mMultipleLinksPref; private Preference mModerationHoldPref; private Preference mBlacklistPref; + + // Delete site option (NOTE: only for WP.org) private Preference mDeleteSitePref; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // make sure we have local site data - mBlog = WordPress.getBlog(getArguments().getInt(ARG_LOCAL_BLOG_ID, -1)); + Activity activity = getActivity(); - if (!NetworkUtils.checkConnection(getActivity()) || mBlog == null) { + // make sure we have local site data and a network connection, otherwise finish activity + mBlog = WordPress.getBlog(getArguments().getInt(ARG_LOCAL_BLOG_ID, -1)); + if (mBlog == null || !NetworkUtils.checkConnection(activity)) { getActivity().finish(); return; } + // track successful settings screen access + AnalyticsUtils.trackWithCurrentBlogDetails( + AnalyticsTracker.Stat.SITE_SETTINGS_ACCESSED); + + // setup state to fetch remote settings mShouldFetch = true; - mSiteSettings = SiteSettingsInterface.getInterface(getActivity(), mBlog, this); + + // initialize the appropriate settings interface (WP.com or WP.org) + mSiteSettings = SiteSettingsInterface.getInterface(activity, mBlog, this); setRetainInstance(true); addPreferencesFromResource(R.xml.site_settings); + + // toggle which preferences are shown and set references initPreferences(); } @Override - public void onResume() { - super.onResume(); - - mSiteSettings.init(mShouldFetch); - mShouldFetch = false; + public void onPause() { + super.onPause(); + WordPress.wpDB.saveBlog(mBlog); } @Override - public void onDestroy() { - super.onDestroy(); + public void onResume() { + super.onResume(); + + // always load cached settings + mSiteSettings.init(false); - // Assume user wanted changes propagated when they leave - if (!mBlogDeleted && mSiteSettings != null) { - mSiteSettings.saveSettings(); + if (mShouldFetch) { + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + // initialize settings with locally cached values, fetch remote on first pass + mSiteSettings.init(true); + // stop future calls from fetching remote settings + } + }, FETCH_DELAY); + mShouldFetch = false; } } @@ -154,10 +218,42 @@ public void onDestroy() { public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case RELATED_POSTS_REQUEST_CODE: - if (data == null) return; - mSiteSettings.setShowRelatedPosts(data.getBooleanExtra(RelatedPostsDialog.SHOW_RELATED_POSTS_KEY, false)); - mSiteSettings.setShowRelatedPostHeader(data.getBooleanExtra(RelatedPostsDialog.SHOW_HEADER_KEY, false)); - mSiteSettings.setShowRelatedPostImages(data.getBooleanExtra(RelatedPostsDialog.SHOW_IMAGES_KEY, false)); + // data is null if user cancelled editing Related Posts settings + if (data == null) break; + mSiteSettings.setShowRelatedPosts(data.getBooleanExtra( + RelatedPostsDialog.SHOW_RELATED_POSTS_KEY, false)); + mSiteSettings.setShowRelatedPostHeader(data.getBooleanExtra( + RelatedPostsDialog.SHOW_HEADER_KEY, false)); + mSiteSettings.setShowRelatedPostImages(data.getBooleanExtra( + RelatedPostsDialog.SHOW_IMAGES_KEY, false)); + mSiteSettings.saveSettings(); + break; + case THREADING_REQUEST_CODE: + if (data == null) break; + mSiteSettings.setShouldThreadComments(data.getBooleanExtra + (NumberPickerDialog.SWITCH_ENABLED_KEY, false)); + onPreferenceChange(mThreadingPref, data.getIntExtra( + NumberPickerDialog.CUR_VALUE_KEY, -1)); + break; + case PAGING_REQUEST_CODE: + if (data == null) break; + mSiteSettings.setShouldPageComments(data.getBooleanExtra + (NumberPickerDialog.SWITCH_ENABLED_KEY, false)); + onPreferenceChange(mPagingPref, data.getIntExtra( + NumberPickerDialog.CUR_VALUE_KEY, -1)); + break; + case CLOSE_AFTER_REQUEST_CODE: + if (data == null) break; + mSiteSettings.setShouldCloseAfter(data.getBooleanExtra + (NumberPickerDialog.SWITCH_ENABLED_KEY, false)); + onPreferenceChange(mCloseAfterPref, data.getIntExtra( + NumberPickerDialog.CUR_VALUE_KEY, -1)); + break; + case MULTIPLE_LINKS_REQUEST_CODE: + if (data == null) break; + int numLinks = data.getIntExtra(NumberPickerDialog.CUR_VALUE_KEY, -1); + if (numLinks < 0 || numLinks == mSiteSettings.getMultipleLinks()) return; + onPreferenceChange(mMultipleLinksPref, numLinks); break; } @@ -165,62 +261,57 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { } @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { - Context contextThemeWrapper = new ContextThemeWrapper(getActivity(), R.style.Calypso_SiteSettingsTheme); - LayoutInflater localInflater = inflater.cloneInContext(contextThemeWrapper); + // use a wrapper to apply the Calypso theme + Context themer = new ContextThemeWrapper(getActivity(), R.style.Calypso_SiteSettingsTheme); + LayoutInflater localInflater = inflater.cloneInContext(themer); View view = super.onCreateView(localInflater, container, savedInstanceState); - // Setup the preferences to handled long clicks if (view != null) { - ListView prefList = (ListView) view.findViewById(android.R.id.list); - Resources res = getResources(); + setupPreferenceList((ListView) view.findViewById(android.R.id.list), getResources()); + } - if (prefList != null && res != null) { - prefList.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() { - @Override - public void onChildViewAdded(View parent, View child) { - if (child.getId() == android.R.id.title && child instanceof TextView) { - Resources res = getResources(); - TextView title = (TextView) child; - title.setTypeface(TypefaceCache.getTypeface(getActivity(), - TypefaceCache.FAMILY_OPEN_SANS, - Typeface.BOLD, - TypefaceCache.VARIATION_LIGHT)); - title.setTextSize(TypedValue.COMPLEX_UNIT_PX, - res.getDimensionPixelSize(R.dimen.text_sz_medium)); - title.setTextColor(ContextCompat.getColor(getActivity(), - R.color.orange_jazzy)); - } - } + return view; + } - @Override - public void onChildViewRemoved(View parent, View child) { - } - }); - prefList.setOnItemLongClickListener(this); - prefList.setFooterDividersEnabled(false); - //noinspection deprecation - prefList.setOverscrollFooter(res.getDrawable(R.color.transparent)); - } + @Override + public void onChildViewAdded(View parent, View child) { + if (child.getId() == android.R.id.title && child instanceof TextView) { + // style preference category title views + TextView title = (TextView) child; + WPPrefUtils.layoutAsBody2(title); + } else { + // style preference title views + TextView title = (TextView) child.findViewById(android.R.id.title); + if (title != null) WPPrefUtils.layoutAsSubhead(title); } + } - return view; + @Override + public void onChildViewRemoved(View parent, View child) { + // NOP } @Override public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) { super.onPreferenceTreeClick(screen, preference); - // Add Action Bar to sub-screens + // More preference selected, style the Discussion screen if (preference == findPreference(getString(R.string.pref_key_site_more_discussion))) { Dialog dialog = ((PreferenceScreen) preference).getDialog(); - if (dialog != null) { - ListView prefList = (ListView) dialog.findViewById(android.R.id.list); - prefList.setOnItemLongClickListener(this); - String title = getString(R.string.site_settings_discussion_title); - WPActivityUtils.addToolbarToDialog(this, dialog, title); - } + if (dialog == null) return false; + + setupPreferenceList((ListView) dialog.findViewById(android.R.id.list), getResources()); + + // add Action Bar + String title = getString(R.string.site_settings_discussion_title); + WPActivityUtils.addToolbarToDialog(this, dialog, title); + + // track user accessing the full Discussion settings screen + AnalyticsUtils.trackWithCurrentBlogDetails( + AnalyticsTracker.Stat.SITE_SETTINGS_ACCESSED_MORE_SETTINGS); } return false; @@ -237,16 +328,24 @@ public boolean onPreferenceClick(Preference preference) { } else if (preference == mModerationHoldPref) { mEditingList = mSiteSettings.getModerationKeys(); showListEditorDialog(R.string.site_settings_moderation_hold_title, - R.string.hold_for_moderation_description); + R.string.site_settings_hold_for_moderation_description); return true; } else if (preference == mBlacklistPref) { mEditingList = mSiteSettings.getBlacklistKeys(); showListEditorDialog(R.string.site_settings_blacklist_title, - R.string.blacklist_description); + R.string.site_settings_blacklist_description); return true; } else if (preference == mDeleteSitePref) { removeBlogWithConfirmation(); return true; + } else if (preference == mCloseAfterPref) { + showCloseAfterDialog(); + return true; + } else if (preference == mPagingPref) { + showPagingDialog(); + return true; + } else if (preference == mCategoryPref || preference == mFormatPref) { + return !shouldShowListPreference((DetailListPreference) preference); } return false; @@ -272,7 +371,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { mSiteSettings.setPrivacy(Integer.valueOf(newValue.toString())); setDetailListPreferenceValue(mPrivacyPref, String.valueOf(mSiteSettings.getPrivacy()), - getPrivacySummary(mSiteSettings.getPrivacy())); + mSiteSettings.getPrivacyDescription()); } else if (preference == mAllowCommentsPref || preference == mAllowCommentsNested) { setAllowComments((Boolean) newValue); } else if (preference == mSendPingbacksPref || preference == mSendPingbacksNested) { @@ -281,24 +380,24 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { setReceivePingbacks((Boolean) newValue); } else if (preference == mCloseAfterPref) { mSiteSettings.setCloseAfter(Integer.parseInt(newValue.toString())); - setDetailListPreferenceValue(mCloseAfterPref, - newValue.toString(), - getCloseAfterSummary(mSiteSettings.getCloseAfter())); + if (mSiteSettings.getShouldCloseAfter()) { + mCloseAfterPref.setSummary(mSiteSettings.getCloseAfterDescription()); + } else { + mCloseAfterPref.setSummary(mSiteSettings.getCloseAfterDescription(0)); + } } else if (preference == mSortByPref) { mSiteSettings.setCommentSorting(Integer.parseInt(newValue.toString())); setDetailListPreferenceValue(mSortByPref, newValue.toString(), - getSortOrderSummary(mSiteSettings.getCommentSorting())); + mSiteSettings.getSortingDescription()); } else if (preference == mThreadingPref) { mSiteSettings.setThreadingLevels(Integer.parseInt(newValue.toString())); setDetailListPreferenceValue(mThreadingPref, newValue.toString(), - getThreadingSummary(mSiteSettings.getThreadingLevels())); + mSiteSettings.getThreadingDescription()); } else if (preference == mPagingPref) { mSiteSettings.setPagingCount(Integer.parseInt(newValue.toString())); - setDetailListPreferenceValue(mPagingPref, - newValue.toString(), - getPagingSummary(mSiteSettings.getPagingCount())); + mPagingPref.setSummary(mSiteSettings.getPagingDescription()); } else if (preference == mIdentityRequiredPreference) { mSiteSettings.setIdentityRequired((Boolean) newValue); } else if (preference == mUserAccountRequiredPref) { @@ -308,7 +407,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { } else if (preference == mMultipleLinksPref) { mSiteSettings.setMultipleLinks(Integer.parseInt(newValue.toString())); mMultipleLinksPref.setSummary(getResources() - .getQuantityString(R.plurals.multiple_links_summary, + .getQuantityString(R.plurals.site_settings_multiple_links_summary, mSiteSettings.getMultipleLinks(), mSiteSettings.getMultipleLinks())); } else if (preference == mUsernamePref) { @@ -333,6 +432,8 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { return false; } + mSiteSettings.saveSettings(); + return true; } @@ -349,6 +450,10 @@ public boolean onItemLongClick(AdapterView parent, View view, int position, l } else if (obj instanceof PreferenceHint) { PreferenceHint hintObj = (PreferenceHint) obj; if (hintObj.hasHint()) { + HashMap properties = new HashMap<>(); + properties.put("hint_shown", hintObj.getHint()); + AnalyticsUtils.trackWithCurrentBlogDetails( + AnalyticsTracker.Stat.SITE_SETTINGS_HINT_TOAST_SHOWN, properties); ToastUtils.showToast(getActivity(), hintObj.getHint(), ToastUtils.Duration.SHORT); } return true; @@ -360,6 +465,7 @@ public boolean onItemLongClick(AdapterView parent, View view, int position, l @Override public void onDismiss(DialogInterface dialog) { + mSiteSettings.saveSettings(); mEditingList = null; } @@ -367,18 +473,19 @@ public void onDismiss(DialogInterface dialog) { public void onSettingsUpdated(Exception error) { if (error != null) { ToastUtils.showToast(getActivity(), R.string.error_fetch_remote_site_settings); + getActivity().finish(); return; } - setPreferencesFromSiteSettings(); + if (isAdded()) setPreferencesFromSiteSettings(); } @Override public void onSettingsSaved(Exception error) { - int message = error == null ? - R.string.site_settings_save_success : R.string.error_post_remote_site_settings; - - ToastUtils.showToast(WordPress.getContext(), message); + if (error != null) { + ToastUtils.showToast(WordPress.getContext(), R.string.error_post_remote_site_settings); + return; + } mBlog.setBlogName(mSiteSettings.getTitle()); WordPress.wpDB.saveBlog(mBlog); EventBus.getDefault().post(new CoreEvents.BlogListChanged()); @@ -400,52 +507,65 @@ public void allowEditing(boolean allow) { if (mLanguagePref != null) mLanguagePref.setEnabled(allow); } + private void setupPreferenceList(ListView prefList, Resources res) { + if (prefList == null || res == null) return; + + // customize list dividers + prefList.setDividerHeight(1); + //noinspection deprecation + prefList.setDivider(getResources().getDrawable(R.drawable.preferences_divider)); + // handle long clicks on preferences to display hints + prefList.setOnItemLongClickListener(this); + // required to customize (Calypso) preference views + prefList.setOnHierarchyChangeListener(this); + // remove footer divider bar + prefList.setFooterDividersEnabled(false); + //noinspection deprecation + prefList.setOverscrollFooter(res.getDrawable(R.color.transparent)); + } + /** * Helper method to retrieve {@link Preference} references and initialize any data. */ private void initPreferences() { - mTitlePref = (EditTextPreference) getPref(R.string.pref_key_site_title); - mTaglinePref = (EditTextPreference) getPref(R.string.pref_key_site_tagline); - mAddressPref = (EditTextPreference) getPref(R.string.pref_key_site_address); - mPrivacyPref = (DetailListPreference) getPref(R.string.pref_key_site_visibility); - mLanguagePref = (DetailListPreference) getPref(R.string.pref_key_site_language); - mUsernamePref = (EditTextPreference) getPref(R.string.pref_key_site_username); - mPasswordPref = (EditTextPreference) getPref(R.string.pref_key_site_password); - mLocationPref = (WPSwitchPreference) getPref(R.string.pref_key_site_location); - mCategoryPref = (DetailListPreference) getPref(R.string.pref_key_site_category); - mFormatPref = (DetailListPreference) getPref(R.string.pref_key_site_format); - mAllowCommentsPref = (WPSwitchPreference) getPref(R.string.pref_key_site_allow_comments); - mAllowCommentsNested = (WPSwitchPreference) getPref(R.string.pref_key_site_allow_comments_nested); - mSendPingbacksPref = (WPSwitchPreference) getPref(R.string.pref_key_site_send_pingbacks); - mSendPingbacksNested = (WPSwitchPreference) getPref(R.string.pref_key_site_send_pingbacks_nested); - mReceivePingbacksPref = (WPSwitchPreference) getPref(R.string.pref_key_site_receive_pingbacks); - mReceivePingbacksNested = (WPSwitchPreference) getPref(R.string.pref_key_site_receive_pingbacks_nested); - mIdentityRequiredPreference = (WPSwitchPreference) getPref(R.string.pref_key_site_identity_required); - mUserAccountRequiredPref = (WPSwitchPreference) getPref(R.string.pref_key_site_user_account_required); - mCloseAfterPref = (DetailListPreference) getPref(R.string.pref_key_site_close_after); - mSortByPref = (DetailListPreference) getPref(R.string.pref_key_site_sort_by); - mThreadingPref = (DetailListPreference) getPref(R.string.pref_key_site_threading); - mPagingPref = (DetailListPreference) getPref(R.string.pref_key_site_paging); - mWhitelistPref = (DetailListPreference) getPref(R.string.pref_key_site_whitelist); - mMultipleLinksPref = getPref(R.string.pref_key_site_multiple_links); - mModerationHoldPref = getPref(R.string.pref_key_site_moderation_hold); - mBlacklistPref = getPref(R.string.pref_key_site_blacklist); - mRelatedPostsPref = findPreference(getString(R.string.pref_key_site_related_posts)); - mDeleteSitePref = findPreference(getString(R.string.pref_key_site_delete_site)); - mRelatedPostsPref.setOnPreferenceClickListener(this); - mMultipleLinksPref.setOnPreferenceClickListener(this); - mModerationHoldPref.setOnPreferenceClickListener(this); - mBlacklistPref.setOnPreferenceClickListener(this); - mDeleteSitePref.setOnPreferenceClickListener(this); + mTitlePref = (EditTextPreference) getChangePref(R.string.pref_key_site_title); + mTaglinePref = (EditTextPreference) getChangePref(R.string.pref_key_site_tagline); + mAddressPref = (EditTextPreference) getChangePref(R.string.pref_key_site_address); + mPrivacyPref = (DetailListPreference) getChangePref(R.string.pref_key_site_visibility); + mLanguagePref = (DetailListPreference) getChangePref(R.string.pref_key_site_language); + mUsernamePref = (EditTextPreference) getChangePref(R.string.pref_key_site_username); + mPasswordPref = (EditTextPreference) getChangePref(R.string.pref_key_site_password); + mLocationPref = (WPSwitchPreference) getChangePref(R.string.pref_key_site_location); + mCategoryPref = (DetailListPreference) getChangePref(R.string.pref_key_site_category); + mFormatPref = (DetailListPreference) getChangePref(R.string.pref_key_site_format); + mAllowCommentsPref = (WPSwitchPreference) getChangePref(R.string.pref_key_site_allow_comments); + mAllowCommentsNested = (WPSwitchPreference) getChangePref(R.string.pref_key_site_allow_comments_nested); + mSendPingbacksPref = (WPSwitchPreference) getChangePref(R.string.pref_key_site_send_pingbacks); + mSendPingbacksNested = (WPSwitchPreference) getChangePref(R.string.pref_key_site_send_pingbacks_nested); + mReceivePingbacksPref = (WPSwitchPreference) getChangePref(R.string.pref_key_site_receive_pingbacks); + mReceivePingbacksNested = (WPSwitchPreference) getChangePref(R.string.pref_key_site_receive_pingbacks_nested); + mIdentityRequiredPreference = (WPSwitchPreference) getChangePref(R.string.pref_key_site_identity_required); + mUserAccountRequiredPref = (WPSwitchPreference) getChangePref(R.string.pref_key_site_user_account_required); + mSortByPref = (DetailListPreference) getChangePref(R.string.pref_key_site_sort_by); + mThreadingPref = (DetailListPreference) getChangePref(R.string.pref_key_site_threading); + mWhitelistPref = (DetailListPreference) getChangePref(R.string.pref_key_site_whitelist); + mRelatedPostsPref = getClickPref(R.string.pref_key_site_related_posts); + mCloseAfterPref = getClickPref(R.string.pref_key_site_close_after); + mPagingPref = getClickPref(R.string.pref_key_site_paging); + mMultipleLinksPref = getClickPref(R.string.pref_key_site_multiple_links); + mModerationHoldPref = getClickPref(R.string.pref_key_site_moderation_hold); + mBlacklistPref = getClickPref(R.string.pref_key_site_blacklist); + mDeleteSitePref = getClickPref(R.string.pref_key_site_delete_site); // .com sites hide the Account category, self-hosted sites hide the Related Posts preference if (mBlog.isDotcomFlag()) { - removePreference(R.string.pref_key_site_screen, R.string.pref_key_site_account); - removePreference(R.string.pref_key_site_screen, R.string.pref_key_site_delete_site); + removeSelfHostedOnlyPreferences(); } else { - removePreference(R.string.pref_key_site_general, R.string.pref_key_site_language); - removePreference(R.string.pref_key_site_writing, R.string.pref_key_site_related_posts); + removeDotComOnlyPreferences(); } + + // hide all options except for Delete site and Enable Location if user is not admin + if (!mBlog.isAdmin()) hideAdminRequiredPreferences(); } private void showRelatedPostsDialog() { @@ -459,36 +579,47 @@ private void showRelatedPostsDialog() { relatedPosts.show(getFragmentManager(), "related-posts"); } + private void showNumberPickerDialog(Bundle args, int requestCode, String tag) { + NumberPickerDialog dialog = new NumberPickerDialog(); + dialog.setArguments(args); + dialog.setTargetFragment(this, requestCode); + dialog.show(getFragmentManager(), tag); + } + + private void showPagingDialog() { + Bundle args = new Bundle(); + args.putBoolean(NumberPickerDialog.SHOW_SWITCH_KEY, true); + args.putBoolean(NumberPickerDialog.SWITCH_ENABLED_KEY, mSiteSettings.getShouldPageComments()); + args.putString(NumberPickerDialog.SWITCH_TITLE_KEY, getString(R.string.site_settings_paging_title)); + args.putString(NumberPickerDialog.TITLE_KEY, getString(R.string.site_settings_paging_title)); + args.putString(NumberPickerDialog.HEADER_TEXT_KEY, getString(R.string.site_settings_paging_dialog_header)); + args.putInt(NumberPickerDialog.MIN_VALUE_KEY, 1); + args.putInt(NumberPickerDialog.MAX_VALUE_KEY, getResources().getInteger(R.integer.paging_limit)); + args.putInt(NumberPickerDialog.CUR_VALUE_KEY, mSiteSettings.getPagingCount()); + showNumberPickerDialog(args, PAGING_REQUEST_CODE, "paging-dialog"); + } + + private void showCloseAfterDialog() { + Bundle args = new Bundle(); + args.putBoolean(NumberPickerDialog.SHOW_SWITCH_KEY, true); + args.putBoolean(NumberPickerDialog.SWITCH_ENABLED_KEY, mSiteSettings.getShouldCloseAfter()); + args.putString(NumberPickerDialog.SWITCH_TITLE_KEY, getString(R.string.site_settings_close_after_dialog_switch_text)); + args.putString(NumberPickerDialog.TITLE_KEY, getString(R.string.site_settings_close_after_dialog_title)); + args.putString(NumberPickerDialog.HEADER_TEXT_KEY, getString(R.string.site_settings_close_after_dialog_header)); + args.putInt(NumberPickerDialog.MIN_VALUE_KEY, 1); + args.putInt(NumberPickerDialog.MAX_VALUE_KEY, getResources().getInteger(R.integer.close_after_limit)); + args.putInt(NumberPickerDialog.CUR_VALUE_KEY, mSiteSettings.getCloseAfter()); + showNumberPickerDialog(args, CLOSE_AFTER_REQUEST_CODE, "close-after-dialog"); + } + private void showMultipleLinksDialog() { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.Calypso_AlertDialog); - View view = View.inflate(getActivity(), R.layout.number_picker_dialog, null); - final NumberPicker numberPicker = (NumberPicker) view.findViewById(R.id.number_picker); - TextView detailText = (TextView) view.findViewById(R.id.number_picker_text); - //noinspection deprecation - detailText.setTextColor(getResources().getColor(R.color.grey_darken_10)); - detailText.setText(R.string.multiple_links_description); - numberPicker.setMaxValue(getResources().getInteger(R.integer.max_links_limit)); - numberPicker.setValue(Math.max(numberPicker.getMinValue(), Math.min(numberPicker.getMaxValue(), mSiteSettings.getMultipleLinks()))); - LayoutInflater inflater = LayoutInflater.from(getActivity()); - @SuppressLint("InflateParams") - View titleView = inflater.inflate(R.layout.detail_list_preference_title, null); - TextView titleText = ((TextView) titleView.findViewById(R.id.title)); - titleText.setText(R.string.site_settings_multiple_links_title); - titleText.setLayoutParams(new RelativeLayout.LayoutParams( - RelativeLayout.LayoutParams.MATCH_PARENT, - RelativeLayout.LayoutParams.WRAP_CONTENT)); - builder.setCustomTitle(titleView); - builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (mSiteSettings.getMultipleLinks() != numberPicker.getValue()) { - onPreferenceChange(mMultipleLinksPref, numberPicker.getValue()); - } - } - }); - builder.setNegativeButton(R.string.cancel, null); - builder.setView(view); - builder.create().show(); + Bundle args = new Bundle(); + args.putBoolean(NumberPickerDialog.SHOW_SWITCH_KEY, false); + args.putString(NumberPickerDialog.TITLE_KEY, getString(R.string.site_settings_multiple_links_title)); + args.putInt(NumberPickerDialog.MIN_VALUE_KEY, 0); + args.putInt(NumberPickerDialog.MAX_VALUE_KEY, getResources().getInteger(R.integer.max_links_limit)); + args.putInt(NumberPickerDialog.CUR_VALUE_KEY, mSiteSettings.getMultipleLinks()); + showNumberPickerDialog(args, MULTIPLE_LINKS_REQUEST_CODE, "multiple-links-dialog"); } private void setPreferencesFromSiteSettings() { @@ -501,36 +632,31 @@ private void setPreferencesFromSiteSettings() { changeLanguageValue(mSiteSettings.getLanguageCode()); setDetailListPreferenceValue(mPrivacyPref, String.valueOf(mSiteSettings.getPrivacy()), - getPrivacySummary(mSiteSettings.getPrivacy())); + mSiteSettings.getPrivacyDescription()); setCategories(); setPostFormats(); setAllowComments(mSiteSettings.getAllowComments()); setSendPingbacks(mSiteSettings.getSendPingbacks()); setReceivePingbacks(mSiteSettings.getReceivePingbacks()); - setDetailListPreferenceValue(mCloseAfterPref, - String.valueOf(mSiteSettings.getCloseAfter()), - getCloseAfterSummary(mSiteSettings.getCloseAfter())); setDetailListPreferenceValue(mSortByPref, String.valueOf(mSiteSettings.getCommentSorting()), - getSortOrderSummary(mSiteSettings.getCommentSorting())); + mSiteSettings.getSortingDescription()); setDetailListPreferenceValue(mThreadingPref, String.valueOf(mSiteSettings.getThreadingLevels()), - getThreadingSummary(mSiteSettings.getThreadingLevels())); - setDetailListPreferenceValue(mPagingPref, - String.valueOf(mSiteSettings.getPagingCount()), - getPagingSummary(mSiteSettings.getPagingCount())); + mSiteSettings.getThreadingDescription()); int approval = mSiteSettings.getManualApproval() ? mSiteSettings.getUseCommentWhitelist() ? 0 : -1 : 1; setDetailListPreferenceValue(mWhitelistPref, String.valueOf(approval), getWhitelistSummary(approval)); mMultipleLinksPref.setSummary(getResources() - .getQuantityString(R.plurals.multiple_links_summary, - mSiteSettings.getMultipleLinks(), - mSiteSettings.getMultipleLinks())); + .getQuantityString(R.plurals.site_settings_multiple_links_summary, + mSiteSettings.getMultipleLinks(), + mSiteSettings.getMultipleLinks())); mIdentityRequiredPreference.setChecked(mSiteSettings.getIdentityRequired()); mUserAccountRequiredPref.setChecked(mSiteSettings.getUserAccountRequired()); mThreadingPref.setValue(String.valueOf(mSiteSettings.getThreadingLevels())); - mPagingPref.setValue(String.valueOf(mSiteSettings.getPagingCount())); + mCloseAfterPref.setSummary(mSiteSettings.getCloseAfterDescription()); + mPagingPref.setSummary(mSiteSettings.getPagingDescription()); } private void setCategories() { @@ -547,7 +673,16 @@ private void setCategories() { int i = 0; for (Integer key : categories.keySet()) { entries[i] = categories.get(key); - values[i++] = String.valueOf(key); + values[i] = String.valueOf(key); + if (key == UNCATEGORIZED_CATEGORY_ID) { + CharSequence temp = entries[0]; + entries[0] = entries[i]; + entries[i] = temp; + temp = values[0]; + values[0] = values[i]; + values[i] = temp; + } + ++i; } mCategoryPref.setEntries(entries); @@ -559,14 +694,13 @@ private void setCategories() { private void setPostFormats() { // Ignore if there are no changes if (mSiteSettings.isSameFormatList(mFormatPref.getEntryValues())) { - mFormatPref.setValue(String.valueOf(mSiteSettings.getDefaultFormat())); + mFormatPref.setValue(String.valueOf(mSiteSettings.getDefaultPostFormat())); mFormatPref.setSummary(mSiteSettings.getDefaultPostFormatDisplay()); return; } Map formats = mSiteSettings.getFormats(); String[] formatKeys = mSiteSettings.getFormatKeys(); - if (formats == null || formatKeys == null) return; String[] entries = new String[formatKeys.length]; String[] values = new String[formatKeys.length]; @@ -577,7 +711,7 @@ private void setPostFormats() { mFormatPref.setEntries(entries); mFormatPref.setEntryValues(values); - mFormatPref.setValue(String.valueOf(mSiteSettings.getDefaultFormat())); + mFormatPref.setValue(String.valueOf(mSiteSettings.getDefaultPostFormat())); mFormatPref.setSummary(mSiteSettings.getDefaultPostFormatDisplay()); } @@ -632,7 +766,7 @@ private void changeLanguageValue(String newValue) { if (TextUtils.isEmpty(mLanguagePref.getSummary()) || !newValue.equals(mLanguagePref.getValue())) { mLanguagePref.setValue(newValue); - String summary = getLanguageString(newValue, languageLocale(newValue)); + String summary = getLanguageString(newValue, WPPrefUtils.languageLocale(newValue)); mLanguagePref.setSummary(summary); // update details to display in selected locale @@ -643,59 +777,15 @@ private void changeLanguageValue(String newValue) { } } - private String getPrivacySummary(int privacy) { - if (isAdded()) { - switch (privacy) { - case -1: - return getString(R.string.privacy_private); - case 0: - return getString(R.string.privacy_hidden); - case 1: - return getString(R.string.privacy_public); - } - } - return ""; - } - - private String getCloseAfterSummary(int period) { - if (!isAdded()) return ""; - if (period == 0) return getString(R.string.never); - return getResources().getQuantityString(R.plurals.days_quantity, period, period); - } - - private String getSortOrderSummary(int order) { - if (!isAdded()) return ""; - switch (order) { - case SiteSettingsInterface.ASCENDING_SORT: - return getString(R.string.oldest_first); - case SiteSettingsInterface.DESCENDING_SORT: - return getString(R.string.newest_first); - default: - return getString(R.string.unknown); - } - } - - private String getThreadingSummary(int levels) { - if (!isAdded()) return ""; - if (levels <= 1) return getString(R.string.none); - return String.format(getString(R.string.levels_quantity), levels); - } - - private String getPagingSummary(int count) { - if (!isAdded()) return ""; - if (count == 0) return getString(R.string.none); - return getResources().getQuantityString(R.plurals.paging_quantity, count, count); - } - private String getWhitelistSummary(int value) { if (isAdded()) { switch (value) { case -1: - return getString(R.string.whitelist_summary_no_users); + return getString(R.string.site_settings_whitelist_none_summary); case 0: - return getString(R.string.whitelist_summary_known_users); + return getString(R.string.site_settings_whitelist_known_summary); case 1: - return getString(R.string.whitelist_summary_all_users); + return getString(R.string.site_settings_whitelist_all_summary); } } return ""; @@ -730,36 +820,46 @@ private void showListEditorDialog(int titleRes, int footerRes) { } private View getListEditorView(final Dialog dialog, String footerText) { - View view = View.inflate(getActivity(), R.layout.list_editor, null); + Context themer = new ContextThemeWrapper(getActivity(), R.style.Calypso_SiteSettingsTheme); + View view = View.inflate(themer, R.layout.list_editor, null); ((TextView) view.findViewById(R.id.list_editor_footer_text)).setText(footerText); final MultiSelectListView list = (MultiSelectListView) view.findViewById(android.R.id.list); list.setEnterMultiSelectListener(new MultiSelectListView.OnEnterMultiSelect() { @Override public void onEnterMultiSelect() { - WPActivityUtils.changeDialogToolbarVisibility(dialog, View.GONE); + WPActivityUtils.setStatusBarColor(dialog.getWindow(), R.color.action_mode_status_bar_tint); } }); list.setExitMultiSelectListener(new MultiSelectListView.OnExitMultiSelect() { @Override public void onExitMultiSelect() { - WPActivityUtils.changeDialogToolbarVisibility(dialog, View.VISIBLE); + WPActivityUtils.setStatusBarColor(dialog.getWindow(), R.color.status_bar_tint); } }); list.setDeleteRequestListener(new MultiSelectListView.OnDeleteRequested() { @Override public boolean onDeleteRequested() { SparseBooleanArray checkedItems = list.getCheckedItemPositions(); + + HashMap properties = new HashMap<>(); + properties.put("num_items_deleted", checkedItems.size()); + AnalyticsUtils.trackWithCurrentBlogDetails( + AnalyticsTracker.Stat.SITE_SETTINGS_DELETED_LIST_ITEMS, properties); + ListAdapter adapter = list.getAdapter(); + List itemsToRemove = new ArrayList<>(); for (int i = 0; i < checkedItems.size(); i++) { final int index = checkedItems.keyAt(i); - if (checkedItems.get(index) && mEditingList.size() > index) { - mEditingList.remove(adapter.getItem(index).toString()); + if (checkedItems.get(index)) { + itemsToRemove.add(adapter.getItem(index).toString()); } } + mEditingList.removeAll(itemsToRemove); list.setAdapter(new ArrayAdapter<>(getActivity(), R.layout.wp_simple_list_item_1, mEditingList)); + mSiteSettings.saveSettings(); return true; } }); @@ -773,8 +873,9 @@ public boolean onDeleteRequested() { public void onClick(View v) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.Calypso_AlertDialog); - final EditText input = new WPEditText(getActivity()); - input.setHint("Enter a word or phrase"); + final EditText input = new EditText(getActivity()); + WPPrefUtils.layoutAsInput(input); + input.setHint(R.string.site_settings_list_editor_input_hint); builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { @@ -784,14 +885,24 @@ public void onClick(DialogInterface dialog, int which) { list.setAdapter(new ArrayAdapter<>(getActivity(), R.layout.wp_simple_list_item_1, mEditingList)); + mSiteSettings.saveSettings(); + AnalyticsUtils.trackWithCurrentBlogDetails( + AnalyticsTracker.Stat.SITE_SETTINGS_ADDED_LIST_ITEM); } } }); builder.setNegativeButton(R.string.cancel, null); AlertDialog alertDialog = builder.create(); - alertDialog.setView(input, 64, 64, 64, 0); + int spacing = getResources().getDimensionPixelSize(R.dimen.dlp_padding_start); + alertDialog.setView(input, spacing, spacing, spacing, 0); alertDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); alertDialog.show(); + alertDialog.getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT); + Button positive = alertDialog.getButton(DialogInterface.BUTTON_POSITIVE); + Button negative = alertDialog.getButton(DialogInterface.BUTTON_NEGATIVE); + if (positive != null) WPPrefUtils.layoutAsFlatButton(positive); + if (negative != null) WPPrefUtils.layoutAsFlatButton(negative); + WPActivityUtils.showKeyboard(input); } }); @@ -805,7 +916,6 @@ private void removeBlog() { ToastUtils.showToast(getActivity(), R.string.blog_removed_successfully); WordPress.wpDB.deleteLastBlogId(); WordPress.currentBlog = null; - mBlogDeleted = true; getActivity().setResult(RESULT_BLOG_REMOVED); // If the last blog is removed and the user is not signed in wpcom, broadcast a UserSignedOut event @@ -841,6 +951,10 @@ public void onClick(DialogInterface dialog, int whichButton) { dialogBuilder.create().show(); } + private boolean shouldShowListPreference(DetailListPreference preference) { + return preference != null && preference.getEntries() != null && preference.getEntries().length > 0; + } + /** * Generates display strings for given language codes. Used as entries in language preference. */ @@ -851,7 +965,7 @@ private String[] createLanguageDisplayStrings(CharSequence[] languageCodes) { for (int i = 0; i < languageCodes.length; ++i) { displayStrings[i] = StringUtils.capitalize(getLanguageString( - String.valueOf(languageCodes[i]), languageLocale(languageCodes[i].toString()))); + String.valueOf(languageCodes[i]), WPPrefUtils.languageLocale(languageCodes[i].toString()))); } return displayStrings; @@ -867,7 +981,7 @@ public String[] createLanguageDetailDisplayStrings(CharSequence[] languageCodes, String[] detailStrings = new String[languageCodes.length]; for (int i = 0; i < languageCodes.length; ++i) { detailStrings[i] = StringUtils.capitalize( - getLanguageString(languageCodes[i].toString(), languageLocale(locale))); + getLanguageString(languageCodes[i].toString(), WPPrefUtils.languageLocale(locale))); } return detailStrings; @@ -881,8 +995,8 @@ private String getLanguageString(String languageCode, Locale displayLocale) { return ""; } - Locale languageLocale = languageLocale(languageCode); - String displayLanguage = languageLocale.getDisplayLanguage(displayLocale); + Locale languageLocale = WPPrefUtils.languageLocale(languageCode); + String displayLanguage = StringUtils.capitalize(languageLocale.getDisplayLanguage(displayLocale)); String displayCountry = languageLocale.getDisplayCountry(displayLocale); if (!TextUtils.isEmpty(displayCountry)) { @@ -891,37 +1005,30 @@ private String getLanguageString(String languageCode, Locale displayLocale) { return displayLanguage; } - /** - * Gets a locale for the given language code. - */ - private Locale languageLocale(String languageCode) { - if (TextUtils.isEmpty(languageCode)) return Locale.getDefault(); - - if (languageCode.length() > NO_REGION_LANG_CODE_LEN) { - return new Locale(languageCode.substring(0, NO_REGION_LANG_CODE_LEN), - languageCode.substring(REGION_SUBSTRING_INDEX)); - } + private void hideAdminRequiredPreferences() { + WPPrefUtils.removePreference(this, R.string.pref_key_site_screen, R.string.pref_key_site_general); + WPPrefUtils.removePreference(this, R.string.pref_key_site_screen, R.string.pref_key_site_account); + WPPrefUtils.removePreference(this, R.string.pref_key_site_screen, R.string.pref_key_site_discussion); + WPPrefUtils.removePreference(this, R.string.pref_key_site_writing, R.string.pref_key_site_category); + WPPrefUtils.removePreference(this, R.string.pref_key_site_writing, R.string.pref_key_site_format); + WPPrefUtils.removePreference(this, R.string.pref_key_site_writing, R.string.pref_key_site_related_posts); + } - return new Locale(languageCode); + private void removeDotComOnlyPreferences() { + WPPrefUtils.removePreference(this, R.string.pref_key_site_general, R.string.pref_key_site_language); + WPPrefUtils.removePreference(this, R.string.pref_key_site_writing, R.string.pref_key_site_related_posts); } - /** - * Gets a preference and sets the {@link android.preference.Preference.OnPreferenceChangeListener}. - */ - private Preference getPref(int id) { - Preference pref = findPreference(getString(id)); - pref.setOnPreferenceChangeListener(this); - return pref; + private void removeSelfHostedOnlyPreferences() { + WPPrefUtils.removePreference(this, R.string.pref_key_site_screen, R.string.pref_key_site_account); + WPPrefUtils.removePreference(this, R.string.pref_key_site_screen, R.string.pref_key_site_delete_site); } - /** - * Removes a {@link Preference} from the {@link PreferenceCategory} with the given key. - */ - private void removePreference(int parentKey, int preference) { - PreferenceGroup parent = (PreferenceGroup) findPreference(getString(parentKey)); + private Preference getChangePref(int id) { + return WPPrefUtils.getPrefAndSetChangeListener(this, id, this); + } - if (parent != null) { - parent.removePreference(findPreference(getString(preference))); - } + private Preference getClickPref(int id) { + return WPPrefUtils.getPrefAndSetClickListener(this, id, this); } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/SiteSettingsInterface.java b/WordPress/src/main/java/org/wordpress/android/ui/prefs/SiteSettingsInterface.java index 81457680a11c..6d2aa3158696 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/SiteSettingsInterface.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/SiteSettingsInterface.java @@ -4,6 +4,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.database.Cursor; +import android.support.annotation.NonNull; import android.text.TextUtils; import org.wordpress.android.R; @@ -11,6 +12,7 @@ import org.wordpress.android.models.Blog; import org.wordpress.android.models.CategoryModel; import org.wordpress.android.models.SiteSettingsModel; +import org.wordpress.android.util.WPPrefUtils; import org.xmlrpc.android.ApiHelper; import org.xmlrpc.android.XMLRPCCallback; import org.xmlrpc.android.XMLRPCClientInterface; @@ -23,43 +25,124 @@ import java.util.Map; /** - * Keeps track of various settings and handles propagating changes where necessary. + * Interface for WordPress (.com and .org) Site Settings. The {@link SiteSettingsModel} class is + * used to store the following settings: * * - Title * - Tagline + * - Address * - Privacy * - Language - * - Address - * - Username - * - Password - * - Location: only stored locally - * - Default post category - * - Default post format - * - Related posts + * - Username (.org only) + * - Password (.org only) + * - Location (local device setting, not saved remotely) + * - Default Category + * - Default Format + * - Related Posts + * - Allow Comments + * - Send Pingbacks + * - Receive Pingbacks + * - Identity Required + * - User Account Required + * - Close Comments After + * - Comment Sort Order + * - Comment Threading + * - Comment Paging + * - Comment User Whitelist + * - Comment Link Limit + * - Comment Moderation Hold Filter + * - Comment Blacklist Filter + * + * This class is marked abstract. This is due to the fact that .org (self-hosted) and .com sites + * expose different API's to query and edit their respective settings (even though the options + * offered by each is roughly the same). To get an instance of this interface class use the + * {@link SiteSettingsInterface#getInterface(Activity, Blog, SiteSettingsListener)} method. It will + * determine which interface ({@link SelfHostedSiteSettings} or {@link DotComSiteSettings}) is + * appropriate for the given blog. */ + public abstract class SiteSettingsInterface { + + /** + * Name of the {@link SharedPreferences} that is used to store local settings. + */ public static final String SITE_SETTINGS_PREFS = "site-settings-prefs"; + + /** + * Key used to access the language preference stored in {@link SharedPreferences}. + */ public static final String LANGUAGE_PREF_KEY = "site-settings-language-pref"; + + /** + * Key used to access the location preference stored in {@link SharedPreferences}. + */ public static final String LOCATION_PREF_KEY = "site-settings-location-pref"; + + /** + * Key used to access the default category preference stored in {@link SharedPreferences}. + */ public static final String DEF_CATEGORY_PREF_KEY = "site-settings-category-pref"; + + /** + * Key used to access the default post format preference stored in {@link SharedPreferences}. + */ public static final String DEF_FORMAT_PREF_KEY = "site-settings-format-pref"; + /** + * Identifies an Ascending (oldest to newest) sort order. + */ public static final int ASCENDING_SORT = 0; + + /** + * Identifies an Descending (newest to oldest) sort order. + */ public static final int DESCENDING_SORT = 1; - private static final String STANDARD_POST_FORMAT = "standard"; + /** + * Used to prefix keys in an analytics property list. + */ + protected static final String SAVED_ITEM_PREFIX = "item_saved_"; + + /** + * Key for the Standard post format. Used as default if post format is not set/known. + */ + private static final String STANDARD_POST_FORMAT_KEY = "standard"; + + /** + * Standard post format value. Used as default display value if post format is unknown. + */ + private static final String STANDARD_POST_FORMAT = "Standard"; /** - * Returns a SharedPreference instance to interface with Site Settings. + * Instantiates the appropriate (self-hosted or .com) SiteSettingsInterface. + */ + public static SiteSettingsInterface getInterface(Activity host, Blog blog, SiteSettingsListener listener) { + if (host == null || blog == null) return null; + + if (blog.isDotcomFlag()) { + return new DotComSiteSettings(host, blog, listener); + } else { + return new SelfHostedSiteSettings(host, blog, listener); + } + } + + /** + * Returns an instance of the {@link this#SITE_SETTINGS_PREFS} {@link SharedPreferences}. */ public static SharedPreferences siteSettingsPreferences(Context context) { return context.getSharedPreferences(SITE_SETTINGS_PREFS, Context.MODE_PRIVATE); } + /** + * Gets the geo-tagging value stored in {@link SharedPreferences}, false by default. + */ public static boolean getGeotagging(Context context) { return siteSettingsPreferences(context).getBoolean(LOCATION_PREF_KEY, false); } + /** + * Gets the default category value stored in {@link SharedPreferences}, 0 by default. + */ public static String getDefaultCategory(Context context) { int id = siteSettingsPreferences(context).getInt(DEF_CATEGORY_PREF_KEY, 0); @@ -75,13 +158,21 @@ public static String getDefaultCategory(Context context) { return ""; } + /** + * Gets the default post format value stored in {@link SharedPreferences}, "" by default. + */ public static String getDefaultFormat(Context context) { return siteSettingsPreferences(context).getString(DEF_FORMAT_PREF_KEY, ""); } - public class AuthenticationError extends Exception { - } + /** + * Thrown when provided credentials are not valid. + */ + public class AuthenticationError extends Exception { } + /** + * Interface callbacks for settings events. + */ public interface SiteSettingsListener { /** * Called when settings have been updated with remote changes. @@ -108,28 +199,11 @@ public interface SiteSettingsListener { void onCredentialsValidated(Exception error); } - public void saveSettings() { - SiteSettingsTable.saveSettings(mSettings); - siteSettingsPreferences(mActivity).edit().putString(LANGUAGE_PREF_KEY, mSettings.language).apply(); - siteSettingsPreferences(mActivity).edit().putBoolean(LOCATION_PREF_KEY, mSettings.location).apply(); - siteSettingsPreferences(mActivity).edit().putInt(DEF_CATEGORY_PREF_KEY, mSettings.defaultCategory).apply(); - siteSettingsPreferences(mActivity).edit().putString(DEF_FORMAT_PREF_KEY, mSettings.defaultPostFormat).apply(); - } - - protected abstract void fetchRemoteData(); - /** - * Instantiates the appropriate (self-hosted or .com) SiteSettingsInterface. + * {@link SiteSettingsInterface} implementations should use this method to start a background + * task to load settings data from a remote source. */ - public static SiteSettingsInterface getInterface(Activity host, Blog blog, SiteSettingsListener listener) { - if (host == null || blog == null) return null; - - if (blog.isDotcomFlag()) { - return new DotComSiteSettings(host, blog, listener); - } else { - return new SelfHostedSiteSettings(host, blog, listener); - } - } + protected abstract void fetchRemoteData(); protected final Activity mActivity; protected final Blog mBlog; @@ -137,7 +211,7 @@ public static SiteSettingsInterface getInterface(Activity host, Blog blog, SiteS protected final SiteSettingsModel mSettings; protected final SiteSettingsModel mRemoteSettings; - private final HashMap mLanguageCodes; + private final Map mLanguageCodes; protected SiteSettingsInterface(Activity host, Blog blog, SiteSettingsListener listener) { mActivity = host; @@ -145,56 +219,80 @@ protected SiteSettingsInterface(Activity host, Blog blog, SiteSettingsListener l mListener = listener; mSettings = new SiteSettingsModel(); mRemoteSettings = new SiteSettingsModel(); - mLanguageCodes = new HashMap<>(); - } - - public String getAddress() { - return mSettings.address; + mLanguageCodes = WPPrefUtils.generateLanguageMap(host); } - public String getUsername() { - return mSettings.username; + public void saveSettings() { + SiteSettingsTable.saveSettings(mSettings); + siteSettingsPreferences(mActivity).edit().putString(LANGUAGE_PREF_KEY, mSettings.language).apply(); + siteSettingsPreferences(mActivity).edit().putBoolean(LOCATION_PREF_KEY, mSettings.location).apply(); + siteSettingsPreferences(mActivity).edit().putInt(DEF_CATEGORY_PREF_KEY, mSettings.defaultCategory).apply(); + siteSettingsPreferences(mActivity).edit().putString(DEF_FORMAT_PREF_KEY, mSettings.defaultPostFormat).apply(); } - public String getPassword() { - return mSettings.password; + public @NonNull String getTitle() { + return mSettings.title == null ? "" : mSettings.title; } - public String getTitle() { - return mSettings.title; + public @NonNull String getTagline() { + return mSettings.tagline == null ? "" : mSettings.tagline; } - public String getTagline() { - return mSettings.tagline; + public @NonNull String getAddress() { + return mSettings.address == null ? "" : mSettings.address; } public int getPrivacy() { return mSettings.privacy; } - public String getLanguageCode() { - return mSettings.language; + public @NonNull String getPrivacyDescription() { + if (mActivity != null) { + switch (getPrivacy()) { + case -1: + return mActivity.getString(R.string.site_settings_privacy_private_summary); + case 0: + return mActivity.getString(R.string.site_settings_privacy_hidden_summary); + case 1: + return mActivity.getString(R.string.site_settings_privacy_public_summary); + } + } + return ""; + } + + public @NonNull String getLanguageCode() { + return mSettings.language == null ? "" : mSettings.language; + } + + public @NonNull String getUsername() { + return mSettings.username == null ? "" : mSettings.username; + } + + public @NonNull String getPassword() { + return mSettings.password == null ? "" : mSettings.password; } public boolean getLocation() { return mSettings.location; } - public Map getFormats() { + public @NonNull Map getFormats() { + if (mSettings.postFormats == null) mSettings.postFormats = new HashMap<>(); return mSettings.postFormats; } - public String[] getFormatKeys() { + public @NonNull String[] getFormatKeys() { + if (mSettings.postFormatKeys == null) mSettings.postFormatKeys = new String[0]; return mSettings.postFormatKeys; } - public CategoryModel[] getCategories() { + public @NonNull CategoryModel[] getCategories() { + if (mSettings.categories == null) mSettings.categories = new CategoryModel[0]; return mSettings.categories; } - public Map getCategoryNames() { + public @NonNull Map getCategoryNames() { Map categoryNames = new HashMap<>(); - if (mSettings.categories != null && mSettings.categories.length > 0) { for (CategoryModel model : mSettings.categories) { categoryNames.put(model.id, model.name); @@ -204,34 +302,33 @@ public Map getCategoryNames() { return categoryNames; } - public String getDefaultPostFormat() { - if (TextUtils.isEmpty(mSettings.defaultPostFormat)) { - return STANDARD_POST_FORMAT; - } - - return mSettings.defaultPostFormat; - } - - public String getDefaultPostFormatDisplay() { - return getFormats().get(mSettings.defaultPostFormat); - } - public int getDefaultCategory() { return mSettings.defaultCategory; } - public String getDefaultCategoryForDisplay() { - if (getCategories() != null) { - for (CategoryModel model : getCategories()) { - if (model != null && model.id == getDefaultCategory()) { - return model.name; - } + public @NonNull String getDefaultCategoryForDisplay() { + for (CategoryModel model : getCategories()) { + if (model != null && model.id == getDefaultCategory()) { + return model.name; } } return ""; } + public @NonNull String getDefaultPostFormat() { + if (TextUtils.isEmpty(mSettings.defaultPostFormat) || !getFormats().containsKey(mSettings.defaultPostFormat)) { + mSettings.defaultPostFormat = STANDARD_POST_FORMAT_KEY; + } + return mSettings.defaultPostFormat; + } + + public @NonNull String getDefaultPostFormatDisplay() { + String defaultFormat = getFormats().get(getDefaultPostFormat()); + if (TextUtils.isEmpty(defaultFormat)) defaultFormat = STANDARD_POST_FORMAT; + return defaultFormat; + } + public boolean getShowRelatedPosts() { return mSettings.showRelatedPosts; } @@ -244,16 +341,115 @@ public boolean getShowRelatedPostImages() { return mSettings.showRelatedPostImages; } - public void setAddress(String address) { - mSettings.address = address; + public boolean getAllowComments() { + return mSettings.allowComments; } - public void setUsername(String username) { - mSettings.username = username; + public boolean getSendPingbacks() { + return mSettings.sendPingbacks; } - public void setPassword(String password) { - mSettings.password = password; + public boolean getReceivePingbacks() { + return mSettings.receivePingbacks; + } + + public boolean getShouldCloseAfter() { + return mSettings.shouldCloseAfter; + } + + public int getCloseAfter() { + return mSettings.closeCommentAfter; + } + + public @NonNull String getCloseAfterDescription() { + return getCloseAfterDescription(getCloseAfter()); + } + + public @NonNull String getCloseAfterDescription(int period) { + if (mActivity == null) return ""; + + if (period == 0) return mActivity.getString(R.string.never); + return mActivity.getResources().getQuantityString(R.plurals.days_quantity, period, period); + } + + public int getCommentSorting() { + return mSettings.sortCommentsBy; + } + + public @NonNull String getSortingDescription() { + if (mActivity == null) return ""; + + int order = getCommentSorting(); + switch (order) { + case SiteSettingsInterface.ASCENDING_SORT: + return mActivity.getString(R.string.oldest_first); + case SiteSettingsInterface.DESCENDING_SORT: + return mActivity.getString(R.string.newest_first); + default: + return mActivity.getString(R.string.unknown); + } + } + + public boolean getShouldThreadComments() { + return mSettings.shouldThreadComments; + } + + public int getThreadingLevels() { + return mSettings.threadingLevels; + } + + public @NonNull String getThreadingDescription() { + if (mActivity == null) return ""; + + int levels = getThreadingLevels(); + if (levels <= 1) return mActivity.getString(R.string.none); + return String.format(mActivity.getString(R.string.site_settings_threading_summary), levels); + } + + public boolean getShouldPageComments() { + return mSettings.shouldPageComments; + } + + public int getPagingCount() { + return mSettings.commentsPerPage; + } + + public @NonNull String getPagingDescription() { + if (mActivity == null) return ""; + + int count = getPagingCount(); + if (count == 0) return mActivity.getString(R.string.none); + return mActivity.getResources().getQuantityString(R.plurals.site_settings_paging_summary, count, count); + } + + public boolean getManualApproval() { + return mSettings.commentApprovalRequired; + } + + public boolean getIdentityRequired() { + return mSettings.commentsRequireIdentity; + } + + public boolean getUserAccountRequired() { + return mSettings.commentsRequireUserAccount; + } + + public boolean getUseCommentWhitelist() { + return mSettings.commentAutoApprovalKnownUsers; + } + + public int getMultipleLinks() { + return mSettings.maxLinks; + } + + public @NonNull List getModerationKeys() { + if (mSettings.holdForModeration == null) return new ArrayList<>(); + return mSettings.holdForModeration; + } + + public @NonNull List getBlacklistKeys() { + if (mSettings.blacklist == null) return new ArrayList<>(); + return mSettings.blacklist; } public void setTitle(String title) { @@ -264,6 +460,10 @@ public void setTagline(String tagline) { mSettings.tagline = tagline; } + public void setAddress(String address) { + mSettings.address = address; + } + public void setPrivacy(int privacy) { mSettings.privacy = privacy; } @@ -274,13 +474,21 @@ public void setLanguageCode(String languageCode) { } public void setLanguageId(int languageId) { - // Want to prevent O(n) language code lookup if there is no change + // want to prevent O(n) language code lookup if there is no change if (mSettings.languageId != languageId) { mSettings.languageId = languageId; mSettings.language = languageIdToLanguageCode(Integer.toString(languageId)); } } + public void setUsername(String username) { + mSettings.username = username; + } + + public void setPassword(String password) { + mSettings.password = password; + } + public void setLocation(boolean location) { mSettings.location = location; } @@ -289,116 +497,70 @@ public void setAllowComments(boolean allowComments) { mSettings.allowComments = allowComments; } - public boolean getAllowComments() { - return mSettings.allowComments; - } - public void setSendPingbacks(boolean sendPingbacks) { mSettings.sendPingbacks = sendPingbacks; } - public boolean getSendPingbacks() { - return mSettings.sendPingbacks; - } - public void setReceivePingbacks(boolean receivePingbacks) { mSettings.receivePingbacks = receivePingbacks; } - public boolean getReceivePingbacks() { - return mSettings.receivePingbacks; + public void setShouldCloseAfter(boolean shouldCloseAfter) { + mSettings.shouldCloseAfter = shouldCloseAfter; } public void setCloseAfter(int period) { mSettings.closeCommentAfter = period; } - public int getCloseAfter() { - return mSettings.closeCommentAfter; - } - public void setCommentSorting(int method) { mSettings.sortCommentsBy = method; } - public int getCommentSorting() { - return mSettings.sortCommentsBy; + public void setShouldThreadComments(boolean shouldThread) { + mSettings.shouldThreadComments = shouldThread; } public void setThreadingLevels(int levels) { mSettings.threadingLevels = levels; } - public int getThreadingLevels() { - return mSettings.threadingLevels; + public void setShouldPageComments(boolean shouldPage) { + mSettings.shouldPageComments= shouldPage; } public void setPagingCount(int count) { mSettings.commentsPerPage = count; } - public int getPagingCount() { - return mSettings.commentsPerPage; - } - public void setManualApproval(boolean required) { mSettings.commentApprovalRequired = required; } - public boolean getManualApproval() { - return mSettings.commentApprovalRequired; - } - public void setIdentityRequired(boolean required) { mSettings.commentsRequireIdentity = required; } - public boolean getIdentityRequired() { - return mSettings.commentsRequireIdentity; - } - public void setUserAccountRequired(boolean required) { mSettings.commentsRequireUserAccount = required; } - public boolean getUserAccountRequired() { - return mSettings.commentsRequireUserAccount; - } - public void setUseCommentWhitelist(boolean useWhitelist) { mSettings.commentAutoApprovalKnownUsers = useWhitelist; } - public boolean getUseCommentWhitelist() { - return mSettings.commentAutoApprovalKnownUsers; - } - public void setMultipleLinks(int count) { mSettings.maxLinks = count; } - public int getMultipleLinks() { - return mSettings.maxLinks; - } - public void setModerationKeys(List keys) { mSettings.holdForModeration = keys; } - public List getModerationKeys() { - if (mSettings.holdForModeration == null) return new ArrayList<>(); - return mSettings.holdForModeration; - } - public void setBlacklistKeys(List keys) { mSettings.blacklist = keys; } - public List getBlacklistKeys() { - if (mSettings.blacklist == null) return new ArrayList<>(); - return mSettings.blacklist; - } - public void setDefaultCategory(int category) { mSettings.defaultCategory = category; } @@ -407,20 +569,16 @@ public void setDefaultCategory(int category) { * Sets the default post format. * * @param format - * if null or empty default format is set to {@link SiteSettingsInterface#STANDARD_POST_FORMAT} + * if null or empty default format is set to {@link SiteSettingsInterface#STANDARD_POST_FORMAT_KEY} */ public void setDefaultFormat(String format) { if (TextUtils.isEmpty(format)) { - mSettings.defaultPostFormat = STANDARD_POST_FORMAT; + mSettings.defaultPostFormat = STANDARD_POST_FORMAT_KEY; } else { mSettings.defaultPostFormat = format.toLowerCase(); } } - public String getDefaultFormat() { - return TextUtils.isEmpty(mSettings.defaultPostFormat) ? STANDARD_POST_FORMAT : mSettings.defaultPostFormat; - } - public void setShowRelatedPosts(boolean relatedPosts) { mSettings.showRelatedPosts = relatedPosts; } @@ -433,6 +591,20 @@ public void setShowRelatedPostImages(boolean showImages) { mSettings.showRelatedPostImages = showImages; } + /** + * Determines if the current Moderation Hold list contains a given value. + */ + public boolean moderationHoldListContains(String value) { + return getModerationKeys().contains(value); + } + + /** + * Determines if the current Blacklist list contains a given value. + */ + public boolean blacklistListContains(String value) { + return getBlacklistKeys().contains(value); + } + /** * Checks if the provided list of post format IDs is the same (order dependent) as the current * list of Post Formats in the local settings object. @@ -483,38 +655,14 @@ public boolean isSameCategoryList(CharSequence[] ids) { * {@link SiteSettingsInterface#getInterface(Activity, Blog, SiteSettingsListener)} */ public SiteSettingsInterface init(boolean fetchRemote) { - generateLanguageMap(); - initSettings(fetchRemote); - - return this; - } - - /** - * Notifies listener that settings have been updated with the latest remote data. - */ - protected void notifyUpdatedOnUiThread(final Exception error) { - if (mActivity == null || mListener == null) return; - - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - mListener.onSettingsUpdated(error); - } - }); - } + loadCachedSettings(); - /** - * Notifies listener that settings have been saved or an error occurred while saving. - */ - protected void notifySavedOnUiThread(final Exception error) { - if (mActivity == null || mListener == null) return; + if (fetchRemote) { + fetchRemoteData(); + fetchPostFormats(); + } - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - mListener.onSettingsSaved(error); - } - }); + return this; } /** @@ -658,26 +806,30 @@ public void run() { } /** - * Attempts to load cached settings for the blog then fetches remote settings. + * Notifies listener that settings have been updated with the latest remote data. */ - private void initSettings(boolean fetchRemote) { - loadCachedSettings(); + protected void notifyUpdatedOnUiThread(final Exception error) { + if (mActivity == null || mActivity.isFinishing() || mListener == null) return; - if (fetchRemote) { - fetchRemoteData(); - fetchPostFormats(); - } + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + mListener.onSettingsUpdated(error); + } + }); } /** - * Creates a map from language codes to WordPress language IDs. + * Notifies listener that settings have been saved or an error occurred while saving. */ - private void generateLanguageMap() { - // Generate map of language codes - String[] languageIds = mActivity.getResources().getStringArray(R.array.lang_ids); - String[] languageCodes = mActivity.getResources().getStringArray(R.array.language_codes); - for (int i = 0; i < languageIds.length && i < languageCodes.length; ++i) { - mLanguageCodes.put(languageCodes[i], languageIds[i]); - } + protected void notifySavedOnUiThread(final Exception error) { + if (mActivity == null || mListener == null) return; + + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + mListener.onSettingsSaved(error); + } + }); } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/SummaryEditTextPreference.java b/WordPress/src/main/java/org/wordpress/android/ui/prefs/SummaryEditTextPreference.java index e4d2687a3c08..2ab7e866e4dc 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/SummaryEditTextPreference.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/SummaryEditTextPreference.java @@ -1,21 +1,25 @@ package org.wordpress.android.ui.prefs; -import android.app.Activity; import android.content.Context; +import android.content.DialogInterface; import android.content.res.Resources; import android.content.res.TypedArray; -import android.graphics.Typeface; +import android.os.Bundle; import android.preference.EditTextPreference; import android.support.annotation.NonNull; +import android.support.v7.app.AlertDialog; import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import org.wordpress.android.R; -import org.wordpress.android.util.ActivityUtils; -import org.wordpress.android.widgets.TypefaceCache; +import org.wordpress.android.util.WPActivityUtils; +import org.wordpress.android.util.WPPrefUtils; /** * Standard EditTextPreference that has attributes to limit summary length. @@ -34,6 +38,9 @@ public class SummaryEditTextPreference extends EditTextPreference implements Pre private int mLines; private int mMaxLines; private String mHint; + private AlertDialog mDialog; + private EditText mEditText; + private int mWhichButtonClicked; public SummaryEditTextPreference(Context context) { super(context); @@ -69,55 +76,99 @@ public SummaryEditTextPreference(Context context, AttributeSet attrs) { protected void onBindView(@NonNull View view) { super.onBindView(view); - Resources res = getContext().getResources(); TextView titleView = (TextView) view.findViewById(android.R.id.title); TextView summaryView = (TextView) view.findViewById(android.R.id.summary); - Typeface font = TypefaceCache.getTypeface(getContext(), - TypefaceCache.FAMILY_OPEN_SANS, - Typeface.NORMAL, - TypefaceCache.VARIATION_NORMAL); - if (titleView != null) { - if (isEnabled()) { - titleView.setTextColor(res.getColor(R.color.grey_dark)); - } else { - titleView.setTextColor(res.getColor(R.color.grey_lighten_10)); - } - titleView.setTextSize(16); - titleView.setTypeface(font); - } + if (titleView != null) WPPrefUtils.layoutAsSubhead(titleView); if (summaryView != null) { - if (isEnabled()) { - summaryView.setTextColor(res.getColor(R.color.grey_darken_10)); - } else { - summaryView.setTextColor(res.getColor(R.color.grey_lighten_10)); - } - summaryView.setInputType(getEditText().getInputType()); - summaryView.setTextSize(14); - summaryView.setTypeface(font); + WPPrefUtils.layoutAsBody1(summaryView); summaryView.setEllipsize(TextUtils.TruncateAt.END); + summaryView.setInputType(getEditText().getInputType()); if (mLines != -1) summaryView.setLines(mLines); if (mMaxLines != -1) summaryView.setMaxLines(mMaxLines); } } @Override - protected void onBindDialogView(View view) { + protected void showDialog(Bundle state) { + Context context = getContext(); + Resources res = context.getResources(); + AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.Calypso_AlertDialog); + View titleView = View.inflate(getContext(), R.layout.detail_list_preference_title, null); + mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE; + + builder.setPositiveButton(R.string.ok, this); + builder.setNegativeButton(res.getString(R.string.cancel).toUpperCase(), this); + if (titleView != null) { + TextView titleText = (TextView) titleView.findViewById(R.id.title); + if (titleText != null) { + titleText.setText(getTitle()); + } + + builder.setCustomTitle(titleView); + } else { + builder.setTitle(getTitle()); + } + + View view = View.inflate(getContext(), getDialogLayoutResource(), null); + if (view != null) { + onBindDialogView(view); + builder.setView(view); + } + + if ((mDialog = builder.create()) == null) return; + + if (state != null) { + mDialog.onRestoreInstanceState(state); + } + mDialog.setOnDismissListener(this); + mDialog.show(); + + Button positive = mDialog.getButton(DialogInterface.BUTTON_POSITIVE); + Button negative = mDialog.getButton(DialogInterface.BUTTON_NEGATIVE); + if (positive != null) WPPrefUtils.layoutAsFlatButton(positive); + if (negative != null) WPPrefUtils.layoutAsFlatButton(negative); + } + + @Override + protected void onBindDialogView(final View view) { super.onBindDialogView(view); if (view != null) { - EditText text = (EditText) view.findViewById(android.R.id.edit); - if (text != null) { - text.setSelection(text.getText().length()); + mEditText = getEditText(); + ViewParent oldParent = mEditText.getParent(); + if (oldParent != view) { + if (oldParent != null) { + ((ViewGroup) oldParent).removeView(mEditText); + } + ((View) oldParent).setPadding(((View) oldParent).getPaddingLeft(), 0, ((View) oldParent).getPaddingRight(), ((View) oldParent).getPaddingBottom()); + onAddEditTextToDialogView(view, mEditText); } + WPPrefUtils.layoutAsInput(mEditText); + mEditText.setSelection(mEditText.getText().length()); + WPActivityUtils.showKeyboard(mEditText); } } + @Override + public void onClick(DialogInterface dialog, int which) { + WPActivityUtils.hideKeyboard(mEditText); + mWhichButtonClicked = which; + } + + @Override + public void onDismiss(DialogInterface dialog) { + onDialogClosed(mWhichButtonClicked == DialogInterface.BUTTON_POSITIVE); + mDialog = null; + } + @Override protected void onDialogClosed(boolean positiveResult) { super.onDialogClosed(positiveResult); - ActivityUtils.hideKeyboard((Activity) getContext()); + if (positiveResult) { + callChangeListener(getEditText().getText()); + } } @Override diff --git a/WordPress/src/main/java/org/wordpress/android/util/WPActivityUtils.java b/WordPress/src/main/java/org/wordpress/android/util/WPActivityUtils.java index efbd7938f835..ff7e1c4d2145 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/WPActivityUtils.java +++ b/WordPress/src/main/java/org/wordpress/android/util/WPActivityUtils.java @@ -9,6 +9,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.os.Build; +import android.os.Handler; import android.preference.PreferenceManager; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; @@ -18,8 +19,12 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; import android.widget.LinearLayout; import android.widget.ListView; +import android.widget.TextView; import org.wordpress.android.R; import org.wordpress.android.ui.prefs.SettingsFragment; @@ -27,6 +32,8 @@ import java.util.Locale; public class WPActivityUtils { + private static final long SHOW_KEYBOARD_DELAY = 250; + // Hack! PreferenceScreens don't show the toolbar, so we'll manually add one // See: http://stackoverflow.com/a/27455363/309558 public static void addToolbarToDialog(final Fragment context, final Dialog dialog, String title) { @@ -72,7 +79,12 @@ public static void addToolbarToDialog(final Fragment context, final Dialog dialo dialog.getWindow().setWindowAnimations(R.style.DialogAnimations); - toolbar.setTitle(title); + TextView titleView = (TextView) toolbar.findViewById(R.id.toolbar_title); + titleView.setVisibility(View.VISIBLE); + titleView.setText(title); + + toolbar.setTitle(""); + toolbar.setContentInsetsAbsolute(0, 0); toolbar.setNavigationIcon(org.wordpress.android.R.drawable.ic_arrow_back_white_24dp); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override @@ -82,15 +94,29 @@ public void onClick(View v) { }); } - public static void changeDialogToolbarVisibility(Dialog dialog, int visibility) { - if (dialog == null || !dialog.isShowing()) return; - - View toolbar = dialog.findViewById(R.id.toolbar); - if (toolbar != null) { - toolbar.setVisibility(visibility); + public static void setStatusBarColor(Window window, int color) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + //noinspection deprecation + window.setStatusBarColor(window.getContext().getResources().getColor(color)); } } + public static void showKeyboard(final View view) { + (new Handler()).postDelayed(new Runnable() { + public void run() { + InputMethodManager inputMethodManager = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + inputMethodManager.toggleSoftInputFromWindow(view.getWindowToken(), InputMethodManager.SHOW_IMPLICIT, 0); + } + }, SHOW_KEYBOARD_DELAY); + } + + public static void hideKeyboard(final View view) { + InputMethodManager inputMethodManager = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + public static void applyLocale(Activity context, boolean restart) { SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); diff --git a/WordPress/src/main/java/org/wordpress/android/util/WPPrefUtils.java b/WordPress/src/main/java/org/wordpress/android/util/WPPrefUtils.java new file mode 100644 index 000000000000..618dcaca1e83 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/util/WPPrefUtils.java @@ -0,0 +1,226 @@ +package org.wordpress.android.util; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Typeface; +import android.preference.Preference; +import android.preference.PreferenceCategory; +import android.preference.PreferenceFragment; +import android.preference.PreferenceGroup; +import android.text.TextUtils; +import android.util.TypedValue; +import android.widget.EditText; +import android.widget.TextView; + +import org.wordpress.android.widgets.TypefaceCache; + +import org.wordpress.android.R; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +/** + * Design guidelines for Calypso-styled Site Settings (and likely other screens) + */ + +public class WPPrefUtils { + + /** + * Length of a {@link String} (representing a language code) when there is no region included. + * For example: "en" contains no region, "en_US" contains a region (US) + * + * Used to parse a language code {@link String} when creating a {@link Locale}. + */ + private static final int NO_REGION_LANG_CODE_LEN = 2; + + /** + * Index of a language code {@link String} where the region code begins. The language code + * format is cc_rr, where cc is the country code (e.g. en, es, az) and rr is the region code + * (e.g. us, au, gb). + */ + private static final int REGION_SUBSTRING_INDEX = 3; + + /** + * Gets a preference and sets the {@link android.preference.Preference.OnPreferenceChangeListener}. + */ + public static Preference getPrefAndSetClickListener(PreferenceFragment prefFrag, + int id, + Preference.OnPreferenceClickListener listener) { + Preference pref = prefFrag.findPreference(prefFrag.getString(id)); + if (pref != null) pref.setOnPreferenceClickListener(listener); + return pref; + } + + /** + * Gets a preference and sets the {@link android.preference.Preference.OnPreferenceChangeListener}. + */ + public static Preference getPrefAndSetChangeListener(PreferenceFragment prefFrag, + int id, + Preference.OnPreferenceChangeListener listener) { + Preference pref = prefFrag.findPreference(prefFrag.getString(id)); + if (pref != null) pref.setOnPreferenceChangeListener(listener); + return pref; + } + + /** + * Removes a {@link Preference} from the {@link PreferenceCategory} with the given key. + */ + public static void removePreference(PreferenceFragment prefFrag, int parentKey, int prefKey) { + String parentName = prefFrag.getString(parentKey); + String prefName = prefFrag.getString(prefKey); + PreferenceGroup parent = (PreferenceGroup) prefFrag.findPreference(parentName); + Preference child = prefFrag.findPreference(prefName); + + if (parent != null && child != null) { + parent.removePreference(child); + } + } + + /** + * Font : Open Sans + * Style : Normal + * Variation : Normal + */ + public static Typeface getNormalTypeface(Context context) { + return TypefaceCache.getTypeface(context, + TypefaceCache.FAMILY_OPEN_SANS, Typeface.NORMAL, TypefaceCache.VARIATION_NORMAL); + } + + /** + * Font : Open Sans + * Style : Bold + * Variation : Light + */ + public static Typeface getSemiboldTypeface(Context context) { + return TypefaceCache.getTypeface(context, + TypefaceCache.FAMILY_OPEN_SANS, Typeface.BOLD, TypefaceCache.VARIATION_LIGHT); + } + + /** + * Styles a {@link TextView} to display a large title against a dark background. + */ + public static void layoutAsLightTitle(TextView view) { + int size = view.getResources().getDimensionPixelSize(R.dimen.text_sz_extra_large); + setTextViewAttributes(view, size, R.color.white, getSemiboldTypeface(view.getContext())); + } + + /** + * Styles a {@link TextView} to display a large title against a light background. + */ + public static void layoutAsDarkTitle(TextView view) { + int size = view.getResources().getDimensionPixelSize(R.dimen.text_sz_extra_large); + setTextViewAttributes(view, size, R.color.grey_dark, getSemiboldTypeface(view.getContext())); + } + + /** + * Styles a {@link TextView} to display medium sized text as a header with sub-elements. + */ + public static void layoutAsSubhead(TextView view) { + int color = view.isEnabled() ? R.color.grey_dark : R.color.grey_lighten_10; + int size = view.getResources().getDimensionPixelSize(R.dimen.text_sz_large); + setTextViewAttributes(view, size, color, getNormalTypeface(view.getContext())); + } + + /** + * Styles a {@link TextView} to display smaller text. + */ + public static void layoutAsBody1(TextView view) { + int color = view.isEnabled() ? R.color.grey_darken_10 : R.color.grey_lighten_10; + int size = view.getResources().getDimensionPixelSize(R.dimen.text_sz_medium); + setTextViewAttributes(view, size, color, getNormalTypeface(view.getContext())); + } + + /** + * Styles a {@link TextView} to display smaller text with the orange accent color. + */ + public static void layoutAsBody2(TextView view) { + int size = view.getResources().getDimensionPixelSize(R.dimen.text_sz_medium); + setTextViewAttributes(view, size, R.color.orange_jazzy, getSemiboldTypeface(view.getContext())); + } + + /** + * Styles a {@link TextView} to display very small helper text. + */ + public static void layoutAsCaption(TextView view) { + int size = view.getResources().getDimensionPixelSize(R.dimen.text_sz_small); + setTextViewAttributes(view, size, R.color.grey_darken_10, getNormalTypeface(view.getContext())); + } + + /** + * Styles a {@link TextView} to display text in a button. + */ + public static void layoutAsFlatButton(TextView view) { + int size = view.getResources().getDimensionPixelSize(R.dimen.text_sz_medium); + setTextViewAttributes(view, size, R.color.blue_medium, getSemiboldTypeface(view.getContext())); + } + + /** + * Styles a {@link TextView} to display text in a button. + */ + public static void layoutAsRaisedButton(TextView view) { + int size = view.getResources().getDimensionPixelSize(R.dimen.text_sz_medium); + setTextViewAttributes(view, size, R.color.white, getSemiboldTypeface(view.getContext())); + } + + /** + * Styles a {@link TextView} to display text in an editable text field. + */ + public static void layoutAsInput(EditText view) { + int size = view.getResources().getDimensionPixelSize(R.dimen.text_sz_large); + setTextViewAttributes(view, size, R.color.grey_dark, getNormalTypeface(view.getContext())); + view.setHintTextColor(view.getResources().getColor(R.color.grey_lighten_10)); + view.setTextColor(view.getResources().getColor(R.color.grey_dark)); + } + + /** + * Styles a {@link TextView} to display selected numbers in a {@link android.widget.NumberPicker}. + */ + public static void layoutAsNumberPickerSelected(TextView view) { + int size = view.getResources().getDimensionPixelSize(R.dimen.text_sz_triple_extra_large); + setTextViewAttributes(view, size, R.color.blue_medium, getSemiboldTypeface(view.getContext())); + } + + /** + * Styles a {@link TextView} to display non-selected numbers in a {@link android.widget.NumberPicker}. + */ + public static void layoutAsNumberPickerPeek(TextView view) { + int size = view.getResources().getDimensionPixelSize(R.dimen.text_sz_large); + setTextViewAttributes(view, size, R.color.grey_dark, getNormalTypeface(view.getContext())); + } + + public static void setTextViewAttributes(TextView textView, int size, int colorRes, Typeface typeface) { + textView.setTypeface(typeface); + textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, size); + textView.setTextColor(textView.getResources().getColor(colorRes)); + } + + /** + * Gets a locale for the given language code. + */ + public static Locale languageLocale(String languageCode) { + if (TextUtils.isEmpty(languageCode)) return Locale.getDefault(); + + if (languageCode.length() > NO_REGION_LANG_CODE_LEN) { + return new Locale(languageCode.substring(0, NO_REGION_LANG_CODE_LEN), + languageCode.substring(REGION_SUBSTRING_INDEX)); + } + + return new Locale(languageCode); + } + + /** + * Creates a map from language codes to WordPress language IDs. + */ + public static Map generateLanguageMap(Activity activity) { + String[] languageIds = activity.getResources().getStringArray(R.array.lang_ids); + String[] languageCodes = activity.getResources().getStringArray(R.array.language_codes); + + Map languageMap = new HashMap<>(); + for (int i = 0; i < languageIds.length && i < languageCodes.length; ++i) { + languageMap.put(languageCodes[i], languageIds[i]); + } + + return languageMap; + } +} diff --git a/WordPress/src/main/java/org/xmlrpc/android/ApiHelper.java b/WordPress/src/main/java/org/xmlrpc/android/ApiHelper.java index 7de3ca772edb..f0b01844633d 100644 --- a/WordPress/src/main/java/org/xmlrpc/android/ApiHelper.java +++ b/WordPress/src/main/java/org/xmlrpc/android/ApiHelper.java @@ -10,13 +10,10 @@ import com.android.volley.DefaultRetryPolicy; import com.android.volley.NetworkResponse; import com.android.volley.RedirectError; -import com.android.volley.Request; -import com.android.volley.toolbox.JsonObjectRequest; import com.android.volley.toolbox.RequestFuture; import com.android.volley.toolbox.StringRequest; import com.google.gson.Gson; -import org.json.JSONObject; import org.wordpress.android.WordPress; import org.wordpress.android.analytics.AnalyticsTracker; import org.wordpress.android.datasets.CommentTable; @@ -25,7 +22,6 @@ import org.wordpress.android.models.Comment; import org.wordpress.android.models.CommentList; import org.wordpress.android.models.FeatureSet; -import org.wordpress.android.networking.WPDelayedHurlStack; import org.wordpress.android.ui.media.MediaGridFragment.Filter; import org.wordpress.android.ui.stats.StatsUtils; import org.wordpress.android.ui.stats.StatsWidgetProvider; @@ -34,20 +30,14 @@ import org.wordpress.android.util.AppLog.T; import org.wordpress.android.util.DateTimeUtils; import org.wordpress.android.util.MapUtils; -import org.wordpress.android.util.UrlUtils; import org.wordpress.android.util.helpers.MediaFile; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.io.StringReader; -import java.net.URL; -import java.security.GeneralSecurityException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -59,7 +49,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLHandshakeException; public class ApiHelper { diff --git a/WordPress/src/main/res/color/number_picker_title.xml b/WordPress/src/main/res/color/number_picker_title.xml new file mode 100644 index 000000000000..ad850970ea5e --- /dev/null +++ b/WordPress/src/main/res/color/number_picker_title.xml @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/WordPress/src/main/res/color/related_posts_list_header.xml b/WordPress/src/main/res/color/related_posts_list_header.xml index 31c580903a59..6c730e29ce1e 100644 --- a/WordPress/src/main/res/color/related_posts_list_header.xml +++ b/WordPress/src/main/res/color/related_posts_list_header.xml @@ -4,7 +4,7 @@ xmlns:android="http://schemas.android.com/apk/res/android"> @@ -29,9 +33,10 @@ android:paddingRight="@dimen/margin_extra_large" android:paddingEnd="@dimen/margin_extra_large" android:paddingTop="@dimen/margin_extra_large" + android:textSize="@dimen/text_sz_small" android:textColor="@color/grey_darken_10" /> - + - + + android:orientation="vertical"> + + + android:layout_marginLeft="24dp" + android:layout_marginRight="24dp" + android:textSize="@dimen/text_sz_small" + android:textColor="@color/grey_darken_10" + app:fontFamily="openSans" + app:fontVariation="normal" /> - + android:orientation="vertical"> + + + + + + + + + + diff --git a/WordPress/src/main/res/layout/related_posts_dialog.xml b/WordPress/src/main/res/layout/related_posts_dialog.xml index cde1b3844ac3..bdd5e8e3e8e9 100644 --- a/WordPress/src/main/res/layout/related_posts_dialog.xml +++ b/WordPress/src/main/res/layout/related_posts_dialog.xml @@ -5,7 +5,10 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" - android:orientation="vertical"> + android:paddingTop="@dimen/site_settings_divider_height" + android:paddingBottom="@dimen/site_settings_divider_height" + android:orientation="vertical" + android:background="@color/grey_lighten_30"> + android:orientation="vertical" + android:background="@color/white"> @@ -32,9 +36,9 @@ android:id="@+id/toggleRelatedPostsSummary" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="@string/related_posts_switch_summary" + android:text="@string/site_settings_rp_switch_summary" android:textColor="@color/grey_darken_10" - android:textSize="@dimen/text_sz_medium" + android:textSize="@dimen/text_sz_small" app:fontFamily="openSans" app:fontVariation="normal" /> @@ -43,14 +47,18 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="12dp" - android:text="@string/related_posts_show_header_title" /> + android:textColor="@color/grey_dark" + android:textSize="@dimen/text_sz_medium" + android:text="@string/site_settings_rp_show_header_title" /> + android:textColor="@color/grey_dark" + android:textSize="@dimen/text_sz_medium" + android:text="@string/site_settings_rp_show_images_title" /> + app:fontVariation="light" /> @@ -104,7 +112,7 @@ android:textStyle="bold" android:textSize="@dimen/text_sz_medium" android:textColor="@color/grey_dark" - android:text="@string/related_post_preview1_title" + android:text="@string/site_settings_rp_preview1_title" app:fontFamily="openSans" app:fontVariation="light" /> @@ -116,7 +124,7 @@ android:textStyle="italic" android:textSize="@dimen/text_sz_related_post_small" android:textColor="@color/grey_dark" - android:text="@string/related_post_preview1_site" + android:text="@string/site_settings_rp_preview1_site" app:fontFamily="openSans" app:fontVariation="normal" /> @@ -135,7 +143,7 @@ android:textStyle="bold" android:textSize="@dimen/text_sz_medium" android:textColor="@color/grey_dark" - android:text="@string/related_post_preview2_title" + android:text="@string/site_settings_rp_preview2_title" app:fontFamily="openSans" app:fontVariation="light" /> @@ -147,7 +155,7 @@ android:textStyle="italic" android:textSize="@dimen/text_sz_related_post_small" android:textColor="@color/grey_dark" - android:text="@string/related_post_preview2_site" + android:text="@string/site_settings_rp_preview2_site" app:fontFamily="openSans" app:fontVariation="normal" /> @@ -166,7 +174,7 @@ android:textStyle="bold" android:textSize="@dimen/text_sz_medium" android:textColor="@color/grey_dark" - android:text="@string/related_post_preview3_title" + android:text="@string/site_settings_rp_preview3_title" app:fontFamily="openSans" app:fontVariation="light" /> @@ -178,7 +186,7 @@ android:textStyle="italic" android:textSize="@dimen/text_sz_related_post_small" android:textColor="@color/grey_dark" - android:text="@string/related_post_preview3_site" + android:text="@string/site_settings_rp_preview3_site" app:fontFamily="openSans" app:fontVariation="normal" /> diff --git a/WordPress/src/main/res/layout/toolbar.xml b/WordPress/src/main/res/layout/toolbar.xml index 2027c8bcd7c8..a1344d3d0cb1 100644 --- a/WordPress/src/main/res/layout/toolbar.xml +++ b/WordPress/src/main/res/layout/toolbar.xml @@ -10,4 +10,18 @@ app:contentInsetLeft="@dimen/toolbar_content_offset" app:contentInsetStart="@dimen/toolbar_content_offset" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" - app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" /> + app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"> + + + + diff --git a/WordPress/src/main/res/values/colors.xml b/WordPress/src/main/res/values/colors.xml index 8fc43508eb5e..d98c303ff972 100644 --- a/WordPress/src/main/res/values/colors.xml +++ b/WordPress/src/main/res/values/colors.xml @@ -76,6 +76,7 @@ #006b98 + #ff517188 @color/semi_transparent_grey_dark diff --git a/WordPress/src/main/res/values/dimens.xml b/WordPress/src/main/res/values/dimens.xml index e9f2f0174eb1..b4bb5a833a67 100644 --- a/WordPress/src/main/res/values/dimens.xml +++ b/WordPress/src/main/res/values/dimens.xml @@ -93,6 +93,7 @@ 16sp 20sp 24sp + 26sp 24dp 32dp diff --git a/WordPress/src/main/res/values/integers.xml b/WordPress/src/main/res/values/integers.xml index 11f6f7a0fee0..842ecb672708 100644 --- a/WordPress/src/main/res/values/integers.xml +++ b/WordPress/src/main/res/values/integers.xml @@ -18,5 +18,7 @@ 100 + 300 + 365 diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index 9ad7b3868f87..01dc9a7017d3 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -177,7 +177,7 @@ URL copied to clipboard - Comments + @string/comments Live preview @@ -222,8 +222,8 @@ Preview Stats Trash - Delete - More + @string/delete + @string/more Back Revert @@ -465,139 +465,124 @@ wp_pref_site_delete_site wp_pref_site_remove_site + + + + + Discussion + Privacy + Related Posts + More + Comments + Close after + Oldest first + Newest first + + 1 day + %d days + + + General Account Writing + @string/discussion + Defaults for New Posts + @string/comments + + Site Title Tagline Address - Privacy - Username - Password + @string/privacy @string/language + @string/username + @string/password Enable Location - Default Category - Default Format - Related Posts - Discussion - Danger! - Defaults for New Posts + Default Category + Default Format + @string/related_posts + @string/more Allow Comments Send Pingbacks Receive Pingbacks - More - Comments - Close after + Must include name and email + Users must be signed in + @string/close_after Sort by Threading Paging - Must be manually approved - Must include name and email - Users must be signed in - Automatically approve comments Automatically approve Links in comments - Require manual approval for more than %d links Hold for Moderation Blacklist - Start Over Delete Site - Remove Site - In a few words, explain what this site is about - A short description or catchy phrase to describe your blog - Changing your address is not currently supported - Controls who can see your site - Language this blog is primarily written in - Current user account - Change your password - Automatically add location data to your posts - Sets new post category - Sets new post format - Show or hide related posts in reader - View and change your sites discussion settings - Allow readers to post comments - Attempt to notify any blogs linked to from the article - Allow link notifications from other blogs - Disallow comments after the specified time - Determines the order comments are displayed - Allow nested comments to a certain depth - Display comments in chunks of a specified size - Comments must be manually approved - Comment author must fill out name and e-mail - Users must be registered and logged in to comment - Comment author must have a previously approved comment - Ignores link limit from known users - Comments that match a filter are put in the moderation queue - Comments that match a filter are marked as spam - Show related content after posts - Disconnected, editing disabled. - Site settings updated - In the Default article settings, there are three options. These settings are defaults for new posts or pages, which can always be changed individually on each article. - The Allow Comment option allows you to enable or disable comments by default. - You can override thee settings for individual posts. - Show Related Posts - Related Posts displays relevant content from your site below your posts. - Show Header - Show Images - Related Posts - Big iPhone/iPad Update Now Available - in \"Mobile\" - The WordPress for Android App Gets a Big Facelift - in \"Apps\" - Upgrade Focus: VideoPress For Weddings - in \"Upgrade\" - When a comment contains any of these words in its content, name, URL, e-mail, or IP, it will be held in the moderation queue. You can enter partial words, so \"press\" will match \"WordPress.\" - When a comment contains any of these words in its content, name, URL, e-mail, or IP, it will be marked as spam. You can enter partial words, so \"press\" will match \"WordPress.\" - Require approval for comments that include more than this number of links. - Oldest first - Newest first - No items - + + + Public + Hidden + Private + %d levels + Comments from all users + Comments from known users + @string/none + + Require manual approval for everyone\'s comments. + Automatically approve if the user has a previously approved comment. + Automatically approve everyone\'s comments. + + Require approval for more than 1 link Require approval for more than %d links + + 1 comment per page + %d comments per page + + + Your site is visible to everyone and may be indexed by search engines + Your site is visible to everyone but asks search engines not to index it + Your site is visible only to you and users you approve + + + No comments Known users\' comments All users - - -1 - 0 - 1 - - - Require manual approval for everyone\'s comments. - Automatically approve if the user has a previously approved comment. - Automatically approve everyone\'s comments. - - - @string/never - One day - One week - One month - - - 0 - 1 - 7 - 30 - @string/oldest_first @string/newest_first - - 0 - 1 - @string/none Two levels Three levels Four levels Five levels + Six levels + Seven levels + Eight levels + Nine levels + Ten levels + + + @string/site_settings_privacy_public_summary + @string/site_settings_privacy_hidden_summary + @string/site_settings_privacy_private_summary + + + + + -1 + 0 + 1 + + + 0 + 1 1 @@ -605,51 +590,89 @@ 3 4 5 + 6 + 7 + 8 + 9 + 10 - - @string/none - 50 comments per page - 100 comments per page - 200 comments per page - - - 0 - 50 - 100 - 200 - - - 1 day - %d days - - - 1 comment per page - %d comments per page - - %d levels - Comments from all users - Comments from known users - @string/none - Privacy - Public - Hidden - Private - - @string/privacy_public - @string/privacy_hidden - @string/privacy_private - - - Your site is visible to everyone and may be indexed by search engines - Your site is visible to everyone but asks search engines not to index it - Your site is visible only to you and users you approve - - + 1 0 -1 + + In a few words, explain what this site is about + A short description or catchy phrase to describe your blog + Changing your address is not currently supported + Controls who can see your site + Language this blog is primarily written in + Current user account + Change your password + Automatically add location data to your posts + Sets new post category + Sets new post format + Show or hide related posts in reader + View all available Discussion settings + View and change your sites discussion settings + Allow readers to post comments + Attempt to notify any blogs linked to from the article + Allow link notifications from other blogs + Disallow comments after the specified time + Determines the order comments are displayed + Allow nested comments to a certain depth + Display comments in chunks of a specified size + Comments must be manually approved + Comment author must fill out name and e-mail + Users must be registered and logged in to comment + Comment author must have a previously approved comment + Ignores link limit from known users + Comments that match a filter are put in the moderation queue + Comments that match a filter are marked as spam + Removes your site data from the app + + + Show Related Posts + Related Posts displays relevant content from your site below your posts. + Show Header + Show Images + @string/related_posts + Big iPhone/iPad Update Now Available + in \"Mobile\" + The WordPress for Android App Gets a Big Facelift + in \"Apps\" + Upgrade Focus: VideoPress For Weddings + in \"Upgrade\" + + + @string/learn_more + You can override these settings for individual posts. + + + No items + Enter a word or phrase + When a comment contains any of these words in its content, name, URL, e-mail, or IP, it will be held in the moderation queue. You can enter partial words, so \"press\" will match \"WordPress.\" + When a comment contains any of these words in its content, name, URL, e-mail, or IP, it will be marked as spam. You can enter partial words, so \"press\" will match \"WordPress.\" + + + @string/discussion + Close commenting + Comments per page + Break comment threads into multiple pages. + @string/close_after + Automatically close comments on articles. + Automatically close + Require approval for comments that include more than this number of links. + + + Unsupported WordPress version + Disconnected, editing disabled. + + + + + Open source licenses Privacy @@ -695,7 +718,7 @@ Views Visitors Likes - Comments + @string/comments Visitors and Views @@ -706,7 +729,7 @@ Authors Referrers Videos - Comments + @string/comments Search Terms Publicize Followers @@ -728,7 +751,7 @@ Views Clicks Plays - Comments + @string/comments Followers Since @@ -1134,7 +1157,7 @@ Reader Post Tags & Blogs %1$d of %2$d - Comments + @string/comments Followed tags @@ -1335,7 +1358,7 @@ Publish Blog Posts Settings - Comments + @string/comments Switch Site View Admin View Site diff --git a/WordPress/src/main/res/values/styles_calypso.xml b/WordPress/src/main/res/values/styles_calypso.xml index 779a6b97e338..ca3ac4c82330 100644 --- a/WordPress/src/main/res/values/styles_calypso.xml +++ b/WordPress/src/main/res/values/styles_calypso.xml @@ -8,35 +8,48 @@ @style/Calypso.PopupMenu @style/Calypso.TextAppearance @style/Calypso.TextAppearance - @style/Calypso.ActionMode - + + - -