diff --git a/WordPress/src/main/AndroidManifest.xml b/WordPress/src/main/AndroidManifest.xml index 23ac7a6c8c1f..9509fb43b7e1 100644 --- a/WordPress/src/main/AndroidManifest.xml +++ b/WordPress/src/main/AndroidManifest.xml @@ -92,6 +92,7 @@ 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); - } + @SuppressWarnings("unused") + public void onEventMainThread(ConnectionChangeReceiver.ConnectionChangeEvent event) { + FragmentManager fragmentManager = getFragmentManager(); + SiteSettingsFragment siteSettingsFragment = + (SiteSettingsFragment) fragmentManager.findFragmentByTag(KEY_SETTINGS_FRAGMENT); - /** - * 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 (siteSettingsFragment != null) { + siteSettingsFragment.allowEditing(event.isConnected()); - // 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()); + if (!event.isConnected()) { + ToastUtils.showToast(this, getString(R.string.site_settings_disconnected_toast)); } - - 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 new file mode 100644 index 000000000000..ce9410c5a9b9 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/DetailListPreference.java @@ -0,0 +1,271 @@ +package org.wordpress.android.ui.prefs; + +import android.content.res.Resources; +import android.support.annotation.NonNull; +import android.support.v7.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.TypedArray; +import android.graphics.Typeface; +import android.os.Bundle; +import android.preference.ListPreference; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ListView; +import android.widget.RadioButton; +import android.widget.TextView; + +import org.wordpress.android.R; +import org.wordpress.android.widgets.TypefaceCache; + +/** + * Custom {@link ListPreference} used to display detail text per item. + */ + +public class DetailListPreference extends ListPreference + implements SiteSettingsFragment.HasHint { + private DetailListAdapter mListAdapter; + private String[] mDetails; + private int mStartingIndex; + private int mSelectedIndex; + private String mHint; + private AlertDialog mDialog; + private int mWhichButtonClicked; + + public DetailListPreference(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.DetailListPreference); + + for (int i = 0; i < array.getIndexCount(); ++i) { + int index = array.getIndex(i); + if (index == R.styleable.DetailListPreference_entryDetails) { + int id = array.getResourceId(index, -1); + if (id != -1) { + mDetails = array.getResources().getStringArray(id); + } else { + mDetails = null; + } + } else if (index == R.styleable.DetailListPreference_longClickHint) { + mHint = array.getString(index); + } + } + + array.recycle(); + } + + public DetailListPreference(Context context) { + super(context); + + mSelectedIndex = 0; + mDetails = null; + setLayoutResource(R.layout.detail_list_preference); + } + + @Override + protected void onBindView(@NonNull View view) { + super.onBindView(view); + + setupView((TextView) view.findViewById(android.R.id.title), + R.dimen.text_sz_large, R.color.grey_dark, R.color.grey_lighten_10); + setupView((TextView) view.findViewById(android.R.id.summary), + R.dimen.text_sz_medium, R.color.grey_darken_10, R.color.grey_lighten_10); + } + + @Override + protected void showDialog(Bundle state) { + Context context = getContext(); + Resources res = context.getResources(); + AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.Calypso_AlertDialog); + + mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE; + builder.setPositiveButton(R.string.ok, this); + builder.setNegativeButton(res.getString(R.string.cancel).toUpperCase(), this); + + mListAdapter = new DetailListAdapter(getContext(), R.layout.detail_list_preference, mDetails); + mStartingIndex = mSelectedIndex = findIndexOfValue(getValue()); + + builder.setSingleChoiceItems(mListAdapter, mSelectedIndex, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + if (mSelectedIndex != which) { + mSelectedIndex = which; + mListAdapter.notifyDataSetChanged(); + setValue(getEntryValues()[mSelectedIndex].toString()); + notifyChanged(); + } + } + }); + + View titleView = View.inflate(getContext(), R.layout.detail_list_preference_title, null); + + if (titleView != null) { + TextView titleText = (TextView) titleView.findViewById(R.id.title); + if (titleText != null) { + titleText.setText(getTitle()); + } + + builder.setCustomTitle(titleView); + } else { + builder.setTitle(getTitle()); + } + + if ((mDialog = builder.create()) == null) return; + + if (state != null) { + mDialog.onRestoreInstanceState(state); + } + mDialog.setOnDismissListener(this); + mDialog.show(); + + ListView listView = mDialog.getListView(); + Button positive = mDialog.getButton(DialogInterface.BUTTON_POSITIVE); + Button negative = mDialog.getButton(DialogInterface.BUTTON_NEGATIVE); + Typeface typeface = TypefaceCache.getTypeface(getContext(), + TypefaceCache.FAMILY_OPEN_SANS, + Typeface.BOLD, + TypefaceCache.VARIATION_LIGHT); + + if (listView != null) { + listView.setDividerHeight(0); + listView.setClipToPadding(true); + listView.setBackgroundColor(res.getColor(R.color.grey_lighten_30)); + listView.setPadding(0, 0, 0, res.getDimensionPixelSize(R.dimen.site_settings_divider_height)); + } + + 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); + } + } + + @Override + public void onClick(DialogInterface dialog, int which) { + mWhichButtonClicked = which; + } + + @Override + public void onDismiss(DialogInterface dialog) { + mDialog = null; + onDialogClosed(mWhichButtonClicked == DialogInterface.BUTTON_POSITIVE); + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + CharSequence[] values = getEntryValues(); + if (values != null && mSelectedIndex < values.length) { + String value = values[positiveResult ? mSelectedIndex : mStartingIndex].toString(); + callChangeListener(value); + } + } + + @Override + public boolean hasHint() { + return !TextUtils.isEmpty(mHint); + } + + @Override + public String getHintText() { + return mHint; + } + + public void refreshAdapter() { + if (mListAdapter != null) { + mListAdapter.notifyDataSetChanged(); + } + } + + public void setDetails(String[] details) { + mDetails = details; + refreshAdapter(); + } + + /** + * Helper method to style the Preference screen view + */ + private void setupView(TextView view, int sizeRes, int enabledColorRes, int disabledColorRes) { + if (view != null) { + Resources res = getContext().getResources(); + Typeface typeface = TypefaceCache.getTypeface(getContext(), + TypefaceCache.FAMILY_OPEN_SANS, + Typeface.NORMAL, + TypefaceCache.VARIATION_NORMAL); + + view.setTypeface(typeface); + view.setTextSize(TypedValue.COMPLEX_UNIT_PX, res.getDimensionPixelSize(sizeRes)); + view.setTextColor(res.getColor(isEnabled() ? enabledColorRes : disabledColorRes)); + } + } + + private class DetailListAdapter extends ArrayAdapter { + public DetailListAdapter(Context context, int resource, String[] objects) { + super(context, resource, objects); + } + + @Override + public View getView(final int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = View.inflate(getContext(), R.layout.detail_list_preference, null); + } + + final RadioButton radioButton = (RadioButton) convertView.findViewById(R.id.radio); + TextView mainText = (TextView) convertView.findViewById(R.id.main_text); + TextView detailText = (TextView) convertView.findViewById(R.id.detail_text); + + if (mainText != null && position < getEntries().length) { + mainText.setText(getEntries()[position]); + mainText.setTypeface(TypefaceCache.getTypeface(getContext(), + TypefaceCache.FAMILY_OPEN_SANS, + Typeface.NORMAL, + TypefaceCache.VARIATION_NORMAL)); + } + + if (detailText != null && position < mDetails.length) { + detailText.setText(mDetails[position]); + detailText.setTypeface(TypefaceCache.getTypeface(getContext(), + TypefaceCache.FAMILY_OPEN_SANS, + Typeface.NORMAL, + TypefaceCache.VARIATION_NORMAL)); + } + + if (radioButton != null) { + radioButton.setChecked(mSelectedIndex == position); + radioButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + changeSelection(radioButton, position); + } + }); + } + + convertView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + changeSelection(radioButton, position); + } + }); + + return convertView; + } + + private void changeSelection(RadioButton radioButton, int position) { + CharSequence[] values = getEntryValues(); + + if (radioButton != null && values != null && position < values.length) { + mSelectedIndex = position; + radioButton.setChecked(true); + callChangeListener(values[position]); + } + } + } +} 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 new file mode 100644 index 000000000000..1ced6579777b --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/SiteSettingsFragment.java @@ -0,0 +1,675 @@ +package org.wordpress.android.ui.prefs; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Typeface; +import android.os.Bundle; +import android.preference.EditTextPreference; +import android.preference.Preference; +import android.preference.PreferenceCategory; +import android.preference.PreferenceFragment; +import android.support.annotation.NonNull; +import android.text.TextUtils; +import android.util.Log; +import android.util.TypedValue; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import com.android.volley.VolleyError; +import com.wordpress.rest.RestRequest; + +import org.json.JSONObject; +import org.wordpress.android.R; +import org.wordpress.android.WordPress; +import org.wordpress.android.models.Blog; +import org.wordpress.android.networking.RestClientUtils; +import org.wordpress.android.util.AppLog; +import org.wordpress.android.util.CoreEvents; +import org.wordpress.android.util.MapUtils; +import org.wordpress.android.util.NetworkUtils; +import org.wordpress.android.util.StringUtils; +import org.wordpress.android.util.ToastUtils; +import org.wordpress.android.widgets.TypefaceCache; +import org.xmlrpc.android.ApiHelper; +import org.xmlrpc.android.XMLRPCCallback; +import org.xmlrpc.android.XMLRPCClientInterface; +import org.xmlrpc.android.XMLRPCFactory; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import de.greenrobot.event.EventBus; + +/** + * Handles changes to WordPress site settings. Syncs with host automatically when user leaves. + */ + +public class SiteSettingsFragment extends PreferenceFragment + implements Preference.OnPreferenceChangeListener, AdapterView.OnItemLongClickListener { + public interface HasHint { + boolean hasHint(); + String getHintText(); + } + + private HashMap mLanguageCodes = new HashMap<>(); + private Blog mBlog; + private EditTextPreference mTitlePreference; + private EditTextPreference mTaglinePreference; + private EditTextPreference mAddressPreference; + private DetailListPreference mLanguagePreference; + private DetailListPreference mPrivacyPreference; + + // Most recent remote site data. Current local data is used if remote data cannot be fetched. + private String mRemoteTitle; + private String mRemoteTagline; + private String mRemoteAddress; + private int mRemotePrivacy; + private String mRemoteLanguage; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setRetainInstance(true); + + if (!NetworkUtils.checkConnection(getActivity())) { + getActivity().finish(); + } + + // make sure we have local site data + mBlog = WordPress.getBlog( + getArguments().getInt(BlogPreferencesActivity.ARG_LOCAL_BLOG_ID, -1)); + if (mBlog == null) return; + + mRemoteTitle = mBlog.getBlogName(); + + // inflate Site Settings preferences from XML + addPreferencesFromResource(R.xml.site_settings); + + // set preference references, add change listeners, and setup various entries and values + initPreferences(); + + // fetch remote site data + fetchRemoteData(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + + // Assume user wanted changes propagated when they leave + applyChanges(); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + Context contextThemeWrapper = new ContextThemeWrapper(getActivity(), R.style.Calypso_SiteSettingsTheme); + LayoutInflater localInflater = inflater.cloneInContext(contextThemeWrapper); + 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(); + + 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(res.getColor(R.color.orange_jazzy)); + } + } + + @Override + public void onChildViewRemoved(View parent, View child) { + } + }); + prefList.setOnItemLongClickListener(this); + prefList.setFooterDividersEnabled(false); + prefList.setOverscrollFooter(res.getDrawable(R.color.transparent)); + } + } + + return view; + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (newValue == null) return false; + + if (preference == mTitlePreference) { + changeEditTextPreferenceValue(mTitlePreference, newValue.toString()); + return true; + } else if (preference == mTaglinePreference) { + changeEditTextPreferenceValue(mTaglinePreference, newValue.toString()); + return true; + } else if (preference == mAddressPreference) { + changeEditTextPreferenceValue(mAddressPreference, newValue.toString()); + return true; + } else if (preference == mLanguagePreference) { + changeLanguageValue(newValue.toString()); + return true; + } else if (preference == mPrivacyPreference) { + changePrivacyValue(Integer.valueOf(newValue.toString())); + return true; + } + + return false; + } + + @Override + public boolean onItemLongClick(AdapterView> parent, View view, int position, long id) { + ListView listView = (ListView) parent; + ListAdapter listAdapter = listView.getAdapter(); + Object obj = listAdapter.getItem(position); + + if (obj != null) { + if (obj instanceof View.OnLongClickListener) { + View.OnLongClickListener longListener = (View.OnLongClickListener) obj; + return longListener.onLongClick(view); + } else if (obj instanceof HasHint && ((HasHint) obj).hasHint()) { + ToastUtils.showToast(getActivity(), ((HasHint)obj).getHintText(), ToastUtils.Duration.SHORT); + return true; + } + } + + return false; + } + + public void allowEditing(boolean allow) { + if (mTitlePreference != null) mTitlePreference.setEnabled(allow); + if (mTaglinePreference != null) mTaglinePreference.setEnabled(allow); + if (mAddressPreference != null) mAddressPreference.setEnabled(false); + if (mPrivacyPreference != null) mPrivacyPreference.setEnabled(allow); + if (mLanguagePreference != null) mLanguagePreference.setEnabled(allow); + if (mLanguagePreference != null) mLanguagePreference.setEnabled(allow); + } + + /** + * Helper method to retrieve {@link Preference} references and initialize any data. + */ + private void initPreferences() { + // Title preference + if (null != (mTitlePreference = + (EditTextPreference) findPreference(getString(R.string.pref_key_site_title)))) { + mTitlePreference.setOnPreferenceChangeListener(this); + changeEditTextPreferenceValue(mTitlePreference, StringUtils.unescapeHTML(mRemoteTitle)); + } + + // Tagline preference + if (null != (mTaglinePreference = + (EditTextPreference) findPreference(getString(R.string.pref_key_site_tagline)))) { + mTaglinePreference.setOnPreferenceChangeListener(this); + } + + // Address preferences + if (null != (mAddressPreference = + (EditTextPreference) findPreference(getString(R.string.pref_key_site_address)))) { + mAddressPreference.setOnPreferenceChangeListener(this); + } + + // Privacy preference, removed for self-hosted sites + if (null != (mPrivacyPreference = + (DetailListPreference) findPreference(getString(R.string.pref_key_site_visibility)))) { + if (!mBlog.isDotcomFlag()) { + removePreference(R.string.pref_key_site_general, mPrivacyPreference); + } else { + mPrivacyPreference.setOnPreferenceChangeListener(this); + } + } + + // Language preference, removed for self-hosted sites + if (null != (mLanguagePreference = + (DetailListPreference) findPreference(getString(R.string.pref_key_site_language)))) { + if (!mBlog.isDotcomFlag()) { + removePreference(R.string.pref_key_site_general, mLanguagePreference); + } else { + mLanguagePreference.setOnPreferenceChangeListener(this); + + // Generate map of language codes + String[] languageIds = getResources().getStringArray(R.array.lang_ids); + String[] languageCodes = getResources().getStringArray(R.array.language_codes); + for (int i = 0; i < languageIds.length && i < languageCodes.length; ++i) { + mLanguageCodes.put(languageCodes[i], languageIds[i]); + } + } + } + } + + /** + * Request remote site data via the WordPress REST API. + */ + private void fetchRemoteData() { + if (mBlog.isDotcomFlag()) { + WordPress.getRestClientUtils().getGeneralSettings( + String.valueOf(mBlog.getRemoteBlogId()), new RestRequest.Listener() { + @Override + public void onResponse(JSONObject response) { + if (isAdded()) { + handleResponseToWPComSettingsRequest(response); + } + } + }, new RestRequest.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + handleSettingsFetchError(error.toString()); + } + }); + } else { + // self-hosted settings + Object[] params = { + mBlog.getRemoteBlogId(), mBlog.getUsername(), mBlog.getPassword() + }; + + instantiateInterface().callAsync(mXmlRpcFetchCallback, ApiHelper.Methods.GET_OPTIONS, params); + } + } + + private XMLRPCClientInterface instantiateInterface() { + return XMLRPCFactory.instantiate(mBlog.getUri(), mBlog.getHttpuser(), mBlog.getHttppassword()); + } + + private void handleSettingsFetchError(String error) { + AppLog.w(AppLog.T.API, "Error GETing site settings: " + error); + if (isAdded()) { + ToastUtils.showToast(getActivity(), getString(R.string.error_fetch_remote_site_settings)); + getActivity().finish(); + } + } + + private void handleResponseToSelfHostedSettingsRequest(Map result) { + if (mTitlePreference != null) { + mTitlePreference.setEnabled(true); + String title = getNestedMapValue(result, "blog_title"); + changeEditTextPreferenceValue(mTitlePreference, title); + } + + if (mTaglinePreference != null) { + mTaglinePreference.setEnabled(true); + mRemoteTagline = getNestedMapValue(result, "blog_tagline"); + changeEditTextPreferenceValue(mTaglinePreference, mRemoteTagline); + } + + if (mAddressPreference != null) { + mRemoteAddress = getNestedMapValue(result, "blog_url"); + changeEditTextPreferenceValue(mAddressPreference, mRemoteAddress); + } + } + + private void handleResponseToSelfHostedSettingsSetRequest(Map result) { + AppLog.d(AppLog.T.API, "Site settings saved"); + + if (result.containsKey("blog_title")) { + String titleResult = getNestedMapValue(result, "blog_title"); + if (TextUtils.isEmpty(titleResult)) { + titleResult = mBlog.getUrl(); + } + + mBlog.setBlogName(StringUtils.unescapeHTML(titleResult)); + WordPress.wpDB.saveBlog(mBlog); + EventBus.getDefault().post(new CoreEvents.BlogListChanged()); + } + } + + private void handleSettingsSetError(String error) { + AppLog.w(AppLog.T.API, "Error setting site settings: " + error); + if (isAdded()) { + ToastUtils.showToast(getActivity(), getString(R.string.error_post_remote_site_settings)); + getActivity().finish(); + } + } + + /** + * Helper method to parse JSON response to REST request. + */ + private void handleResponseToWPComSettingsRequest(JSONObject response) { + if (mTitlePreference != null) { + mTitlePreference.setEnabled(true); + String title = response.optString(RestClientUtils.SITE_TITLE_KEY); + changeEditTextPreferenceValue(mTitlePreference, title); + } + + if (mTaglinePreference != null) { + mTaglinePreference.setEnabled(true); + mRemoteTagline = response.optString(RestClientUtils.SITE_DESC_KEY); + changeEditTextPreferenceValue(mTaglinePreference, mRemoteTagline); + } + + mRemoteAddress = response.optString(RestClientUtils.SITE_URL_KEY); + changeEditTextPreferenceValue(mAddressPreference, mRemoteAddress); + + JSONObject settingsObject = response.optJSONObject("settings"); + + if (settingsObject != null) { + mRemoteLanguage = convertLanguageIdToLanguageCode(settingsObject.optString("lang_id")); + if (mLanguagePreference != null) { + String[] languageCodes = getResources().getStringArray(R.array.language_codes); + mLanguagePreference.setEntries(createLanguageDisplayStrings(languageCodes)); + mLanguagePreference.setDetails( + createLanguageDetailDisplayStrings(languageCodes, mRemoteLanguage)); + mLanguagePreference.setEnabled(true); + changeLanguageValue(mRemoteLanguage); + mLanguagePreference.setSummary( + firstLetterCapitalized( + getLanguageString(mRemoteLanguage, new Locale(localeInput(mRemoteLanguage))))); + } + + mRemotePrivacy = settingsObject.optInt("blog_public"); + if (mPrivacyPreference != null) { + mPrivacyPreference.setEnabled(true); + mPrivacyPreference.setValue(String.valueOf(mRemotePrivacy)); + mPrivacyPreference.setSummary(privacyStringForValue(mRemotePrivacy)); + } + } + } + + /** + * Helper method to create the parameters for the site settings POST request + * + * Using undocumented endpoint WPCOM_JSON_API_Site_Settings_Endpoint + * https://wpcom.trac.automattic.com/browser/trunk/public.api/rest/json-endpoints.php#L1903 + */ + private HashMap generateDotComPostParams() { + HashMap params = new HashMap<>(); + + if (mTitlePreference != null && !mTitlePreference.getText().equals(mRemoteTitle)) { + params.put("blogname", mTitlePreference.getText()); + } + + if (mTaglinePreference != null && !mTaglinePreference.getText().equals(mRemoteTagline)) { + params.put("blogdescription", mTaglinePreference.getText()); + } + + if (mAddressPreference != null && !mAddressPreference.getText().equals(mRemoteAddress)) { + } + + if (mLanguagePreference != null && mLanguageCodes != null && mRemoteLanguage != null && + mLanguageCodes.containsKey(mLanguagePreference.getValue()) && + !mRemoteLanguage.equals(mLanguageCodes.get(mLanguagePreference.getValue()))) { + params.put("lang_id", String.valueOf(mLanguageCodes.get(mLanguagePreference.getValue()))); + } + + if (mPrivacyPreference != null && + Integer.valueOf(mPrivacyPreference.getValue()) != mRemotePrivacy) { + params.put("blog_public", mPrivacyPreference.getValue()); + } + + return params; + } + + private HashMap generateSelfHostedParams() { + HashMap params = new HashMap<>(); + + if (mTitlePreference != null && !mTitlePreference.getText().equals(StringUtils.unescapeHTML(mRemoteTitle))) { + params.put("blog_title", mTitlePreference.getText()); + } + + if (mTaglinePreference != null && !mTaglinePreference.getText().equals(StringUtils.unescapeHTML(mRemoteTagline))) { + params.put("blog_tagline", mTaglinePreference.getText()); + } + + return params; + } + + /** + * Persists changed settings remotely + */ + private void applyChanges() { + if (mBlog.isDotcomFlag()) { + postDotComChanges(generateDotComPostParams()); + } else { + postSelfHostedChanges(generateSelfHostedParams()); + } + } + + private void postSelfHostedChanges(final HashMap params) { + if (params == null || params.size() == 0) return; + + Object[] callParams = { + mBlog.getRemoteBlogId(), mBlog.getUsername(), mBlog.getPassword(), params + }; + + instantiateInterface().callAsync(mXmlRpcSetCallback, ApiHelper.Methods.SET_OPTIONS, callParams); + } + + private void postDotComChanges(final HashMap params) { + if (params == null || params.size() == 0) return; + + WordPress.getRestClientUtils().setGeneralSiteSettings( + String.valueOf(mBlog.getRemoteBlogId()), new RestRequest.Listener() { + @Override + public void onResponse(JSONObject response) { + // Update local Blog name + if (params.containsKey("blogname")) { + mBlog.setBlogName(params.get("blogname")); + WordPress.wpDB.saveBlog(mBlog); + EventBus.getDefault().post(new CoreEvents.BlogListChanged()); + } + } + }, new RestRequest.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + AppLog.w(AppLog.T.API, "Error POSTing site settings changes: " + error); + if (isAdded()) { + ToastUtils.showToast(getActivity(), getString(R.string.error_post_remote_site_settings)); + } + } + }, params); + } + + /** + * Helper method to perform validation and set multiple properties on an EditTextPreference. + * If newValue is equal to the current preference text no action will be taken. + */ + private void changeEditTextPreferenceValue(EditTextPreference pref, String newValue) { + if (pref != null && newValue != null && !newValue.equals(pref.getSummary())) { + pref.setText(StringUtils.unescapeHTML(newValue)); + pref.setSummary(StringUtils.unescapeHTML(newValue)); + } + } + + private void changeLanguageValue(String newValue) { + if (mLanguagePreference != null && !newValue.equals(mLanguagePreference.getValue())) { + mLanguagePreference.setValue(newValue); + mLanguagePreference.setSummary(getLanguageString(newValue, new Locale(localeInput(newValue)))); + + // update details to display in selected locale + String[] languageCodes = getResources().getStringArray(R.array.language_codes); + mLanguagePreference.setEntries(createLanguageDisplayStrings(languageCodes)); + mLanguagePreference.setDetails(createLanguageDetailDisplayStrings(languageCodes, newValue)); + mLanguagePreference.refreshAdapter(); + } + } + + private void changePrivacyValue(int newValue) { + if (mPrivacyPreference != null && Integer.valueOf(mPrivacyPreference.getValue()) != newValue) { + mPrivacyPreference.setValue(String.valueOf(newValue)); + mPrivacyPreference.setSummary(privacyStringForValue(newValue)); + mPrivacyPreference.refreshAdapter(); + } + } + + /** + * Returns non-null String representation of WordPress.com privacy value. + */ + private String privacyStringForValue(int value) { + switch (value) { + case -1: + return getString(R.string.privacy_private); + case 0: + return getString(R.string.privacy_hidden); + case 1: + return getString(R.string.privacy_public); + default: + return ""; + } + } + + /** + * Generates display strings for given language codes. Used as entries in language preference. + */ + private String[] createLanguageDisplayStrings(CharSequence[] languageCodes) { + if (languageCodes == null || languageCodes.length < 1) return null; + + String[] displayStrings = new String[languageCodes.length]; + + for (int i = 0; i < languageCodes.length; ++i) { + displayStrings[i] = firstLetterCapitalized(getLanguageString( + String.valueOf(languageCodes[i]), new Locale(localeInput(languageCodes[i].toString())))); + } + + return displayStrings; + } + + /** + * Generates detail display strings in the currently selected locale. Used as detail text + * in language preference dialog. + */ + public String[] createLanguageDetailDisplayStrings(String[] languageCodes, String locale) { + if (languageCodes == null || languageCodes.length < 1) return null; + + String[] detailStrings = new String[languageCodes.length]; + for (int i = 0; i < languageCodes.length; ++i) { + detailStrings[i] = firstLetterCapitalized( + getLanguageString(languageCodes[i], new Locale(localeInput(locale)))); + } + + return detailStrings; + } + + /** + * Return a non-null display string for a given language code. + */ + private String getLanguageString(String languageCode, Locale displayLocale) { + if (languageCode == null || languageCode.length() < 2 || languageCode.length() > 6) { + return ""; + } + + Locale languageLocale = new Locale(localeInput(languageCode)); + return languageLocale.getDisplayLanguage(displayLocale) + languageCode.substring(2); + } + + /** + * Removes a {@link Preference} from the {@link PreferenceCategory} with the given key. + */ + private void removePreference(int categoryKey, Preference preference) { + if (preference == null) return; + + PreferenceCategory category = (PreferenceCategory) findPreference(getString(categoryKey)); + + if (category != null) { + category.removePreference(preference); + } + } + + /** + * Converts a language ID (WordPress defined) to a language code (i.e. en, es, gb, etc...). + */ + private String convertLanguageIdToLanguageCode(String id) { + if (id != null) { + for (String key : mLanguageCodes.keySet()) { + if (id.equals(mLanguageCodes.get(key))) { + return key; + } + } + } + + return ""; + } + + private String localeInput(String languageCode) { + if (TextUtils.isEmpty(languageCode)) return ""; + + return languageCode.substring(0, 2); + } + + /** + * Helper method to get a value from a nested Map. Used to parse self-hosted response object. + */ + private String getNestedMapValue(Map map, String key) { + if (map != null && key != null) { + return MapUtils.getMapStr((Map) map.get(key), "value"); + } + + return ""; + } + + private String firstLetterCapitalized(String input) { + if (TextUtils.isEmpty(input)) return ""; + + return input.substring(0, 1).toUpperCase() + input.substring(1); + } + + /** + * Handles response to XML-RPC settings fetch + */ + private final XMLRPCCallback mXmlRpcFetchCallback = new XMLRPCCallback() { + @Override + public void onSuccess(long id, final Object result) { + if (isAdded() && result instanceof Map) { + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + handleResponseToSelfHostedSettingsRequest((Map) result); + } + }); + } else { + // Response is considered an error if we are unable to parse it + handleSettingsFetchError("Invalid response object (expected Map): " + result); + } + } + + @Override + public void onFailure(long id, final Exception error) { + if (isAdded()) { + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + handleSettingsFetchError(error.toString()); + } + }); + } + } + }; + + private final XMLRPCCallback mXmlRpcSetCallback = new XMLRPCCallback() { + @Override + public void onSuccess(long id, final Object result) { + if (result instanceof Map) { + handleResponseToSelfHostedSettingsSetRequest((Map) result); + } else { + // Response is considered an error if we are unable to parse it + handleSettingsSetError("Invalid response object (expected Map): " + result); + } + } + + @Override + public void onFailure(long id, final Exception error) { + if (isAdded()) { + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + handleSettingsSetError(error.toString()); + } + }); + } + } + }; +} 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 new file mode 100644 index 000000000000..aff4db0115e6 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/SummaryEditTextPreference.java @@ -0,0 +1,132 @@ +package org.wordpress.android.ui.prefs; + +import android.app.Activity; +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Typeface; +import android.preference.EditTextPreference; +import android.support.annotation.NonNull; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.View; +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; + +/** + * Standard EditTextPreference that has attributes to limit summary length. + * + * Created for and used by {@link SiteSettingsFragment} to style some Preferences. + * + * When declaring this class in a layout file you can use the following attributes: + * - app:summaryLines : sets the number of lines to display in the Summary field + * (see {@link TextView#setLines(int)} for details) + * - app:maxSummaryLines : sets the maximum number of lines the Summary field can display + * (see {@link TextView#setMaxLines(int)} for details) + * - app:longClickHint : sets the string to be shown in a Toast when preference is long clicked + */ + +public class SummaryEditTextPreference extends EditTextPreference + implements SiteSettingsFragment.HasHint { + private int mLines; + private int mMaxLines; + private String mHint; + + public SummaryEditTextPreference(Context context) { + super(context); + } + + public SummaryEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public SummaryEditTextPreference(Context context, AttributeSet attrs) { + super(context, attrs); + + mLines = -1; + mMaxLines = -1; + + TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.SummaryEditTextPreference); + + for (int i = 0; i < array.getIndexCount(); ++i) { + int index = array.getIndex(i); + if (index == R.styleable.SummaryEditTextPreference_summaryLines) { + mLines = array.getInt(index, -1); + } else if (index == R.styleable.SummaryEditTextPreference_maxSummaryLines) { + mMaxLines = array.getInt(index, -1); + } else if (index == R.styleable.SummaryEditTextPreference_longClickHint) { + mHint = array.getString(index); + } + } + + array.recycle(); + } + + @Override + 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 (summaryView != null) { + if (isEnabled()) { + summaryView.setTextColor(res.getColor(R.color.grey_darken_10)); + } else { + summaryView.setTextColor(res.getColor(R.color.grey_lighten_10)); + } + summaryView.setTextSize(14); + summaryView.setTypeface(font); + summaryView.setEllipsize(TextUtils.TruncateAt.END); + if (mLines != -1) summaryView.setLines(mLines); + if (mMaxLines != -1) summaryView.setMaxLines(mMaxLines); + } + } + + @Override + protected void onBindDialogView(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()); + } + } + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + super.onDialogClosed(positiveResult); + ActivityUtils.hideKeyboard((Activity) getContext()); + } + + @Override + public boolean hasHint() { + return !TextUtils.isEmpty(mHint); + } + + @Override + public String getHintText() { + return mHint; + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/util/WPRestClient.java b/WordPress/src/main/java/org/wordpress/android/util/WPRestClient.java index d82f161e6c3c..4d6d327d49af 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/WPRestClient.java +++ b/WordPress/src/main/java/org/wordpress/android/util/WPRestClient.java @@ -255,6 +255,13 @@ public void getStatsVideoSummary(String siteId, Listener listener, ErrorListener getXL(path, params, listener, errorListener); } + public void getSiteDescription(String siteId, Listener listener, ErrorListener errorListener) { + String path = String.format("rest/v1.1/sites/%s", siteId); + Map params = new HashMap(); + params.put("fields", "description"); + get(path, params, null, listener, errorListener); + } + /** * This method is for simulating stats APIs using the XL Studio API simulator. It should be removed once the other APIs are implemented. **/ diff --git a/WordPress/src/main/java/org/wordpress/android/widgets/TypefaceCache.java b/WordPress/src/main/java/org/wordpress/android/widgets/TypefaceCache.java index 926683ae720c..d5e6247efb07 100644 --- a/WordPress/src/main/java/org/wordpress/android/widgets/TypefaceCache.java +++ b/WordPress/src/main/java/org/wordpress/android/widgets/TypefaceCache.java @@ -16,20 +16,20 @@ public class TypefaceCache { * Merriweather is also available via the "fontFamily" attribute */ - private static final int VARIATION_NORMAL = 0; - private static final int VARIATION_LIGHT = 1; - private static final int VARIATION_DEFAULT = VARIATION_NORMAL; + public static final int VARIATION_NORMAL = 0; + public static final int VARIATION_LIGHT = 1; + public static final int VARIATION_DEFAULT = VARIATION_NORMAL; - private static final int FAMILY_OPEN_SANS = 0; - private static final int FAMILY_MERRIWEATHER = 1; - private static final int FAMILY_DEFAULT = FAMILY_OPEN_SANS; + public static final int FAMILY_OPEN_SANS = 0; + public static final int FAMILY_MERRIWEATHER = 1; + public static final int FAMILY_DEFAULT = FAMILY_OPEN_SANS; private static final Hashtable mTypefaceCache = new Hashtable<>(); public static Typeface getTypeface(Context context) { return getTypeface(context, FAMILY_DEFAULT, Typeface.NORMAL, VARIATION_DEFAULT); } - private static Typeface getTypeface(Context context, + public static Typeface getTypeface(Context context, int family, int fontStyle, int variation) { diff --git a/WordPress/src/main/java/org/xmlrpc/android/ApiHelper.java b/WordPress/src/main/java/org/xmlrpc/android/ApiHelper.java index 9a18787c52c8..e2edc60aa46b 100644 --- a/WordPress/src/main/java/org/xmlrpc/android/ApiHelper.java +++ b/WordPress/src/main/java/org/xmlrpc/android/ApiHelper.java @@ -67,6 +67,8 @@ public static final class Methods { public static final String EDIT_POST = "wp.editPost"; public static final String EDIT_COMMENT = "wp.editComment"; + public static final String SET_OPTIONS = "wp.setOptions"; + public static final String UPLOAD_FILE = "wp.uploadFile"; public static final String WPCOM_GET_FEATURES = "wpcom.getFeatures"; diff --git a/WordPress/src/main/java/org/xmlrpc/android/XMLRPCClient.java b/WordPress/src/main/java/org/xmlrpc/android/XMLRPCClient.java index 4ffffa089163..317465832682 100644 --- a/WordPress/src/main/java/org/xmlrpc/android/XMLRPCClient.java +++ b/WordPress/src/main/java/org/xmlrpc/android/XMLRPCClient.java @@ -337,7 +337,7 @@ private static void consumeHttpEntity(HttpEntity entity) { public void preparePostMethod(String method, Object[] params, File tempFile) throws IOException, XMLRPCException, IllegalArgumentException, IllegalStateException { // prepare POST body - if (method.equals("wp.uploadFile")) { + if (method.equals(ApiHelper.Methods.UPLOAD_FILE)) { if (!tempFile.exists() && !tempFile.mkdirs()) { throw new XMLRPCException("Path to file could not be created."); } @@ -511,7 +511,7 @@ private Object callXMLRPC(String method, Object[] params, File tempFile) if (!TextUtils.isEmpty(responseString) && responseString.contains("php fatal error") && responseString.contains("bytes exhausted")) { String newErrorMsg; - if (method.equals("wp.uploadFile")) { + if (method.equals(ApiHelper.Methods.UPLOAD_FILE)) { newErrorMsg = "The server doesn't have enough memory to upload this file. You may need to increase the PHP memory limit on your site."; } else { @@ -613,7 +613,7 @@ private boolean checkXMLRPCErrorMessage(Exception exception) { private void deleteTempFile(String method, File tempFile) { if (tempFile != null) { - if ((method.equals("wp.uploadFile"))){ //get rid of the temp file + if ((method.equals(ApiHelper.Methods.UPLOAD_FILE))){ //get rid of the temp file tempFile.delete(); } } diff --git a/WordPress/src/main/res/color/dialog_compound_button.xml b/WordPress/src/main/res/color/dialog_compound_button.xml new file mode 100644 index 000000000000..31e228846b98 --- /dev/null +++ b/WordPress/src/main/res/color/dialog_compound_button.xml @@ -0,0 +1,14 @@ + + + + + + + + + \ No newline at end of file diff --git a/WordPress/src/main/res/drawable-v21/dialog_info_button_background.xml b/WordPress/src/main/res/drawable-v21/dialog_info_button_background.xml new file mode 100644 index 000000000000..3ba16d9a2fec --- /dev/null +++ b/WordPress/src/main/res/drawable-v21/dialog_info_button_background.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/WordPress/src/main/res/drawable/dialog_info_button_background.xml b/WordPress/src/main/res/drawable/dialog_info_button_background.xml new file mode 100644 index 000000000000..e86873f6bc11 --- /dev/null +++ b/WordPress/src/main/res/drawable/dialog_info_button_background.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/WordPress/src/main/res/drawable/preferences_divider.xml b/WordPress/src/main/res/drawable/preferences_divider.xml new file mode 100644 index 000000000000..b912aac177e8 --- /dev/null +++ b/WordPress/src/main/res/drawable/preferences_divider.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/WordPress/src/main/res/layout/detail_list_preference.xml b/WordPress/src/main/res/layout/detail_list_preference.xml new file mode 100644 index 000000000000..88ab20e7c7d6 --- /dev/null +++ b/WordPress/src/main/res/layout/detail_list_preference.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + diff --git a/WordPress/src/main/res/layout/detail_list_preference_title.xml b/WordPress/src/main/res/layout/detail_list_preference_title.xml new file mode 100644 index 000000000000..48963d4a0372 --- /dev/null +++ b/WordPress/src/main/res/layout/detail_list_preference_title.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/WordPress/src/main/res/layout/site_settings_actionbar.xml b/WordPress/src/main/res/layout/site_settings_actionbar.xml new file mode 100644 index 000000000000..7f8d70145a9d --- /dev/null +++ b/WordPress/src/main/res/layout/site_settings_actionbar.xml @@ -0,0 +1,23 @@ + + + + + + + diff --git a/WordPress/src/main/res/values/attrs.xml b/WordPress/src/main/res/values/attrs.xml index 2a9dfa05d2c6..05d1dce014ef 100644 --- a/WordPress/src/main/res/values/attrs.xml +++ b/WordPress/src/main/res/values/attrs.xml @@ -1,5 +1,23 @@ + + + + + + + + + + + + + + + + + + diff --git a/WordPress/src/main/res/values/colors.xml b/WordPress/src/main/res/values/colors.xml index ba051f2339fc..2a6b4f1d2b5f 100644 --- a/WordPress/src/main/res/values/colors.xml +++ b/WordPress/src/main/res/values/colors.xml @@ -148,4 +148,7 @@ #d0d5d9 + + #dce1e6 + diff --git a/WordPress/src/main/res/values/dimens.xml b/WordPress/src/main/res/values/dimens.xml index a2f496698f89..cb030d2fa85a 100644 --- a/WordPress/src/main/res/values/dimens.xml +++ b/WordPress/src/main/res/values/dimens.xml @@ -133,6 +133,22 @@ 3dp + + 20dp + 24dp + 6dp + 6dp + 4dp + 4dp + 4dp + 24dp + 24dp + 24dp + 16dp + 8dp + 36dp + 36dp + 2dp 128dp @@ -186,4 +202,7 @@ 24dp @dimen/menu_item_margin_normal + + 1dp + diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index 39ee4647438c..59abd12ea3a7 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -406,9 +406,45 @@ wp_pref_notification_other_category wp_pref_notification_other_blogs wp_pref_notifications_enabled + wp_pref_notification_types wp_pref_notification_account_emails wp_pref_notification_sights_sounds wp_pref_notification_site_settings + wp_pref_site_general + wp_pref_site_title + wp_pref_site_tagline + wp_pref_site_address + wp_pref_site_language + wp_pref_site_visibility + wp_pref_site_related_posts + wp_pref_site_location + wp_pref_site_default_category + wp_pref_site_default_format + wp_pref_site_discussion + wp_pref_site_start_over + wp_pref_site_delete_site + + + General + Site Title + Tagline + Address + Privacy + @string/language + 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. + Related Posts + Related Posts + Show related content after posts + Fetching title + Fetching tagline + Fetching address + Fetching privacy + Fetching language + Disconnected, editing disabled. Open source licenses @@ -678,6 +714,104 @@ Video + + 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 + + + + + az + en + de + el + es + fr + gd + hi + hu + id + it + ja + ko + nl + pl + ru + sv + th + uz + zh_CN + zh_TW + en-GB + tr + eu + he + pt-BR + ar + ro + mk + sr + sk + cy + da + + + + + 79 + 1 + 15 + 17 + 19 + 24 + 476 + 30 + 31 + 33 + 35 + 36 + 40 + 49 + 58 + 62 + 68 + 71 + 458 + 449 + 452 + 482 + 78 + 429 + 29 + 438 + 3 + 61 + 435 + 67 + 64 + 13 + 14 + + New post New media @@ -738,6 +872,8 @@ This blog is hidden and couldn\'t be loaded. Enable it again in settings and try again. An error occurred while creating the app database. Try reinstalling the app. An error occurred while copying text to clipboard + Couldn\'t retrieve site info + Couldn\'t save site info Add media @@ -1002,5 +1138,12 @@ My Site Me + I would like my site to be private, visible only to users I choose + Discourage search engines from indexing this site + Allow search engines to index this site + + + https://en.support.wordpress.com/privacy-settings + https://en.support.wordpress.com/language-settings diff --git a/WordPress/src/main/res/values/styles_calypso.xml b/WordPress/src/main/res/values/styles_calypso.xml index e13895ec4f4e..311bee6e4315 100644 --- a/WordPress/src/main/res/values/styles_calypso.xml +++ b/WordPress/src/main/res/values/styles_calypso.xml @@ -5,9 +5,36 @@ + + + + + +