From 74bd49387849a648c84570bef5d7c89f5f37f5e0 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Thu, 18 Jun 2020 11:04:53 +0200 Subject: [PATCH] add possibility to move the main navigation to the bottom (#1808) * add possibility to move the main navigation to the bottom * add top toolbar with drawer toggle, title and search button --- .../com/keylesspalace/tusky/MainActivity.kt | 112 ++++++++++++------ .../tusky/PreferencesActivity.kt | 2 +- .../java/com/keylesspalace/tusky/TabData.kt | 58 +++++++-- .../preference/PreferencesFragment.kt | 9 ++ .../tusky/settings/SettingsConstants.kt | 1 + .../keylesspalace/tusky/util/ThemeUtils.java | 8 ++ app/src/main/res/layout/activity_main.xml | 43 ++++--- app/src/main/res/values/dimens.xml | 2 + app/src/main/res/values/donottranslate.xml | 9 ++ app/src/main/res/values/strings.xml | 5 + 10 files changed, 188 insertions(+), 61 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt index d0d2bf5b..da818b2c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt @@ -31,6 +31,7 @@ import android.view.View import android.widget.ImageView import androidx.appcompat.app.ActionBarDrawerToggle import androidx.appcompat.app.AlertDialog +import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.content.ContextCompat import androidx.core.content.pm.ShortcutManagerCompat import androidx.emoji.text.EmojiCompat @@ -59,7 +60,10 @@ import com.keylesspalace.tusky.interfaces.ActionButtonActivity import com.keylesspalace.tusky.interfaces.ReselectableFragment import com.keylesspalace.tusky.pager.MainPagerAdapter import com.keylesspalace.tusky.util.* +import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial +import com.mikepenz.iconics.utils.colorInt +import com.mikepenz.iconics.utils.sizeDp import com.mikepenz.materialdrawer.iconics.iconicsIcon import com.mikepenz.materialdrawer.model.* import com.mikepenz.materialdrawer.model.interfaces.* @@ -89,8 +93,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje private lateinit var drawerToggle: ActionBarDrawerToggle private var notificationTabPosition = 0 - - private var adapter: MainPagerAdapter? = null + private var onTabSelectedListener: OnTabSelectedListener? = null private val emojiInitCallback = object : InitCallback() { override fun onInitialized() { @@ -159,6 +162,19 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje val composeIntent = Intent(applicationContext, ComposeActivity::class.java) startActivity(composeIntent) } + + mainToolbar.menu.add(R.string.action_search).apply { + setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM) + icon = IconicsDrawable(this@MainActivity, GoogleMaterial.Icon.gmd_search).apply { + sizeDp = 20 + colorInt = ThemeUtils.getColor(this@MainActivity, android.R.attr.textColorPrimary) + } + setOnMenuItemClickListener { + startActivity(SearchActivity.getIntent(this@MainActivity)) + true + } + } + setupDrawer(savedInstanceState) /* Fetch user info while we're doing other things. This has to be done after setting up the @@ -167,30 +183,6 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje setupTabs(showNotificationTab) - val pageMargin = resources.getDimensionPixelSize(R.dimen.tab_page_margin) - viewPager.setPageTransformer(MarginPageTransformer(pageMargin)) - - val uswSwipeForTabs = PreferenceManager.getDefaultSharedPreferences(this) - .getBoolean("enableSwipeForTabs", true) - viewPager.isUserInputEnabled = uswSwipeForTabs - - tabLayout.addOnTabSelectedListener(object : OnTabSelectedListener { - override fun onTabSelected(tab: TabLayout.Tab) { - if (tab.position == notificationTabPosition) { - NotificationHelper.clearNotificationsForActiveAccount(this@MainActivity, accountManager) - } - } - - override fun onTabUnselected(tab: TabLayout.Tab) {} - - override fun onTabReselected(tab: TabLayout.Tab) { - val fragment = adapter?.getFragment(tab.position) - if (fragment is ReselectableFragment) { - (fragment as ReselectableFragment).onReselect() - } - } - }) - // Setup push notifications if (NotificationHelper.areNotificationsEnabled(this, accountManager)) { NotificationHelper.enablePullNotifications(this) @@ -376,13 +368,6 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje startActivityWithSlideInAnimation(ListsActivity.newIntent(context)) } }, - primaryDrawerItem { - nameRes = R.string.action_search - iconicsIcon = GoogleMaterial.Icon.gmd_search - onClick = { - startActivityWithSlideInAnimation(SearchActivity.getIntent(context)) - } - }, primaryDrawerItem { nameRes = R.string.action_access_saved_toot iconRes = R.drawable.ic_notebook @@ -461,20 +446,37 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje } private fun setupTabs(selectNotificationTab: Boolean) { + val preferences = PreferenceManager.getDefaultSharedPreferences(this) + + val activeTabLayout = if(preferences.getString("mainNavPosition", "top") == "bottom") { + val actionBarSize = ThemeUtils.getDimension(this, R.attr.actionBarSize) + val fabMargin = resources.getDimensionPixelSize(R.dimen.fabMargin) + (composeButton.layoutParams as CoordinatorLayout.LayoutParams).bottomMargin = actionBarSize + fabMargin + tabLayout.hide() + bottomTabLayout + } else { + bottomNav.hide() + (viewPager.layoutParams as CoordinatorLayout.LayoutParams).bottomMargin = 0 + (composeButton.layoutParams as CoordinatorLayout.LayoutParams).anchorId = R.id.viewPager + tabLayout + } + val tabs = accountManager.activeAccount!!.tabPreferences - adapter = MainPagerAdapter(tabs, this) + + val adapter = MainPagerAdapter(tabs, this) viewPager.adapter = adapter - TabLayoutMediator(tabLayout, viewPager, TabConfigurationStrategy { _: TabLayout.Tab?, _: Int -> }).attach() - tabLayout.removeAllTabs() + TabLayoutMediator(activeTabLayout, viewPager, TabConfigurationStrategy { _: TabLayout.Tab?, _: Int -> }).attach() + activeTabLayout.removeAllTabs() for (i in tabs.indices) { - val tab = tabLayout.newTab() + val tab = activeTabLayout.newTab() .setIcon(tabs[i].icon) if (tabs[i].id == LIST) { tab.contentDescription = tabs[i].arguments[1] } else { tab.setContentDescription(tabs[i].text) } - tabLayout.addTab(tab) + activeTabLayout.addTab(tab) + if (tabs[i].id == NOTIFICATIONS) { notificationTabPosition = i if (selectNotificationTab) { @@ -482,6 +484,40 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje } } } + + val pageMargin = resources.getDimensionPixelSize(R.dimen.tab_page_margin) + viewPager.setPageTransformer(MarginPageTransformer(pageMargin)) + + val uswSwipeForTabs = preferences.getBoolean("enableSwipeForTabs", true) + viewPager.isUserInputEnabled = uswSwipeForTabs + + onTabSelectedListener?.let { + activeTabLayout.removeOnTabSelectedListener(it) + } + + onTabSelectedListener = object : OnTabSelectedListener { + override fun onTabSelected(tab: TabLayout.Tab) { + if (tab.position == notificationTabPosition) { + NotificationHelper.clearNotificationsForActiveAccount(this@MainActivity, accountManager) + } + + mainToolbar.title = tabs[tab.position].title(this@MainActivity) + } + + override fun onTabUnselected(tab: TabLayout.Tab) {} + + override fun onTabReselected(tab: TabLayout.Tab) { + val fragment = adapter.getFragment(tab.position) + if (fragment is ReselectableFragment) { + (fragment as ReselectableFragment).onReselect() + } + } + }.also { + activeTabLayout.addOnTabSelectedListener(it) + } + + mainToolbar.title = tabs[0].title(this@MainActivity) + } private fun handleProfileClick(profile: IProfile, current: Boolean): Boolean { diff --git a/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.kt b/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.kt index 290485bf..6ba86e2c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.kt @@ -129,7 +129,7 @@ class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreference } "statusTextSize", "absoluteTimeView", "showBotOverlay", "animateGifAvatars", - "useBlurhash", "showCardsInTimelines", "confirmReblogs", "enableSwipeForTabs" -> { + "useBlurhash", "showCardsInTimelines", "confirmReblogs", "enableSwipeForTabs", "mainNavPosition" -> { restartActivitiesOnExit = true } "language" -> { diff --git a/app/src/main/java/com/keylesspalace/tusky/TabData.kt b/app/src/main/java/com/keylesspalace/tusky/TabData.kt index 0e239c5a..4bf123b8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/TabData.kt +++ b/app/src/main/java/com/keylesspalace/tusky/TabData.kt @@ -15,6 +15,7 @@ package com.keylesspalace.tusky +import android.content.Context import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.fragment.app.Fragment @@ -36,17 +37,58 @@ data class TabData(val id: String, @StringRes val text: Int, @DrawableRes val icon: Int, val fragment: (List) -> Fragment, - val arguments: List = emptyList()) + val arguments: List = emptyList(), + val title: (Context) -> String = { context -> context.getString(text)} + ) fun createTabDataFromId(id: String, arguments: List = emptyList()): TabData { return when (id) { - HOME -> TabData(HOME, R.string.title_home, R.drawable.ic_home_24dp, { TimelineFragment.newInstance(TimelineFragment.Kind.HOME) }) - NOTIFICATIONS -> TabData(NOTIFICATIONS, R.string.title_notifications, R.drawable.ic_notifications_24dp, { NotificationsFragment.newInstance() }) - LOCAL -> TabData(LOCAL, R.string.title_public_local, R.drawable.ic_local_24dp, { TimelineFragment.newInstance(TimelineFragment.Kind.PUBLIC_LOCAL) }) - FEDERATED -> TabData(FEDERATED, R.string.title_public_federated, R.drawable.ic_public_24dp, { TimelineFragment.newInstance(TimelineFragment.Kind.PUBLIC_FEDERATED) }) - DIRECT -> TabData(DIRECT, R.string.title_direct_messages, R.drawable.ic_reblog_direct_24dp, { ConversationsFragment.newInstance() }) - HASHTAG -> TabData(HASHTAG, R.string.hashtags, R.drawable.ic_hashtag, { args -> TimelineFragment.newHashtagInstance(args) }, arguments) - LIST -> TabData(LIST, R.string.list, R.drawable.ic_list, { args -> TimelineFragment.newInstance(TimelineFragment.Kind.LIST, args.getOrNull(0).orEmpty()) }, arguments) + HOME -> TabData( + HOME, + R.string.title_home, + R.drawable.ic_home_24dp, + { TimelineFragment.newInstance(TimelineFragment.Kind.HOME) } + ) + NOTIFICATIONS -> TabData( + NOTIFICATIONS, + R.string.title_notifications, + R.drawable.ic_notifications_24dp, + { NotificationsFragment.newInstance() } + ) + LOCAL -> TabData( + LOCAL, + R.string.title_public_local, + R.drawable.ic_local_24dp, + { TimelineFragment.newInstance(TimelineFragment.Kind.PUBLIC_LOCAL) } + ) + FEDERATED -> TabData( + FEDERATED, + R.string.title_public_federated, + R.drawable.ic_public_24dp, + { TimelineFragment.newInstance(TimelineFragment.Kind.PUBLIC_FEDERATED) } + ) + DIRECT -> TabData( + DIRECT, + R.string.title_direct_messages, + R.drawable.ic_reblog_direct_24dp, + { ConversationsFragment.newInstance() } + ) + HASHTAG -> TabData( + HASHTAG, + R.string.hashtags, + R.drawable.ic_hashtag, + { args -> TimelineFragment.newHashtagInstance(args) }, + arguments, + { context -> arguments.joinToString(separator = " ") { context.getString(R.string.title_tag, it) }} + ) + LIST -> TabData( + LIST, + R.string.list, + R.drawable.ic_list, + { args -> TimelineFragment.newInstance(TimelineFragment.Kind.LIST, args.getOrNull(0).orEmpty()) }, + arguments, + { arguments.getOrNull(1).orEmpty() } + ) else -> throw IllegalArgumentException("unknown tab type") } } diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/preference/PreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/preference/PreferencesFragment.kt index 60e97811..da459649 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/preference/PreferencesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/preference/PreferencesFragment.kt @@ -76,6 +76,15 @@ class PreferencesFragment : PreferenceFragmentCompat() { icon = makeIcon(GoogleMaterial.Icon.gmd_format_size) } + listPreference { + setDefaultValue("top") + setEntries(R.array.pref_main_nav_position_options) + setEntryValues(R.array.pref_main_nav_position_values) + key = PrefKeys.MAIN_NAV_POSITION + setSummaryProvider { entry } + setTitle(R.string.pref_main_nav_position) + } + switchPreference { setDefaultValue(false) key = PrefKeys.FAB_HIDE diff --git a/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt b/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt index ab60206f..f3f54ea8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt +++ b/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt @@ -21,6 +21,7 @@ object PrefKeys { const val FAB_HIDE = "fabHide" const val LANGUAGE = "language" const val STATUS_TEXT_SIZE = "statusTextSize" + const val MAIN_NAV_POSITION = "mainNavPosition" const val ABSOLUTE_TIME_VIEW = "absoluteTimeView" const val SHOW_BOT_OVERLAY = "showBotOverlay" const val ANIMATE_GIF_AVATARS = "animateGifAvatars" diff --git a/app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java b/app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java index d98259d7..9f7be621 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java +++ b/app/src/main/java/com/keylesspalace/tusky/util/ThemeUtils.java @@ -16,6 +16,7 @@ package com.keylesspalace.tusky.util; import android.content.Context; +import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; @@ -51,6 +52,13 @@ public class ThemeUtils { } } + public static int getDimension(@NonNull Context context, @AttrRes int attribute) { + TypedArray array = context.obtainStyledAttributes(new int[] { attribute }); + int dimen = array.getDimensionPixelSize(0, -1); + array.recycle(); + return dimen; + } + /** this can be replaced with drawableTint in xml once minSdkVersion >= 23 */ @Nullable public static Drawable getTintedDrawable(@NonNull Context context, @DrawableRes int drawableId, @AttrRes int colorAttr) { diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 168e41b7..2890dd5c 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -22,19 +22,17 @@ android:id="@+id/mainToolbar" android:layout_width="match_parent" android:layout_height="wrap_content" - app:contentInsetStartWithNavigation="0dp"> + app:contentInsetStartWithNavigation="0dp" + app:layout_scrollFlags="scroll|enterAlways" /> - - - + @@ -42,15 +40,32 @@ android:id="@+id/viewPager" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_below="@id/tabLayout" + android:layout_marginBottom="?attr/actionBarSize" android:background="?attr/windowBackgroundColor" app:layout_behavior="@string/appbar_scrolling_view_behavior" /> + + + + + + 72dp 108dp + + 16dp diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index a561d0e4..d6412569 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -110,6 +110,15 @@ ja + + @string/pref_main_nav_position_option_top + @string/pref_main_nav_position_option_bottom + + + + top + bottom + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c48d7cb2..fb4032f4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -252,6 +252,11 @@ Publishing (synced with server) Failed to sync settings + Main navigation position + Top + Bottom + + Public Unlisted Followers-only