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
This commit is contained in:
Konrad Pozniak 2020-06-18 11:04:53 +02:00 committed by GitHub
parent 70d426975f
commit 74bd493878
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 188 additions and 61 deletions

View file

@ -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 {

View file

@ -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" -> {

View file

@ -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<String>) -> Fragment,
val arguments: List<String> = emptyList())
val arguments: List<String> = emptyList(),
val title: (Context) -> String = { context -> context.getString(text)}
)
fun createTabDataFromId(id: String, arguments: List<String> = 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")
}
}

View file

@ -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

View file

@ -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"

View file

@ -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) {

View file

@ -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" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
style="@style/TuskyTabAppearance"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:tabGravity="fill"
app:tabMaxWidth="0dp"
app:tabMode="fixed"
app:tabUnboundedRipple="false" />
</androidx.appcompat.widget.Toolbar>
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
style="@style/TuskyTabAppearance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabGravity="fill"
app:tabMaxWidth="0dp"
app:tabMode="fixed" />
</com.google.android.material.appbar.AppBarLayout>
@ -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" />
<com.google.android.material.bottomappbar.BottomAppBar
android:id="@+id/bottomNav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:contentInsetStart="0dp"
app:fabAlignmentMode="end">
<com.google.android.material.tabs.TabLayout
android:id="@+id/bottomTabLayout"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:tabIndicator="@null"
app:tabMode="fixed" />
</com.google.android.material.bottomappbar.BottomAppBar>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/composeButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:layout_margin="@dimen/fabMargin"
android:contentDescription="@string/action_compose"
app:layout_anchor="@id/viewPager"
app:layout_anchorGravity="bottom|end"

View file

@ -48,4 +48,6 @@
<dimen name="adaptive_bitmap_inner_size">72dp</dimen>
<dimen name="adaptive_bitmap_outer_size">108dp</dimen>
<dimen name="fabMargin">16dp</dimen>
</resources>

View file

@ -110,6 +110,15 @@
<item>ja</item>
</string-array>
<string-array name="pref_main_nav_position_options">
<item>@string/pref_main_nav_position_option_top</item>
<item>@string/pref_main_nav_position_option_bottom</item>
</string-array>
<string-array name="pref_main_nav_position_values">
<item>top</item>
<item>bottom</item>
</string-array>
<string name="description_status" translatable="false">
<!-- Display name, cw?, content?, poll? relative date, reposted by?, reposted?, favorited?, bookmarked?, username, media?; visibility, fav number?, reblog number?-->

View file

@ -252,6 +252,11 @@
<string name="pref_publishing">Publishing (synced with server)</string>
<string name="pref_failed_to_sync">Failed to sync settings</string>
<string name="pref_main_nav_position">Main navigation position</string>
<string name="pref_main_nav_position_option_top">Top</string>
<string name="pref_main_nav_position_option_bottom">Bottom</string>
<string name="post_privacy_public">Public</string>
<string name="post_privacy_unlisted">Unlisted</string>
<string name="post_privacy_followers_only">Followers-only</string>