/* * Twidere - Twitter client for Android * * Copyright (C) 2012-2014 Mariotaku Lee * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.mariotaku.twidere.activity import android.accounts.Account import android.accounts.AccountManager import android.accounts.OnAccountsUpdateListener import android.annotation.SuppressLint import android.app.Dialog import android.app.PendingIntent import android.app.SearchManager import android.content.Context import android.content.DialogInterface import android.content.Intent import android.content.SharedPreferences import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.content.res.Configuration import android.graphics.Rect import android.graphics.drawable.Drawable import android.os.AsyncTask import android.os.Build import android.os.Bundle import android.util.SparseIntArray import android.view.Gravity import android.view.KeyEvent import android.view.MenuItem import android.view.View import android.view.View.OnClickListener import android.view.View.OnLongClickListener import android.view.ViewGroup.MarginLayoutParams import android.widget.RelativeLayout import androidx.annotation.StringRes import androidx.appcompat.app.ActionBarDrawerToggle import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.widget.TintTypedArray import androidx.core.app.NotificationCompat import androidx.core.view.* import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayoutAccessor import androidx.fragment.app.Fragment import androidx.viewpager.widget.ViewPager.OnPageChangeListener import com.getkeepsafe.taptargetview.TapTarget import com.getkeepsafe.taptargetview.TapTargetView import com.squareup.otto.Subscribe import kotlinx.android.synthetic.main.activity_home.* import kotlinx.android.synthetic.main.activity_home_content.* import kotlinx.android.synthetic.main.layout_empty_tab_hint.* import nl.komponents.kovenant.task import org.mariotaku.chameleon.ChameleonUtils import org.mariotaku.kpreferences.contains import org.mariotaku.kpreferences.get import org.mariotaku.kpreferences.set import org.mariotaku.ktextension.addOnAccountsUpdatedListenerSafe import org.mariotaku.ktextension.coerceInOr import org.mariotaku.ktextension.contains import org.mariotaku.ktextension.removeOnAccountsUpdatedListenerSafe import org.mariotaku.sqliteqb.library.Expression import org.mariotaku.twidere.Constants.* import org.mariotaku.twidere.R import org.mariotaku.twidere.activity.iface.IControlBarActivity.ControlBarShowHideHelper import org.mariotaku.twidere.adapter.SupportTabsAdapter import org.mariotaku.twidere.annotation.CustomTabType import org.mariotaku.twidere.annotation.FilterScope import org.mariotaku.twidere.annotation.NavbarStyle import org.mariotaku.twidere.annotation.ReadPositionTag import org.mariotaku.twidere.constant.* import org.mariotaku.twidere.extension.applyTheme import org.mariotaku.twidere.extension.model.notificationBuilder import org.mariotaku.twidere.extension.onShow import org.mariotaku.twidere.fragment.AccountsDashboardFragment import org.mariotaku.twidere.fragment.BaseDialogFragment import org.mariotaku.twidere.fragment.iface.IFloatingActionButtonFragment import org.mariotaku.twidere.fragment.iface.RefreshScrollTopInterface import org.mariotaku.twidere.fragment.iface.SupportFragmentCallback import org.mariotaku.twidere.graphic.EmptyDrawable import org.mariotaku.twidere.model.AccountDetails import org.mariotaku.twidere.model.SupportTabSpec import org.mariotaku.twidere.model.Tab import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.event.UnreadCountUpdatedEvent import org.mariotaku.twidere.model.notification.NotificationChannelSpec import org.mariotaku.twidere.provider.TwidereDataStore.Activities import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations import org.mariotaku.twidere.provider.TwidereDataStore.Statuses import org.mariotaku.twidere.receiver.NotificationReceiver import org.mariotaku.twidere.util.* import org.mariotaku.twidere.util.KeyboardShortcutsHandler.KeyboardShortcutCallback import org.mariotaku.twidere.util.premium.ExtraFeaturesService import org.mariotaku.twidere.view.HomeDrawerLayout import org.mariotaku.twidere.view.TabPagerIndicator import java.lang.ref.WeakReference import kotlin.math.floor class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, SupportFragmentCallback, OnLongClickListener, DrawerLayout.DrawerListener { private val accountUpdatedListener = AccountUpdatedListener(this) private var selectedAccountToSearch: AccountDetails? = null private lateinit var multiSelectHandler: MultiSelectEventHandler private lateinit var pagerAdapter: SupportTabsAdapter private lateinit var drawerToggle: ActionBarDrawerToggle private var propertiesInitialized = false private var actionsButtonBottomMargin: Int = 0 private var updateUnreadCountTask: UpdateUnreadCountTask? = null private val readStateChangeListener = OnSharedPreferenceChangeListener { _, _ -> updateUnreadCount() } private val controlBarShowHideHelper = ControlBarShowHideHelper(this) override val controlBarHeight: Int get() { return mainTabs.height - mainTabs.stripHeight } override val currentVisibleFragment: Fragment? get() { val currentItem = mainPager.currentItem if (currentItem < 0 || currentItem >= pagerAdapter.count) return null return pagerAdapter.instantiateItem(mainPager, currentItem) } private val homeDrawerToggleDelegate = object : ActionBarDrawerToggle.Delegate { override fun setActionBarUpIndicator(upDrawable: Drawable, @StringRes contentDescRes: Int) { drawerToggleButton.setImageDrawable(upDrawable) drawerToggleButton.setColorFilter(ChameleonUtils.getColorDependent(overrideTheme.colorToolbar)) drawerToggleButton.contentDescription = getString(contentDescRes) } override fun setActionBarDescription(@StringRes contentDescRes: Int) { drawerToggleButton.contentDescription = getString(contentDescRes) } @SuppressLint("RestrictedApi") override fun getThemeUpIndicator(): Drawable { val a = TintTypedArray.obtainStyledAttributes(actionBarThemedContext, null, HOME_AS_UP_ATTRS) val result = a.getDrawable(0) a.recycle() return result } override fun getActionBarThemedContext(): Context { return toolbar.context } override fun isNavigationVisible(): Boolean { return true } } private val keyboardShortcutRecipient: Fragment? get() = when { homeMenu.isDrawerOpen(GravityCompat.START) -> leftDrawerFragment homeMenu.isDrawerOpen(GravityCompat.END) -> null else -> currentVisibleFragment } private val activatedAccountKeys: Array get() = DataStoreUtils.getActivatedAccountKeys(this) private val leftDrawerFragment: Fragment? get() = supportFragmentManager.findFragmentById(R.id.leftDrawer) private val isDrawerOpen: Boolean get() { val drawer = homeMenu ?: return false return drawer.isDrawerOpen(GravityCompat.START) || drawer.isDrawerOpen(GravityCompat.END) } /** * Called when the context is first created. */ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) multiSelectHandler = MultiSelectEventHandler(this) multiSelectHandler.dispatchOnCreate() DataStoreUtils.prepareDatabase(this) if (!DataStoreUtils.hasAccount(this)) { val signInIntent = Intent(INTENT_ACTION_TWITTER_LOGIN) signInIntent.setClass(this, SignInActivity::class.java) startActivity(signInIntent) finish() if (defaultAutoRefreshAskedKey !in kPreferences) { // Assume first install kPreferences[defaultAutoRefreshAskedKey] = false } return } else { notifyAccountsChanged() } supportRequestWindowFeature(AppCompatDelegate.FEATURE_ACTION_MODE_OVERLAY) setContentView(R.layout.activity_home) setSupportActionBar(toolbar) drawerToggle = ActionBarDrawerToggle(this, homeMenu, R.string.open_accounts_dashboard, R.string.close_accounts_dashboard) pagerAdapter = SupportTabsAdapter(this, supportFragmentManager, mainTabs) propertiesInitialized = true ThemeUtils.setCompatContentViewOverlay(window, EmptyDrawable()) val refreshOnStart = preferences[refreshOnStartKey] var tabDisplayOptionInt = Utils.getTabDisplayOptionInt(this) ViewCompat.setOnApplyWindowInsetsListener(homeContent, this) homeMenu.fitsSystemWindows = Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP || preferences[navbarStyleKey] != NavbarStyle.TRANSPARENT if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP || !ViewCompat.getFitsSystemWindows(homeMenu)) { ViewCompat.setOnApplyWindowInsetsListener(homeMenu, null) } mainPager.adapter = pagerAdapter mainTabs.setViewPager(mainPager) mainTabs.setOnPageChangeListener(this) if (tabDisplayOptionInt == 0) { tabDisplayOptionInt = TabPagerIndicator.DisplayOption.ICON } mainTabs.setTabDisplayOption(tabDisplayOptionInt) mainTabs.setTabExpandEnabled(TabPagerIndicator.DisplayOption.LABEL !in tabDisplayOptionInt) mainTabs.setDisplayBadge(preferences[unreadCountKey]) mainTabs.updateAppearance() if (preferences[drawerToggleKey]) { drawerToggleButton.visibility = View.VISIBLE } else { drawerToggleButton.visibility = View.GONE } if (preferences[fabVisibleKey]) { actionsButton.visibility = View.VISIBLE } else { actionsButton.visibility = View.GONE } actionsButtonBottomMargin = (actionsButton.layoutParams as MarginLayoutParams).bottomMargin homeContent.addOnLayoutChangeListener { _, _, top, _, _, _, oldTop, _, _ -> if (top != oldTop) { val fragment = leftDrawerFragment if (fragment is AccountsDashboardFragment) { fragment.setStatusBarHeight(top) } } } actionsButton.setOnClickListener(this) actionsButton.setOnLongClickListener(this) drawerToggleButton.setOnClickListener(this) emptyTabHint.setOnClickListener(this) setupSlidingMenu() setupBars() showPromotionOffer() initUnreadCount() setupHomeTabs() updateActionsButton() if (savedInstanceState == null) { if (refreshOnStart) { twitterWrapper.refreshAll(activatedAccountKeys) } if (intent.getBooleanExtra(EXTRA_OPEN_ACCOUNTS_DRAWER, false)) { openAccountsDrawer() } } val initialTabPosition = handleIntent(intent, savedInstanceState == null) setTabPosition(initialTabPosition) if (!showDrawerTutorial() && !kPreferences[defaultAutoRefreshAskedKey]) { showAutoRefreshConfirm() } } override fun onPostCreate(savedInstanceState: Bundle?) { super.onPostCreate(savedInstanceState) // Sync the toggle state after onRestoreInstanceState has occurred. drawerToggle.syncState() } override fun onStart() { super.onStart() multiSelectHandler.dispatchOnStart() AccountManager.get(this).addOnAccountsUpdatedListenerSafe(accountUpdatedListener, updateImmediately = false) bus.register(this) readStateManager.registerOnSharedPreferenceChangeListener(readStateChangeListener) updateUnreadCount() } override fun onResume() { super.onResume() invalidateOptionsMenu() updateActionsButton() } override fun onStop() { multiSelectHandler.dispatchOnStop() readStateManager.unregisterOnSharedPreferenceChangeListener(readStateChangeListener) bus.unregister(this) AccountManager.get(this).removeOnAccountsUpdatedListenerSafe(accountUpdatedListener) preferences.edit().putInt(KEY_SAVED_TAB_POSITION, mainPager.currentItem).apply() timelineSyncManager?.commit() super.onStop() } override fun onDestroy() { if (isFinishing) { // Delete unused items in databases. val context = applicationContext task { DataStoreUtils.cleanDatabasesByItemLimit(context) } } super.onDestroy() } override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) // Pass any configuration change to the drawer toggle drawerToggle.onConfigurationChanged(newConfig) } override fun onAttachFragment(fragment: Fragment) { super.onAttachFragment(fragment) updateActionsButton() } override fun onClick(v: View) { when (v) { actionsButton -> { triggerActionsClick() } emptyTabHint -> { startActivityForResult(IntentUtils.settings("tabs"), REQUEST_SETTINGS) } drawerToggleButton -> { if (homeMenu.isDrawerOpen(GravityCompat.START) || homeMenu.isDrawerOpen(GravityCompat.END)) { homeMenu.closeDrawers() } else { homeMenu.openDrawer(GravityCompat.START) } } } } override fun onLongClick(v: View): Boolean { when (v) { actionsButton -> { Utils.showMenuItemToast(v, v.contentDescription, true) return true } } return false } override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { } override fun onPageSelected(position: Int) { //TODO handle secondary drawer if (homeMenu.isDrawerOpen(GravityCompat.START)) { homeMenu.closeDrawers() } updateActionsButton() } override fun onPageScrollStateChanged(state: Int) { setControlBarVisibleAnimate(true) } override fun onSearchRequested(): Boolean { startActivity(Intent(this, QuickSearchBarActivity::class.java)) return true } override fun getSystemWindowInsets(caller: Fragment, insets: Rect): Boolean { if (caller === leftDrawerFragment) return super.getSystemWindowInsets(caller, insets) if (mainTabs == null || homeContent == null || toolbar == null || !toolbar.isVisible) return false val height = mainTabs.height if (preferences[tabPositionKey] == SharedPreferenceConstants.VALUE_TAB_POSITION_TOP) { if (height != 0) { insets.top = height } else { insets.top = ThemeUtils.getActionBarHeight(this) } insets.bottom = systemWindowsInsets?.bottom ?: 0 } else { if (height != 0) { insets.bottom = height } else { insets.bottom = ThemeUtils.getActionBarHeight(this) } } return true } @SuppressLint("RestrictedApi") override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat { super.onApplyWindowInsets(v, insets) val fragment = leftDrawerFragment if (fragment is AccountsDashboardFragment) { fragment.requestApplyInsets() } homeMenu.setChildInsets(insets.unwrapped, insets.systemWindowInsetTop > 0) if (!ViewCompat.getFitsSystemWindows(homeMenu)) { homeContent.setPadding(0, insets.systemWindowInsetTop, 0, 0) } (toolbar.layoutParams as? MarginLayoutParams)?.bottomMargin = insets.systemWindowInsetBottom (actionsButton.layoutParams as? MarginLayoutParams)?.bottomMargin = actionsButtonBottomMargin + if (preferences[tabPositionKey] == SharedPreferenceConstants.VALUE_TAB_POSITION_TOP) { insets.systemWindowInsetBottom } else { 0 } return insets } override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) val tabPosition = handleIntent(intent, false) if (tabPosition >= 0) { mainPager.currentItem = tabPosition.coerceInOr(0 until pagerAdapter.count, 0) } } override fun setControlBarVisibleAnimate(visible: Boolean, listener: ControlBarShowHideHelper.ControlBarAnimationListener?) { controlBarShowHideHelper.setControlBarVisibleAnimate(visible, listener) } override fun onOptionsItemSelected(item: MenuItem): Boolean { if (drawerToggle.onOptionsItemSelected(item)) { return true } when (item.itemId) { android.R.id.home -> { val fm = supportFragmentManager val count = fm.backStackEntryCount if (homeMenu.isDrawerOpen(GravityCompat.START) || homeMenu.isDrawerOpen(GravityCompat.END)) { homeMenu.closeDrawers() return true } else if (count == 0) { homeMenu.openDrawer(GravityCompat.START) return true } return true } R.id.search -> { openSearchView(selectedAccountToSearch) return true } R.id.actions -> { triggerActionsClick() return true } } return super.onOptionsItemSelected(item) } override fun handleKeyboardShortcutSingle(handler: KeyboardShortcutsHandler, keyCode: Int, event: KeyEvent, metaState: Int): Boolean { if (handleFragmentKeyboardShortcutSingle(handler, keyCode, event, metaState)) return true var action = handler.getKeyAction(KeyboardShortcutConstants.CONTEXT_TAG_HOME, keyCode, event, metaState) if (action != null) { when (action) { KeyboardShortcutConstants.ACTION_HOME_ACCOUNTS_DASHBOARD -> { if (homeMenu.isDrawerOpen(GravityCompat.START)) { homeMenu.closeDrawers() } else { homeMenu.openDrawer(GravityCompat.START) setControlBarVisibleAnimate(true) } return true } } } action = handler.getKeyAction(KeyboardShortcutConstants.CONTEXT_TAG_NAVIGATION, keyCode, event, metaState) if (action != null) { when (action) { KeyboardShortcutConstants.ACTION_NAVIGATION_PREVIOUS_TAB -> { val previous = mainPager.currentItem - 1 if (previous < 0 && DrawerLayoutAccessor.findDrawerWithGravity(homeMenu, Gravity.START) != null) { homeMenu.openDrawer(GravityCompat.START) setControlBarVisibleAnimate(true) } else if (previous < pagerAdapter.count) { if (homeMenu.isDrawerOpen(GravityCompat.END)) { homeMenu.closeDrawers() } else { mainPager.setCurrentItem(previous, true) } } return true } KeyboardShortcutConstants.ACTION_NAVIGATION_NEXT_TAB -> { val next = mainPager.currentItem + 1 if (next >= pagerAdapter.count && DrawerLayoutAccessor.findDrawerWithGravity(homeMenu, Gravity.END) != null) { homeMenu.openDrawer(GravityCompat.END) setControlBarVisibleAnimate(true) } else if (next >= 0) { if (homeMenu.isDrawerOpen(GravityCompat.START)) { homeMenu.closeDrawers() } else { mainPager.setCurrentItem(next, true) } } return true } } } return handler.handleKey(this, null, keyCode, event, metaState) } override fun isKeyboardShortcutHandled(handler: KeyboardShortcutsHandler, keyCode: Int, event: KeyEvent, metaState: Int): Boolean { if (isFragmentKeyboardShortcutHandled(handler, keyCode, event, metaState)) return true return super.isKeyboardShortcutHandled(handler, keyCode, event, metaState) } override fun handleKeyboardShortcutRepeat(handler: KeyboardShortcutsHandler, keyCode: Int, repeatCount: Int, event: KeyEvent, metaState: Int): Boolean { if (handleFragmentKeyboardShortcutRepeat(handler, keyCode, repeatCount, event, metaState)) return true return super.handleKeyboardShortcutRepeat(handler, keyCode, repeatCount, event, metaState) } override fun dispatchKeyEvent(event: KeyEvent): Boolean { when (event.keyCode) { KeyEvent.KEYCODE_MENU -> { if (event.action != KeyEvent.ACTION_UP) return true if (isDrawerOpen) { homeMenu.closeDrawers() } else { homeMenu.openDrawer(GravityCompat.START) } return true } } return super.dispatchKeyEvent(event) } override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean { when (keyCode) { KeyEvent.KEYCODE_BACK -> { if (isDrawerOpen) { homeMenu.closeDrawers() return true } } } return super.onKeyUp(keyCode, event) } override fun triggerRefresh(position: Int): Boolean { val f = pagerAdapter.instantiateItem(mainPager, position) if (f.activity == null || f.isDetached) return false if (f !is RefreshScrollTopInterface) return false return f.triggerRefresh() } fun notifyAccountsChanged() { } @Subscribe fun notifyUnreadCountUpdated(event: UnreadCountUpdatedEvent) { updateUnreadCount() } fun updateUnreadCount() { if (mainTabs == null || updateUnreadCountTask != null && updateUnreadCountTask!!.status == AsyncTask.Status.RUNNING) return updateUnreadCountTask = UpdateUnreadCountTask(this, preferences, readStateManager, mainTabs, pagerAdapter.tabs.toTypedArray()).apply { execute() } mainTabs.setDisplayBadge(preferences.getBoolean(SharedPreferenceConstants.KEY_UNREAD_COUNT, true)) } val tabs: List get() = pagerAdapter.tabs override var controlBarOffset: Float get() { if (mainTabs.columns > 1 || !toolbar.isVisible) { val lp = actionsButton.layoutParams val total: Float total = if (lp is MarginLayoutParams) { (lp.bottomMargin + actionsButton.height).toFloat() } else { actionsButton.height.toFloat() } return 1 - actionsButton.translationY / total } val totalHeight = controlBarHeight.toFloat() return 1 + if (preferences[tabPositionKey] == SharedPreferenceConstants.VALUE_TAB_POSITION_TOP) { toolbar.translationY } else { -toolbar.translationY } / totalHeight } set(offset) { if (preferences[tabPositionKey] == SharedPreferenceConstants.VALUE_TAB_POSITION_TOP) { val translationY = if (mainTabs.columns > 1 || !toolbar.isVisible) { 0 } else { (controlBarHeight * (offset - 1)).toInt() } toolbar.translationY = translationY.toFloat() windowOverlay.translationY = translationY.toFloat() val lp = actionsButton.layoutParams if (lp is MarginLayoutParams) { actionsButton.translationY = (lp.bottomMargin + actionsButton.height) * (1 - offset) } else { actionsButton.translationY = actionsButton.height * (1 - offset) } notifyControlBarOffsetChanged() } else { val layoutparams = toolbar.layoutParams val toolbarMarginBottom = if (layoutparams is MarginLayoutParams) { layoutparams.bottomMargin } else { 0 } val translationY = if (mainTabs.columns > 1 || !toolbar.isVisible) { 0 } else { ((toolbar.height + toolbarMarginBottom) * (offset - 1)).toInt() } toolbar.translationY = -translationY.toFloat() windowOverlay.translationY = -translationY.toFloat() val lp = actionsButton.layoutParams if (lp is MarginLayoutParams) { actionsButton.translationY = (lp.bottomMargin + toolbar.height + actionsButton.height + toolbarMarginBottom) * (1 - offset) } else { actionsButton.translationY = actionsButton.height * (1 - offset) } notifyControlBarOffsetChanged() } } override fun onDrawerSlide(drawerView: View, slideOffset: Float) { } override fun onDrawerOpened(drawerView: View) { } override fun onDrawerClosed(drawerView: View) { } override fun onDrawerStateChanged(newState: Int) { val fragment = leftDrawerFragment if (newState == DrawerLayout.STATE_DRAGGING && fragment is AccountsDashboardFragment) { fragment.loadAccounts() } } override fun getDrawerToggleDelegate(): ActionBarDrawerToggle.Delegate? { return homeDrawerToggleDelegate } fun closeAccountsDrawer() { if (homeMenu == null) return homeMenu.closeDrawers() } private fun openSearchView(account: AccountDetails?) { selectedAccountToSearch = account onSearchRequested() } private fun handleFragmentKeyboardShortcutRepeat(handler: KeyboardShortcutsHandler, keyCode: Int, repeatCount: Int, event: KeyEvent, metaState: Int): Boolean { val fragment = keyboardShortcutRecipient if (fragment is KeyboardShortcutCallback) { return fragment.handleKeyboardShortcutRepeat(handler, keyCode, repeatCount, event, metaState) } return false } private fun handleFragmentKeyboardShortcutSingle(handler: KeyboardShortcutsHandler, keyCode: Int, event: KeyEvent, metaState: Int): Boolean { val fragment = keyboardShortcutRecipient if (fragment is KeyboardShortcutCallback) { return fragment.handleKeyboardShortcutSingle(handler, keyCode, event, metaState) } return false } private fun isFragmentKeyboardShortcutHandled(handler: KeyboardShortcutsHandler, keyCode: Int, event: KeyEvent, metaState: Int): Boolean { val fragment = keyboardShortcutRecipient if (fragment is KeyboardShortcutCallback) { return fragment.isKeyboardShortcutHandled(handler, keyCode, event, metaState) } return false } private fun handleIntent(intent: Intent, handleExtraIntent: Boolean): Int { // use package's class loader to prevent BadParcelException intent.setExtrasClassLoader(classLoader) // reset intent setIntent(Intent(this, HomeActivity::class.java)) val action = intent.action if (Intent.ACTION_SEARCH == action) { val query = intent.getStringExtra(SearchManager.QUERY) val appSearchData = intent.getBundleExtra(SearchManager.APP_DATA) val accountKey = if (appSearchData != null && appSearchData.containsKey(EXTRA_ACCOUNT_KEY)) { appSearchData.getParcelable(EXTRA_ACCOUNT_KEY) } else { Utils.getDefaultAccountKey(this) } if (query != null) { IntentUtils.openSearch(this, accountKey, query) } return -1 } val refreshOnStart = preferences.getBoolean(SharedPreferenceConstants.KEY_REFRESH_ON_START, false) if (handleExtraIntent && refreshOnStart) { twitterWrapper.refreshAll() } val uri = intent.data @CustomTabType val tabType = if (uri != null) Utils.matchTabType(uri) else null var initialTab = -1 if (tabType != null) { val accountKey = uri?.getQueryParameter(QUERY_PARAM_ACCOUNT_KEY)?.let(UserKey::valueOf) val adapter = pagerAdapter for (i in 0 until adapter.count) { val tab = adapter.get(i) if (tabType == Tab.getTypeAlias(tab.type)) { val args = tab.args if (args != null && CustomTabUtils.hasAccountKey(this, args, activatedAccountKeys, accountKey)) { initialTab = i break } } } if (initialTab == -1 && !handleExtraIntent) { // Tab not found, open account specific page when (tabType) { CustomTabType.NOTIFICATIONS_TIMELINE -> { IntentUtils.openInteractions(this, accountKey) return -1 } CustomTabType.DIRECT_MESSAGES -> { IntentUtils.openDirectMessages(this, accountKey) return -1 } } } } return initialTab } private fun initUnreadCount() { for (i in 0 until mainTabs.count) { mainTabs.setBadge(i, 0) } } private fun openAccountsDrawer() { if (homeMenu == null) return homeMenu.openDrawer(GravityCompat.START) } private fun showDrawerTutorial(): Boolean { if (preferences[drawerTutorialCompleted]) return false val targetSize = resources.getDimensionPixelSize(R.dimen.element_size_mlarge) val height = resources.displayMetrics.heightPixels val listener: TapTargetView.Listener = object : TapTargetView.Listener() { override fun onTargetClick(view: TapTargetView?) { if (!homeMenu.isDrawerOpen(GravityCompat.START)) { homeMenu.openDrawer(GravityCompat.START) } super.onTargetClick(view) } override fun onTargetDismissed(view: TapTargetView?, userInitiated: Boolean) { preferences[drawerTutorialCompleted] = true showAutoRefreshConfirm() } } val target = Rect(0, 0, targetSize, targetSize) target.offsetTo(0, height / 2 - targetSize / 2) TapTargetView.showFor(this, TapTarget.forBounds(target, getString(R.string.hint_accounts_dashboard_title), getString(R.string.hint_accounts_dashboard_message)) .apply { outerCircleColor(R.color.branding_color) dimColor(android.R.color.black) }, listener) return true } private fun showAutoRefreshConfirm() { if (isFinishing) return executeAfterFragmentResumed { activity -> val df = AutoRefreshConfirmDialogFragment() df.show(activity.supportFragmentManager, "auto_refresh_confirm") } } private fun setTabPosition(initialTab: Int) { val rememberPosition = preferences.getBoolean(SharedPreferenceConstants.KEY_REMEMBER_POSITION, true) if (initialTab >= 0) { mainPager.currentItem = initialTab.coerceInOr(0 until pagerAdapter.count, 0) } else if (rememberPosition) { val position = preferences.getInt(SharedPreferenceConstants.KEY_SAVED_TAB_POSITION, 0) mainPager.currentItem = position.coerceInOr(0 until pagerAdapter.count, 0) } } private fun setupBars() { val backgroundOption = currentThemeBackgroundOption val isTransparent = ThemeUtils.isTransparentBackground(backgroundOption) val actionBarAlpha = if (isTransparent) { ThemeUtils.getActionBarAlpha(preferences[themeBackgroundAlphaKey]) } else { 0xFF } actionsButton.alpha = actionBarAlpha / 255f } private fun setupHomeTabs() { pagerAdapter.clear() pagerAdapter.addAll(CustomTabUtils.getHomeTabs(this)) val hasNoTab = pagerAdapter.count == 0 emptyTabHint.visibility = if (hasNoTab) View.VISIBLE else View.GONE mainPager.visibility = if (hasNoTab) View.GONE else View.VISIBLE if (pagerAdapter.count > 1 && hasMultiColumns()) { mainPager.pageMargin = resources.getDimensionPixelOffset(R.dimen.home_page_margin) mainPager.setPageMarginDrawable(ThemeUtils.getDrawableFromThemeAttribute(this, R.attr.dividerVertical)) pagerAdapter.hasMultipleColumns = true pagerAdapter.preferredColumnWidth = when (preferences[multiColumnWidthKey]) { "narrow" -> resources.getDimension(R.dimen.preferred_tab_column_width_narrow) "wide" -> resources.getDimension(R.dimen.preferred_tab_column_width_wide) else -> resources.getDimension(R.dimen.preferred_tab_column_width_normal) } mainTabs.columns = floor(1.0 / pagerAdapter.getPageWidth(0)).toInt() } else { mainPager.pageMargin = 0 mainPager.setPageMarginDrawable(null) pagerAdapter.hasMultipleColumns = false mainTabs.columns = 1 } if (pagerAdapter.count == 1 && preferences[autoHideTabs]) { toolbar.isVisible = false actionsButton.updateLayoutParams { addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE) } } else if (preferences[tabPositionKey] == SharedPreferenceConstants.VALUE_TAB_POSITION_TOP) { toolbar.updateLayoutParams { addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE) } actionsButton.updateLayoutParams { addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE) } } else { toolbar.updateLayoutParams { addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE) } actionsButton.updateLayoutParams { addRule(RelativeLayout.ABOVE, toolbar.id) } } } private fun setupSlidingMenu() { homeMenu.setDrawerShadow(R.drawable.drawer_shadow_start, GravityCompat.START) homeMenu.addDrawerListener(drawerToggle) homeMenu.addDrawerListener(this) homeMenu.setShouldDisableDecider(HomeDrawerLayout.ShouldDisableDecider { e -> val fragment = leftDrawerFragment if (fragment is AccountsDashboardFragment) { return@ShouldDisableDecider fragment.shouldDisableDrawerSlide(e) } false }) } private fun showPromotionOffer() { // Skip if app doesn't support extra features if (!extraFeaturesService.isSupported()) return // Skip if already bought enhanced features pack or have set promotions options if (extraFeaturesService.isEnabled(ExtraFeaturesService.FEATURE_FEATURES_PACK) || promotionsEnabledKey in preferences) { return } val intent = Intent(this, PremiumDashboardActivity::class.java) val contentIntent = PendingIntent.getActivity(this, 0, intent, 0) val builder = NotificationChannelSpec.appNotices.notificationBuilder(this) builder.setAutoCancel(true) builder.setSmallIcon(R.drawable.ic_stat_gift) builder.setTicker(getString(R.string.message_ticker_promotions_reward)) builder.setContentTitle(getString(R.string.title_promotions_reward)) builder.setContentText(getString(R.string.message_ticker_promotions_reward)) builder.setContentIntent(contentIntent) builder.setStyle(NotificationCompat.BigTextStyle(builder) .setBigContentTitle(getString(R.string.title_promotions_reward)) .bigText(getString(R.string.message_promotions_reward))) builder.addAction(R.drawable.ic_action_confirm, getString(R.string.action_enable), PendingIntent.getBroadcast(this, 0, Intent(this, NotificationReceiver::class.java).setAction(BROADCAST_PROMOTIONS_ACCEPTED) .putExtra(EXTRA_NOTIFICATION_ID, NOTIFICATION_ID_PROMOTIONS_OFFER), PendingIntent.FLAG_ONE_SHOT)) builder.addAction(R.drawable.ic_action_cancel, getString(R.string.action_no_thanks), PendingIntent.getBroadcast(this, 0, Intent(this, NotificationReceiver::class.java).setAction(BROADCAST_PROMOTIONS_DENIED) .putExtra(EXTRA_NOTIFICATION_ID, NOTIFICATION_ID_PROMOTIONS_OFFER), PendingIntent.FLAG_ONE_SHOT)) notificationManager.notify(NOTIFICATION_ID_PROMOTIONS_OFFER, builder.build()) } private fun triggerActionsClick() { val position = mainPager.currentItem if (pagerAdapter.count == 0) return val fragment = pagerAdapter.instantiateItem(mainPager, position) as? IFloatingActionButtonFragment val handled = fragment?.onActionClick("home") ?: false if (!handled) { startActivity(Intent(INTENT_ACTION_COMPOSE)) } } private fun updateActionsButton() { if (!propertiesInitialized) return val fragment = run { if (pagerAdapter.count == 0) return@run null val position = mainPager.currentItem val f = pagerAdapter.instantiateItem(mainPager, position) as? IFloatingActionButtonFragment if (f is Fragment && (f.isDetached || f.host == null)) { return@run null } return@run f } val info = fragment?.getActionInfo("home") ?: run { actionsButton.setImageResource(R.drawable.ic_action_status_compose) actionsButton.contentDescription = getString(R.string.action_compose) return } actionsButton.setImageResource(info.icon) actionsButton.contentDescription = info.title } private fun hasMultiColumns(): Boolean { if (!DeviceUtils.isDeviceTablet(this) || !DeviceUtils.isScreenTablet(this)) return false if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { return preferences.getBoolean("multi_column_tabs_landscape", resources.getBoolean(R.bool.default_multi_column_tabs_land)) } return preferences.getBoolean("multi_column_tabs_portrait", resources.getBoolean(R.bool.default_multi_column_tabs_port)) } private class AccountUpdatedListener(private val activity: HomeActivity) : OnAccountsUpdateListener { override fun onAccountsUpdated(accounts: Array?) { activity.notifyAccountsChanged() activity.updateUnreadCount() } } private class UpdateUnreadCountTask( context: Context, private val preferences: SharedPreferences, private val readStateManager: ReadStateManager, indicator: TabPagerIndicator, private val tabs: Array ) : AsyncTask() { private val activatedKeys = DataStoreUtils.getActivatedAccountKeys(context) private val contextRef = WeakReference(context) private val indicatorRef = WeakReference(indicator) override fun doInBackground(vararg params: Any): SparseIntArray { val result = SparseIntArray() val context = contextRef.get() ?: return result tabs.forEachIndexed { i, spec -> if (spec.type == null) { publishProgress(TabBadge(i, -1)) return@forEachIndexed } when (spec.type) { CustomTabType.HOME_TIMELINE -> { val accountKeys = Utils.getAccountKeys(context, spec.args) ?: activatedKeys val position = accountKeys.map { val tag = Utils.getReadPositionTagWithAccount(ReadPositionTag.HOME_TIMELINE, it) readStateManager.getPosition(tag) }.fold(0L, Math::max) val count = DataStoreUtils.getStatusesCount(context, preferences, Statuses.CONTENT_URI, spec.args, Statuses.TIMESTAMP, position, true, accountKeys, FilterScope.HOME) result.put(i, count) publishProgress(TabBadge(i, count)) } CustomTabType.NOTIFICATIONS_TIMELINE -> { val accountKeys = Utils.getAccountKeys(context, spec.args) ?: activatedKeys val position = accountKeys.map { val tag = Utils.getReadPositionTagWithAccount(ReadPositionTag.ACTIVITIES_ABOUT_ME, it) readStateManager.getPosition(tag) }.fold(0L, Math::max) val count = DataStoreUtils.getInteractionsCount(context, preferences, spec.args, accountKeys, position, Activities.TIMESTAMP, FilterScope.INTERACTIONS) result.put(i, count) publishProgress(TabBadge(i, count)) } CustomTabType.DIRECT_MESSAGES -> { val accountKeys = Utils.getAccountKeys(context, spec.args) ?: activatedKeys val projection = (Conversations.COLUMNS + Conversations.UNREAD_COUNT).map { TwidereQueryBuilder.mapConversationsProjection(it) }.toTypedArray() val unreadHaving = Expression.greaterThan(Conversations.UNREAD_COUNT, 0) val count = context.contentResolver.getUnreadMessagesEntriesCursorReference(projection, accountKeys, extraHaving = unreadHaving)?.use { (cur) -> return@use cur.count } ?: -1 result.put(i, count) publishProgress(TabBadge(i, count)) } else -> { publishProgress(TabBadge(i, -1)) } } } return result } override fun onPostExecute(result: SparseIntArray) { val indicator = indicatorRef.get() ?: return indicator.clearBadge() for (i in 0 until result.size()) { indicator.setBadge(result.keyAt(i), result.valueAt(i)) } } override fun onProgressUpdate(vararg values: TabBadge) { val indicator = indicatorRef.get() ?: return for (value in values) { indicator.setBadge(value.index, value.count) } } internal class TabBadge(var index: Int, var count: Int) } class AutoRefreshConfirmDialogFragment : BaseDialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val builder = AlertDialog.Builder(requireContext()) builder.setTitle(R.string.auto_refresh) builder.setMessage(R.string.message_auto_refresh_confirm) builder.setPositiveButton(android.R.string.ok) { _, _ -> kPreferences[defaultAutoRefreshKey] = true } builder.setNegativeButton(R.string.action_no_thanks) { _, _ -> kPreferences[defaultAutoRefreshKey] = false } val dialog = builder.create() dialog.onShow { it.applyTheme() } return dialog } override fun onDismiss(dialog: DialogInterface) { kPreferences[defaultAutoRefreshAskedKey] = true super.onDismiss(dialog) } } companion object { private val HOME_AS_UP_ATTRS = intArrayOf(android.R.attr.homeAsUpIndicator) } }