blob: a29e624f1091cac6563d97dd183f42a7f5e6e54f [file] [log] [blame]
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<Note> {
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<Void, Void, Boolean> {
@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<Note> 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<Note> noteBucket, Note note) {
// noop
}
@Override
public void onDeleteObject(Bucket<Note> noteBucket, Note note) {
// noop
}
@Override
public void onSaveObject(Bucket<Note> noteBucket, Note note) {
// noop
}
}