package org.wordpress.android.ui.main; import android.animation.ObjectAnimator; import android.app.DialogFragment; import android.app.Fragment; import android.content.Intent; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.support.design.widget.TabLayout; import android.support.v4.view.ViewPager; import android.support.v7.app.AppCompatActivity; import android.text.TextUtils; import android.view.View; import android.widget.TextView; import com.simperium.client.Bucket; import com.simperium.client.BucketObjectMissingException; import org.wordpress.android.GCMMessageService; import org.wordpress.android.GCMRegistrationIntentService; 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.models.CommentStatus; import org.wordpress.android.models.Note; import org.wordpress.android.networking.ConnectionChangeReceiver; import org.wordpress.android.networking.SelfSignedSSLCertsManager; import org.wordpress.android.ui.ActivityId; import org.wordpress.android.ui.ActivityLauncher; import org.wordpress.android.ui.RequestCodes; import org.wordpress.android.ui.accounts.login.MagicLinkSignInActivity; import org.wordpress.android.ui.notifications.NotificationEvents; import org.wordpress.android.ui.notifications.NotificationsListFragment; import org.wordpress.android.ui.notifications.utils.NotificationsUtils; import org.wordpress.android.ui.notifications.utils.SimperiumUtils; import org.wordpress.android.ui.posts.PromoDialog; import org.wordpress.android.ui.prefs.AppPrefs; import org.wordpress.android.ui.prefs.AppSettingsFragment; 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; import org.wordpress.android.util.AppLog; import org.wordpress.android.util.AppLog.T; import org.wordpress.android.util.AuthenticationDialogUtils; import org.wordpress.android.util.CoreEvents; import org.wordpress.android.util.CoreEvents.MainViewPagerScrolled; import org.wordpress.android.util.CoreEvents.UserSignedOutCompletely; import org.wordpress.android.util.CoreEvents.UserSignedOutWordPressCom; import org.wordpress.android.util.NetworkUtils; import org.wordpress.android.util.ProfilingUtils; import org.wordpress.android.util.StringUtils; import org.wordpress.android.util.ToastUtils; import org.wordpress.android.widgets.WPViewPager; import de.greenrobot.event.EventBus; /** * Main activity which hosts sites, reader, me and notifications tabs */ public class WPMainActivity extends AppCompatActivity implements Bucket.Listener { private WPViewPager mViewPager; private WPMainTabLayout mTabLayout; private WPMainTabAdapter mTabAdapter; private TextView mConnectionBar; private int mAppBarElevation; public static final String ARG_OPENED_FROM_PUSH = "opened_from_push"; /* * tab fragments implement this if their contents can be scrolled, called when user * requests to scroll to the top */ public interface OnScrollToTopListener { void onScrollToTop(); } /* * tab fragments implement this and return true if the fragment handles the back button * and doesn't want the activity to handle it as well */ public interface OnActivityBackPressedListener { boolean onActivityBackPressed(); } @Override public void onCreate(Bundle savedInstanceState) { ProfilingUtils.split("WPMainActivity.onCreate"); super.onCreate(savedInstanceState); setContentView(R.layout.main_activity); mViewPager = (WPViewPager) findViewById(R.id.viewpager_main); mViewPager.setOffscreenPageLimit(WPMainTabAdapter.NUM_TABS - 1); mTabAdapter = new WPMainTabAdapter(getFragmentManager()); mViewPager.setAdapter(mTabAdapter); mConnectionBar = (TextView) findViewById(R.id.connection_bar); mConnectionBar.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // slide out the bar on click, then re-check connection after a brief delay AniUtils.animateBottomBar(mConnectionBar, false); new Handler().postDelayed(new Runnable() { @Override public void run() { if (!isFinishing()) { checkConnection(); } } }, 2000); } }); mTabLayout = (WPMainTabLayout) findViewById(R.id.tab_layout); mTabLayout.createTabs(); mTabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { @Override public void onTabSelected(TabLayout.Tab tab) { mViewPager.setCurrentItem(tab.getPosition()); } @Override public void onTabUnselected(TabLayout.Tab tab) { // nop } @Override public void onTabReselected(TabLayout.Tab tab) { //scroll the active fragment's contents to the top when user taps the current tab Fragment fragment = mTabAdapter.getFragment(tab.getPosition()); if (fragment instanceof OnScrollToTopListener) { ((OnScrollToTopListener) fragment).onScrollToTop(); } } }); mAppBarElevation = getResources().getDimensionPixelSize(R.dimen.appbar_elevation); mViewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(mTabLayout)); mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageSelected(int position) { AppPrefs.setMainTabIndex(position); switch (position) { case WPMainTabAdapter.TAB_MY_SITE: setTabLayoutElevation(mAppBarElevation); break; case WPMainTabAdapter.TAB_READER: setTabLayoutElevation(0); break; case WPMainTabAdapter.TAB_ME: setTabLayoutElevation(mAppBarElevation); break; case WPMainTabAdapter.TAB_NOTIFS: setTabLayoutElevation(mAppBarElevation); new UpdateLastSeenTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); break; } trackLastVisibleTab(position, true); } @Override public void onPageScrollStateChanged(int state) { // noop } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { // fire event if the "My Site" page is being scrolled so the fragment can // animate its fab to match if (position == WPMainTabAdapter.TAB_MY_SITE) { EventBus.getDefault().post(new MainViewPagerScrolled(positionOffset)); } } }); if (savedInstanceState == null) { if (AccountHelper.isSignedIn()) { // open note detail if activity called from a push, otherwise return to the tab // that was showing last time boolean openedFromPush = (getIntent() != null && getIntent().getBooleanExtra(ARG_OPENED_FROM_PUSH, false)); if (openedFromPush) { getIntent().putExtra(ARG_OPENED_FROM_PUSH, false); launchWithNoteId(); } else { int position = AppPrefs.getMainTabIndex(); if (mTabAdapter.isValidPosition(position) && position != mViewPager.getCurrentItem()) { mViewPager.setCurrentItem(position); } checkMagicLinkSignIn(); } } else { ActivityLauncher.showSignInForResult(this); } } } private void setTabLayoutElevation(float newElevation){ if (mTabLayout == null) return; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { float oldElevation = mTabLayout.getElevation(); if (oldElevation != newElevation) { ObjectAnimator.ofFloat(mTabLayout, "elevation", oldElevation, newElevation) .setDuration(1000L) .start(); } } } private void showVisualEditorPromoDialogIfNeeded() { if (AppPrefs.isVisualEditorPromoRequired() && AppPrefs.isVisualEditorEnabled()) { DialogFragment newFragment = PromoDialog.newInstance(R.drawable.new_editor_promo_header, R.string.new_editor_promo_title, R.string.new_editor_promo_desc, R.string.new_editor_promo_button_label); newFragment.show(getFragmentManager(), "visual-editor-promo"); AppPrefs.setVisualEditorPromoRequired(false); } } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); setIntent(intent); AppLog.i(T.MAIN, "main activity > new intent"); if (intent.hasExtra(NotificationsListFragment.NOTE_ID_EXTRA)) { launchWithNoteId(); } } /* * called when app is launched from a push notification, switches to the notification tab * and opens the desired note detail */ private void launchWithNoteId() { if (isFinishing() || getIntent() == null) return; // Check for push authorization request if (getIntent().hasExtra(NotificationsUtils.ARG_PUSH_AUTH_TOKEN)) { Bundle extras = getIntent().getExtras(); String token = extras.getString(NotificationsUtils.ARG_PUSH_AUTH_TOKEN, ""); String title = extras.getString(NotificationsUtils.ARG_PUSH_AUTH_TITLE, ""); String message = extras.getString(NotificationsUtils.ARG_PUSH_AUTH_MESSAGE, ""); long expires = extras.getLong(NotificationsUtils.ARG_PUSH_AUTH_EXPIRES, 0); long now = System.currentTimeMillis() / 1000; if (expires > 0 && now > expires) { // Show a toast if the user took too long to open the notification ToastUtils.showToast(this, R.string.push_auth_expired, ToastUtils.Duration.LONG); AnalyticsTracker.track(AnalyticsTracker.Stat.PUSH_AUTHENTICATION_EXPIRED); } else { NotificationsUtils.showPushAuthAlert(this, token, title, message); } } mViewPager.setCurrentItem(WPMainTabAdapter.TAB_NOTIFS); if (GCMMessageService.getNotificationsCount() == 1) { String noteId = getIntent().getStringExtra(NotificationsListFragment.NOTE_ID_EXTRA); if (!TextUtils.isEmpty(noteId)) { GCMMessageService.bumpPushNotificationsTappedAnalytics(noteId); boolean doLikeNote = getIntent().getBooleanExtra(NotificationsListFragment.NOTE_INSTANT_LIKE_EXTRA, false); if (doLikeNote) { NotificationsListFragment.openNoteForLike(this, noteId); } else { boolean doApproveNote = getIntent().getBooleanExtra(NotificationsListFragment.NOTE_INSTANT_APPROVE_EXTRA, false); if (doApproveNote) { NotificationsListFragment.openNoteForApprove(this, noteId); } else { boolean shouldShowKeyboard = getIntent().getBooleanExtra(NotificationsListFragment.NOTE_INSTANT_REPLY_EXTRA, false); NotificationsListFragment.openNoteForReply(this, noteId, shouldShowKeyboard); } } } } else { // mark all tapped here GCMMessageService.bumpPushNotificationsTappedAllAnalytics(); } GCMMessageService.removeAllNotifications(this); } @Override protected void onPause() { if (SimperiumUtils.getNotesBucket() != null) { SimperiumUtils.getNotesBucket().removeListener(this); } super.onPause(); } @Override protected void onStop() { EventBus.getDefault().unregister(this); super.onStop(); } @Override protected void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Override protected void onResume() { super.onResume(); // Start listening to Simperium Note bucket if (SimperiumUtils.getNotesBucket() != null) { SimperiumUtils.getNotesBucket().addListener(this); } mTabLayout.checkNoteBadge(); // We need to track the current item on the screen when this activity is resumed. // Ex: Notifications -> notifications detail -> back to notifications trackLastVisibleTab(mViewPager.getCurrentItem(), false); checkConnection(); ProfilingUtils.split("WPMainActivity.onResume"); ProfilingUtils.dump(); ProfilingUtils.stop(); } @Override public void onBackPressed() { // let the fragment handle the back button if it implements our OnParentBackPressedListener Fragment fragment = getActiveFragment(); if (fragment instanceof OnActivityBackPressedListener) { boolean handled = ((OnActivityBackPressedListener) fragment).onActivityBackPressed(); if (handled) { return; } } super.onBackPressed(); } private Fragment getActiveFragment() { return mTabAdapter.getFragment(mViewPager.getCurrentItem()); } private void checkMagicLinkSignIn() { if (getIntent() != null) { if (getIntent().getBooleanExtra(MagicLinkSignInActivity.MAGIC_LOGIN, false)) { AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_MAGIC_LINK_SUCCEEDED); startWithNewAccount(); } } } private void trackLastVisibleTab(int position, boolean trackAnalytics) { if (position == WPMainTabAdapter.TAB_MY_SITE) { showVisualEditorPromoDialogIfNeeded(); } switch (position) { case WPMainTabAdapter.TAB_MY_SITE: ActivityId.trackLastActivity(ActivityId.MY_SITE); if (trackAnalytics) { AnalyticsUtils.trackWithCurrentBlogDetails(AnalyticsTracker.Stat.MY_SITE_ACCESSED); } break; case WPMainTabAdapter.TAB_READER: ActivityId.trackLastActivity(ActivityId.READER); if (trackAnalytics) { AnalyticsTracker.track(AnalyticsTracker.Stat.READER_ACCESSED); } break; case WPMainTabAdapter.TAB_ME: ActivityId.trackLastActivity(ActivityId.ME); if (trackAnalytics) { AnalyticsTracker.track(AnalyticsTracker.Stat.ME_ACCESSED); } break; case WPMainTabAdapter.TAB_NOTIFS: ActivityId.trackLastActivity(ActivityId.NOTIFICATIONS); if (trackAnalytics) { AnalyticsTracker.track(AnalyticsTracker.Stat.NOTIFICATIONS_ACCESSED); } break; default: break; } } public void setReaderTabActive() { if (isFinishing() || mTabLayout == null) return; mTabLayout.setSelectedTabPosition(WPMainTabAdapter.TAB_READER); } /* * re-create the fragment adapter so all its fragments are also re-created - used when * user signs in/out so the fragments reflect the active account */ private void resetFragments() { AppLog.i(AppLog.T.MAIN, "main activity > reset fragments"); // reset the timestamp that determines when followed tags/blogs are updated so they're // updated when the fragment is recreated (necessary after signin/disconnect) ReaderPostListFragment.resetLastUpdateDate(); // remember the current tab position, then recreate the adapter so new fragments are created int position = mViewPager.getCurrentItem(); mTabAdapter = new WPMainTabAdapter(getFragmentManager()); mViewPager.setAdapter(mTabAdapter); // restore previous position if (mTabAdapter.isValidPosition(position)) { mViewPager.setCurrentItem(position); } } private void moderateCommentOnActivityResult(Intent data) { try { if (SimperiumUtils.getNotesBucket() != null) { Note note = SimperiumUtils.getNotesBucket().get(StringUtils.notNullStr(data.getStringExtra (NotificationsListFragment.NOTE_MODERATE_ID_EXTRA))); CommentStatus status = CommentStatus.fromString(data.getStringExtra( NotificationsListFragment.NOTE_MODERATE_STATUS_EXTRA)); NotificationsUtils.moderateCommentForNote(note, status, findViewById(R.id.root_view_main)); } } catch (BucketObjectMissingException e) { AppLog.e(T.NOTIFS, e); } } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case RequestCodes.EDIT_POST: case RequestCodes.CREATE_BLOG: if (resultCode == RESULT_OK) { MySiteFragment mySiteFragment = getMySiteFragment(); if (mySiteFragment != null) { mySiteFragment.onActivityResult(requestCode, resultCode, data); } } break; case RequestCodes.ADD_ACCOUNT: if (resultCode == RESULT_OK) { // Register for Cloud messaging startWithNewAccount(); } else if (!AccountHelper.isSignedIn()) { // can't do anything if user isn't signed in (either to wp.com or self-hosted) finish(); } break; case RequestCodes.REAUTHENTICATE: if (resultCode == RESULT_CANCELED) { ActivityLauncher.showSignInForResult(this); } else { // Register for Cloud messaging startService(new Intent(this, GCMRegistrationIntentService.class)); } break; case RequestCodes.NOTE_DETAIL: if (resultCode == RESULT_OK && data != null) { moderateCommentOnActivityResult(data); } break; case RequestCodes.SITE_PICKER: if (getMySiteFragment() != null) { getMySiteFragment().onActivityResult(requestCode, resultCode, data); } break; case RequestCodes.BLOG_SETTINGS: if (resultCode == SiteSettingsFragment.RESULT_BLOG_REMOVED) { handleBlogRemoved(); } break; case RequestCodes.APP_SETTINGS: if (resultCode == AppSettingsFragment.LANGUAGE_CHANGED) { resetFragments(); } break; } } private void startWithNewAccount() { startService(new Intent(this, GCMRegistrationIntentService.class)); resetFragments(); } /* * returns the my site fragment from the sites tab */ private MySiteFragment getMySiteFragment() { Fragment fragment = mTabAdapter.getFragment(WPMainTabAdapter.TAB_MY_SITE); if (fragment instanceof MySiteFragment) { return (MySiteFragment) fragment; } return null; } // Updates `last_seen` notifications flag in Simperium and removes tab indicator private class UpdateLastSeenTask extends AsyncTask { @Override protected Boolean doInBackground(Void... voids) { return SimperiumUtils.updateLastSeenTime(); } @Override protected void onPostExecute(Boolean lastSeenTimeUpdated) { if (isFinishing()) return; if (lastSeenTimeUpdated) { mTabLayout.showNoteBadge(false); } } } // Events @SuppressWarnings("unused") public void onEventMainThread(UserSignedOutWordPressCom event) { resetFragments(); } @SuppressWarnings("unused") public void onEventMainThread(UserSignedOutCompletely event) { ActivityLauncher.showSignInForResult(this); } @SuppressWarnings("unused") public void onEventMainThread(CoreEvents.InvalidCredentialsDetected event) { AuthenticationDialogUtils.showAuthErrorView(this); } @SuppressWarnings("unused") public void onEventMainThread(CoreEvents.RestApiUnauthorized event) { AuthenticationDialogUtils.showAuthErrorView(this); } @SuppressWarnings("unused") public void onEventMainThread(CoreEvents.TwoFactorAuthenticationDetected event) { AuthenticationDialogUtils.showAuthErrorView(this); } @SuppressWarnings("unused") public void onEventMainThread(CoreEvents.InvalidSslCertificateDetected event) { SelfSignedSSLCertsManager.askForSslTrust(this, null); } @SuppressWarnings("unused") public void onEventMainThread(CoreEvents.LoginLimitDetected event) { ToastUtils.showToast(this, R.string.limit_reached, ToastUtils.Duration.LONG); } @SuppressWarnings("unused") public void onEventMainThread(NotificationEvents.NotificationsChanged event) { mTabLayout.checkNoteBadge(); } @SuppressWarnings("unused") public void onEventMainThread(ConnectionChangeReceiver.ConnectionChangeEvent event) { updateConnectionBar(event.isConnected()); } private void checkConnection() { updateConnectionBar(NetworkUtils.isNetworkAvailable(this)); } private void updateConnectionBar(boolean isConnected) { if (isConnected && mConnectionBar.getVisibility() == View.VISIBLE) { AniUtils.animateBottomBar(mConnectionBar, false); } else if (!isConnected && mConnectionBar.getVisibility() != View.VISIBLE) { AniUtils.animateBottomBar(mConnectionBar, true); } } private void handleBlogRemoved() { if (!AccountHelper.isSignedIn()) { ActivityLauncher.showSignInForResult(this); } else { Blog blog = WordPress.getCurrentBlog(); MySiteFragment mySiteFragment = getMySiteFragment(); if (mySiteFragment != null) { mySiteFragment.setBlog(blog); } if (blog != null) { int blogId = blog.getLocalTableBlogId(); ActivityLauncher.showSitePickerForResult(this, blogId); } } } /* * Simperium Note bucket listeners */ @Override public void onNetworkChange(Bucket noteBucket, Bucket.ChangeType changeType, String s) { if (changeType == Bucket.ChangeType.INSERT || changeType == Bucket.ChangeType.MODIFY) { runOnUiThread(new Runnable() { @Override public void run() { if (isFinishing()) return; if (isViewingNotificationsTab()) { new UpdateLastSeenTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } else { mTabLayout.checkNoteBadge(); } } }); } } private boolean isViewingNotificationsTab() { return mViewPager.getCurrentItem() == WPMainTabAdapter.TAB_NOTIFS; } @Override public void onBeforeUpdateObject(Bucket noteBucket, Note note) { // noop } @Override public void onDeleteObject(Bucket noteBucket, Note note) { // noop } @Override public void onSaveObject(Bucket noteBucket, Note note) { // noop } }