diff --git a/WordPress/src/main/AndroidManifest.xml b/WordPress/src/main/AndroidManifest.xml
index 4b5f8090eede..7ac23b64f1ee 100644
--- a/WordPress/src/main/AndroidManifest.xml
+++ b/WordPress/src/main/AndroidManifest.xml
@@ -135,7 +135,7 @@
+
{
+
+ private static final String ARG_SITE_TITLE = "ARG_SITE_TITLE";
+ private static final String ARG_SITE_TAGLINE = "ARG_SITE_TAGLINE";
+ private static final String ARG_SITE_SLUG = "ARG_SITE_SLUG";
+ private static final String ARG_SITE_THEME = "ARG_SITE_THEME";
+
+ public enum SiteCreationPhase {
+ IDLE,
+ NEW_SITE,
+ FETCHING_NEW_SITE,
+ SET_TAGLINE,
+ SET_THEME,
+ SUCCESS,
+ FAILURE
+ }
+
+ public static class OnSiteCreationStateUpdated {
+ public final SiteCreationPhase state;
+
+ public OnSiteCreationStateUpdated(SiteCreationPhase state) {
+ this.state = state;
+ }
+ }
+
+ @Inject Dispatcher mDispatcher;
+ @Inject AccountStore mAccountStore;
+ @Inject SiteStore mSiteStore;
+
+ private SiteCreationPhase mSiteCreationPhase = SiteCreationPhase.IDLE;
+
+ private String mSiteTagline;
+ private String mSiteTheme;
+ private long mNewSiteRemoteId;
+
+ public static void createSite(
+ Context context,
+ String siteTitle,
+ String siteTagline,
+ String siteSlug,
+ String siteTheme) {
+ Intent intent = new Intent(context, SiteCreationService.class);
+ intent.putExtra(ARG_SITE_TITLE, siteTitle);
+ intent.putExtra(ARG_SITE_TAGLINE, siteTagline);
+ intent.putExtra(ARG_SITE_SLUG, siteSlug);
+ intent.putExtra(ARG_SITE_THEME, siteTheme);
+ context.startService(intent);
+ }
+
+ public SiteCreationService() {
+ super(OnSiteCreationStateUpdated.class);
+ }
+
+ @Override
+ protected OnSiteCreationStateUpdated getCurrentStateEvent() {
+ return new OnSiteCreationStateUpdated(mSiteCreationPhase);
+ }
+
+ @Override
+ public boolean isInProgress() {
+ return mSiteCreationPhase != SiteCreationPhase.IDLE
+ && mSiteCreationPhase != SiteCreationPhase.SUCCESS
+ && mSiteCreationPhase != SiteCreationPhase.FAILURE;
+ }
+
+ @Override
+ public boolean isError() {
+ return mSiteCreationPhase == SiteCreationPhase.FAILURE;
+ }
+
+ @Override
+ public Notification getNotification() {
+ switch (mSiteCreationPhase) {
+ case NEW_SITE:
+ return getProgressNotification(25, "Site creation in: " + mSiteCreationPhase.name());
+ case FETCHING_NEW_SITE:
+ return getProgressNotification(50, "Site creation in: " + mSiteCreationPhase.name());
+ case SET_TAGLINE:
+ return getProgressNotification(75, "Site creation in: " + mSiteCreationPhase.name());
+ case SET_THEME:
+ return getProgressNotification(100, "Site creation in: " + mSiteCreationPhase.name());
+ case SUCCESS:
+ return getSuccessNotification("Site created!");
+ case FAILURE:
+ return getFailureNotification("Site creation failed :(");
+ }
+
+ return null;
+ }
+
+ private void setState(SiteCreationPhase siteCreationPhase) {
+ mSiteCreationPhase = siteCreationPhase;
+ notifyState();
+
+ if (siteCreationPhase == SiteCreationPhase.FAILURE || siteCreationPhase == SiteCreationPhase.SUCCESS) {
+ stopSelf();
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ ((WordPress) getApplication()).component().inject(this);
+
+ AppLog.i(T.MAIN, "SiteCreationService > Created");
+ mDispatcher.register(this);
+
+ // TODO: Recover any site creations that were interrupted by the service being stopped?
+ }
+
+ @Override
+ public void onDestroy() {
+ mDispatcher.unregister(this);
+ AppLog.i(T.MAIN, "SiteCreationService > Destroyed");
+ super.onDestroy();
+ }
+
+ private Intent getPendingIntent() {
+ return new Intent(this, NewBlogActivity.class);
+ }
+
+ private Notification getProgressNotification(int progress, String content) {
+ return new NotificationCompat.Builder(this)
+ .setContentTitle(content)
+ .setSmallIcon(R.drawable.ic_my_sites_24dp)
+ .setColor(getResources().getColor(R.color.blue_wordpress))
+ .setLargeIcon(BitmapFactory.decodeResource(getApplicationContext().getResources(),
+ R.mipmap.app_icon))
+ .setAutoCancel(true)
+ .setContentIntent(PendingIntent.getActivity(SiteCreationService.this,
+ AutoForeground.NOTIFICATION_ID_PROGRESS,
+ getPendingIntent(),
+ PendingIntent.FLAG_ONE_SHOT))
+ .setProgress(100, progress, false)
+ .build();
+ }
+
+ private Notification getSuccessNotification(String content) {
+ return new NotificationCompat.Builder(this)
+ .setContentTitle(content)
+ .setSmallIcon(R.drawable.ic_my_sites_24dp)
+ .setColor(getResources().getColor(R.color.blue_wordpress))
+ .setLargeIcon(BitmapFactory.decodeResource(getApplicationContext().getResources(),
+ R.mipmap.app_icon))
+ .setAutoCancel(true)
+ .setContentIntent(PendingIntent.getActivity(SiteCreationService.this,
+ AutoForeground.NOTIFICATION_ID_SUCCESS,
+ getPendingIntent(),
+ PendingIntent.FLAG_ONE_SHOT))
+ .build();
+ }
+
+ private Notification getFailureNotification(String content) {
+ return new NotificationCompat.Builder(this)
+ .setContentTitle(content)
+ .setSmallIcon(R.drawable.ic_my_sites_24dp)
+ .setColor(getResources().getColor(R.color.blue_wordpress))
+ .setLargeIcon(BitmapFactory.decodeResource(getApplicationContext().getResources(),
+ R.mipmap.app_icon))
+ .setAutoCancel(true)
+ .setContentIntent(PendingIntent.getActivity(SiteCreationService.this,
+ AutoForeground.NOTIFICATION_ID_FAILURE,
+ getPendingIntent(),
+ PendingIntent.FLAG_ONE_SHOT))
+ .build();
+ }
+
+ @Override
+ public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
+ if (intent == null) {
+ return START_NOT_STICKY;
+ }
+
+ setState(SiteCreationPhase.NEW_SITE);
+
+ final String siteTitle = intent.getStringExtra(ARG_SITE_TITLE);
+ final String siteSlug = intent.getStringExtra(ARG_SITE_SLUG);
+ mSiteTagline = intent.getStringExtra(ARG_SITE_TAGLINE);
+ mSiteTheme = intent.getStringExtra(ARG_SITE_THEME);
+
+ final String language = LanguageUtils.getPatchedCurrentDeviceLanguage(this);
+
+ SiteStore.NewSitePayload newSitePayload =new SiteStore.NewSitePayload(
+ siteSlug,
+ siteTitle,
+ language,
+ SiteStore.SiteVisibility.PUBLIC,
+ false);
+ mDispatcher.dispatch(SiteActionBuilder.newCreateNewSiteAction(newSitePayload));
+ AppLog.i(T.NUX, "User tries to create a new site, title: " + siteTitle + ", SiteName: " + siteSlug);
+
+ return START_REDELIVER_INTENT;
+ }
+
+ private void activateTheme(final SiteModel site, final String themeId) {
+ WordPress.getRestClientUtils().setTheme(site.getSiteId(), themeId, new RestRequest.Listener() {
+ @Override
+ public void onResponse(JSONObject response) {
+ ThemeTable.setCurrentTheme(WordPress.wpDB.getDatabase(), String.valueOf(site.getSiteId()), themeId);
+
+ setState(SiteCreationPhase.SUCCESS);
+ }
+ }, new RestRequest.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError error) {
+ setState(SiteCreationPhase.FAILURE);
+ }
+ });
+ }
+
+ // OnChanged events
+
+ @SuppressWarnings("unused")
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ public void onNewSiteCreated(SiteStore.OnNewSiteCreated event) {
+ AppLog.i(T.NUX, event.toString());
+ if (event.isError()) {
+ setState(SiteCreationPhase.FAILURE);
+ return;
+ }
+
+ AnalyticsTracker.track(AnalyticsTracker.Stat.CREATED_SITE);
+
+ setState(SiteCreationPhase.FETCHING_NEW_SITE);
+
+ mNewSiteRemoteId = event.newSiteRemoteId;
+
+ // We can't get all the site informations from the new site endpoint, so we have to fetch the site list.
+ mDispatcher.dispatch(SiteActionBuilder.newFetchSitesAction());
+ }
+
+ @SuppressWarnings("unused")
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ public void onSiteChanged(SiteStore.OnSiteChanged event) {
+ AppLog.i(T.NUX, event.toString());
+ if (event.isError()) {
+ // Site has been created but there was a error while fetching the sites. Can happen if we get
+ // a response including a broken Jetpack site. We can continue and check if the newly created
+ // site has been fetched.
+ AppLog.e(T.NUX, event.error.type.toString());
+ }
+
+ final SiteModel site = mSiteStore.getSiteBySiteId(mNewSiteRemoteId);
+
+ if (mSiteCreationPhase == SiteCreationPhase.FETCHING_NEW_SITE) {
+ Intent intent = new Intent();
+ if (site == null) {
+ setState(SiteCreationPhase.FAILURE);
+ return;
+ }
+
+ setState(SiteCreationPhase.SET_TAGLINE);
+
+ SiteSettingsInterface siteSettings = SiteSettingsInterface.getInterface(this, site,
+ new SiteSettingsInterface.SiteSettingsListener() {
+ @Override
+ public void onSaveError(Exception error) {
+ setState(SiteCreationPhase.FAILURE);
+ }
+
+ @Override
+ public void onFetchError(Exception error) {
+ setState(SiteCreationPhase.FAILURE);
+ }
+
+ @Override
+ public void onSettingsUpdated() {
+ // we'll just handle onSettingsSaved()
+ }
+
+ @Override
+ public void onSettingsSaved() {
+ setState(SiteCreationPhase.SET_THEME);
+ SiteModel site = mSiteStore.getSiteBySiteId(mNewSiteRemoteId);
+ activateTheme(site, mSiteTheme);
+ }
+
+ @Override
+ public void onCredentialsValidated(Exception error) {
+ if (error != null) {
+ setState(SiteCreationPhase.FAILURE);
+ }
+ }
+ });
+
+ if (siteSettings == null) {
+ setState(SiteCreationPhase.FAILURE);
+ return;
+ }
+
+ siteSettings.init(false);
+ siteSettings.setTagline(mSiteTagline);
+ siteSettings.saveSettings();
+ } else if (mSiteCreationPhase == SiteCreationPhase.SET_TAGLINE) {
+ setState(SiteCreationPhase.SET_THEME);
+ activateTheme(site, mSiteTheme);
+ }
+ }
+}
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 08f2b769de2a..9813ec1ef03b 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
@@ -227,9 +227,11 @@ protected void finalize() throws Throwable {
public void saveSettings() {
SiteSettingsTable.saveSettings(mSettings);
- siteSettingsPreferences(mContext).edit().putString(LANGUAGE_PREF_KEY, mSettings.language).apply();
- siteSettingsPreferences(mContext).edit().putInt(DEF_CATEGORY_PREF_KEY, mSettings.defaultCategory).apply();
- siteSettingsPreferences(mContext).edit().putString(DEF_FORMAT_PREF_KEY, mSettings.defaultPostFormat).apply();
+ siteSettingsPreferences(mContext).edit()
+ .putString(LANGUAGE_PREF_KEY, mSettings.language)
+ .putInt(DEF_CATEGORY_PREF_KEY, mSettings.defaultCategory)
+ .putString(DEF_FORMAT_PREF_KEY, mSettings.defaultPostFormat)
+ .apply();
}
public @NonNull String getTitle() {
@@ -940,9 +942,7 @@ public void run() {
}
protected void notifyFetchErrorOnUiThread(final Exception error) {
- if (mContext == null
- || (mContext instanceof Activity && ((Activity) mContext).isFinishing())
- || mListener == null) {
+ if (mListener == null) {
return;
}
@@ -955,9 +955,7 @@ public void run() {
}
protected void notifySaveErrorOnUiThread(final Exception error) {
- if (mContext == null
- || (mContext instanceof Activity && ((Activity) mContext).isFinishing())
- || mListener == null) {
+ if (mListener == null) {
return;
}
@@ -973,9 +971,7 @@ public void run() {
* Notifies listener that settings have been updated with the latest remote data.
*/
protected void notifyUpdatedOnUiThread() {
- if (mContext == null
- || (mContext instanceof Activity && ((Activity) mContext).isFinishing())
- || mListener == null) {
+ if (mListener == null) {
return;
}
@@ -991,7 +987,9 @@ public void run() {
* Notifies listener that settings have been saved or an error occurred while saving.
*/
protected void notifySavedOnUiThread() {
- if (mContext == null || mListener == null) return;
+ if (mListener == null) {
+ return;
+ }
new Handler().post(new Runnable() {
@Override
diff --git a/WordPress/src/main/res/layout/new_blog_activity.xml b/WordPress/src/main/res/layout/new_blog_activity.xml
index 8bdf62edb83e..58e55488f85d 100644
--- a/WordPress/src/main/res/layout/new_blog_activity.xml
+++ b/WordPress/src/main/res/layout/new_blog_activity.xml
@@ -2,13 +2,12 @@
android:id="@+id/main_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@color/nux_background"
- android:orientation="vertical">
+ android:orientation="vertical"
+ android:baselineAligned="true">
-
+ android:layout_height="match_parent"/>
-
\ No newline at end of file
+
diff --git a/WordPress/src/main/res/layout/site_creating_screen.xml b/WordPress/src/main/res/layout/site_creating_screen.xml
new file mode 100644
index 000000000000..84185c5161af
--- /dev/null
+++ b/WordPress/src/main/res/layout/site_creating_screen.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml
index 2b1b30aea8cd..eeb6390d954f 100644
--- a/WordPress/src/main/res/values/strings.xml
+++ b/WordPress/src/main/res/values/strings.xml
@@ -18,6 +18,13 @@
Logging out from your account will remove all of @%s’s WordPress.com data from this device, including local drafts and local changes.
This account has two step authentication enabled. Visit your security settings on WordPress.com and generate an application-specific password.
+ Preparing your site…
+ Fetching site info…
+ Setting the tagline…
+ Setting the theme…
+ Sorry, failed to properly create your site :(
+ Success!
+
Select categories
Separate tags with commas
diff --git a/libs/utils/WordPressUtils/build.gradle b/libs/utils/WordPressUtils/build.gradle
index 2906a1f36df8..ca6ac552c1ea 100644
--- a/libs/utils/WordPressUtils/build.gradle
+++ b/libs/utils/WordPressUtils/build.gradle
@@ -21,6 +21,7 @@ dependencies {
compile 'com.android.support:support-v13:25.3.1'
compile 'com.android.support:design:25.3.1'
compile 'com.android.support:recyclerview-v7:25.3.1'
+ compile 'org.greenrobot:eventbus:3.0.0'
}
android {
diff --git a/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/AutoForeground.java b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/AutoForeground.java
new file mode 100644
index 000000000000..da3172c84db2
--- /dev/null
+++ b/libs/utils/WordPressUtils/src/main/java/org/wordpress/android/util/AutoForeground.java
@@ -0,0 +1,138 @@
+package org.wordpress.android.util;
+
+import android.app.Notification;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Binder;
+import android.os.IBinder;
+import android.support.annotation.CallSuper;
+import android.support.annotation.Nullable;
+import android.support.v4.app.NotificationManagerCompat;
+
+import org.greenrobot.eventbus.EventBus;
+
+public abstract class AutoForeground extends Service {
+
+ public static final int NOTIFICATION_ID_PROGRESS = 1;
+ public static final int NOTIFICATION_ID_SUCCESS = 2;
+ public static final int NOTIFICATION_ID_FAILURE = 3;
+
+ public static class ServiceEventConnection {
+ private final ServiceConnection mServiceConnection;
+
+ public ServiceEventConnection(Context context, Class extends AutoForeground> clazz, Object client) {
+ EventBus.getDefault().register(client);
+
+ mServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
+ // nothing here
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName componentName) {
+ // nothing here
+ }
+ };
+
+ context.bindService(new Intent(context, clazz), mServiceConnection, Context.BIND_AUTO_CREATE);
+ }
+
+ public void disconnect(Context context, Object client) {
+ context.unbindService(mServiceConnection);
+ EventBus.getDefault().unregister(client);
+ }
+ }
+
+ private class LocalBinder extends Binder {}
+
+ private final IBinder mBinder = new LocalBinder();
+
+ private final Class mEventClass;
+
+ protected abstract EventClass getCurrentStateEvent();
+ protected abstract Notification getNotification();
+ protected abstract boolean isInProgress();
+ protected abstract boolean isError();
+
+ protected AutoForeground(Class eventClass) {
+ mEventClass = eventClass;
+ }
+
+ @Nullable
+ @CallSuper
+ @Override
+ public IBinder onBind(Intent intent) {
+ notifyState();
+
+ return mBinder;
+ }
+
+ @CallSuper
+ @Override
+ public void onRebind(Intent intent) {
+ super.onRebind(intent);
+
+ background();
+ notifyState();
+ }
+
+ @CallSuper
+ @Override
+ public boolean onUnbind(Intent intent) {
+ if (!hasConnectedClients()) {
+ promoteForeground();
+ }
+
+ return true; // call onRebind() if new clients connect
+ }
+
+ private EventBus getEventBus() {
+ return EventBus.getDefault();
+ }
+
+ private boolean hasConnectedClients() {
+ return getEventBus().hasSubscriberForEvent(mEventClass);
+ }
+
+ private void promoteForeground() {
+ if (isInProgress()) {
+ startForeground(NOTIFICATION_ID_PROGRESS, getNotification());
+ }
+ }
+
+ private void background() {
+ stopForeground(true);
+ }
+
+ @CallSuper
+ protected void notifyState() {
+ if (hasConnectedClients()) {
+ // just send a message to the connected clients
+ getEventBus().post(getCurrentStateEvent());
+ return;
+ }
+
+ // ok, no connected clients so, update will be redirected to a notification
+
+ if (isInProgress()) {
+ // operation still is progress so, update the notification
+ NotificationManagerCompat.from(this).notify(NOTIFICATION_ID_PROGRESS, getNotification());
+ return;
+ }
+
+ // operation has ended so, demote the Service to a background one
+ background();
+
+ // dismiss the sticky notification
+ NotificationManagerCompat.from(this).cancel(NOTIFICATION_ID_PROGRESS);
+
+ // put out a simple success/failure notification
+ NotificationManagerCompat.from(this).notify(
+ isError() ? NOTIFICATION_ID_FAILURE : NOTIFICATION_ID_SUCCESS,
+ getNotification());
+ }
+}