diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt index bf79ef615..e79fab251 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt @@ -16,6 +16,7 @@ package com.keylesspalace.tusky import android.Manifest +import android.annotation.SuppressLint import android.app.NotificationManager import android.content.Context import android.content.DialogInterface @@ -34,6 +35,7 @@ import android.view.KeyEvent import android.view.Menu import android.view.MenuInflater import android.view.MenuItem +import android.view.MenuItem.SHOW_AS_ACTION_NEVER import android.view.View import android.widget.ImageView import androidx.activity.OnBackPressedCallback @@ -46,6 +48,8 @@ import androidx.core.content.IntentCompat import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.view.GravityCompat import androidx.core.view.MenuProvider +import androidx.core.view.forEach +import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import androidx.preference.PreferenceManager import androidx.viewpager2.widget.MarginPageTransformer @@ -102,7 +106,6 @@ import com.keylesspalace.tusky.util.show import com.keylesspalace.tusky.util.unsafeLazy import com.keylesspalace.tusky.util.updateShortcut import com.keylesspalace.tusky.util.viewBinding -import com.keylesspalace.tusky.util.visible import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial import com.mikepenz.iconics.utils.colorInt @@ -177,6 +180,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje /** Adapter for the different timeline tabs */ private lateinit var tabAdapter: MainPagerAdapter + @SuppressLint("RestrictedApi") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -253,7 +257,6 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje } window.statusBarColor = Color.TRANSPARENT // don't draw a status bar, the DrawerLayout and the MaterialDrawerLayout have their own setContentView(binding.root) - setSupportActionBar(binding.mainToolbar) glide = Glide.with(this) @@ -262,8 +265,21 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje startActivity(composeIntent) } + // Determine which of the three toolbars should be the supportActionBar (which hosts + // the options menu). val hideTopToolbar = preferences.getBoolean(PrefKeys.HIDE_TOP_TOOLBAR, false) - binding.mainToolbar.visible(!hideTopToolbar) + if (hideTopToolbar) { + when (preferences.getString(PrefKeys.MAIN_NAV_POSITION, "top")) { + "top" -> setSupportActionBar(binding.topNav) + "bottom" -> setSupportActionBar(binding.bottomNav) + } + binding.mainToolbar.hide() + // There's not enough space in the top/bottom bars to show the title as well. + supportActionBar?.setDisplayShowTitleEnabled(false) + } else { + setSupportActionBar(binding.mainToolbar) + binding.mainToolbar.show() + } loadDrawerAvatar(activeAccount.profilePictureUrl, true) @@ -361,6 +377,14 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje } } + override fun onPrepareMenu(menu: Menu) { + super.onPrepareMenu(menu) + + // If the main toolbar is hidden then there's no space in the top/bottomNav to show + // the menu items as icons, so forceably disable them + if (!binding.mainToolbar.isVisible) menu.forEach { it.setShowAsAction(SHOW_AS_ACTION_NEVER) } + } + override fun onMenuItemSelected(item: MenuItem): Boolean { return when (item.itemId) { R.id.action_search -> { @@ -458,8 +482,8 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje val drawerOpenClickListener = View.OnClickListener { binding.mainDrawerLayout.open() } binding.mainToolbar.setNavigationOnClickListener(drawerOpenClickListener) - binding.topNavAvatar.setOnClickListener(drawerOpenClickListener) - binding.bottomNavAvatar.setOnClickListener(drawerOpenClickListener) + binding.topNav.setNavigationOnClickListener(drawerOpenClickListener) + binding.bottomNav.setNavigationOnClickListener(drawerOpenClickListener) header = AccountHeaderView(this).apply { headerBackgroundScaleType = ImageView.ScaleType.CENTER_CROP @@ -894,112 +918,75 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje val hideTopToolbar = preferences.getBoolean(PrefKeys.HIDE_TOP_TOOLBAR, false) val animateAvatars = preferences.getBoolean("animateGifAvatars", false) - if (hideTopToolbar) { + val activeToolbar = if (hideTopToolbar) { val navOnBottom = preferences.getString("mainNavPosition", "top") == "bottom" - - val avatarView = if (navOnBottom) { - binding.bottomNavAvatar.show() - binding.bottomNavAvatar + if (navOnBottom) { + binding.bottomNav } else { - binding.topNavAvatar.show() - binding.topNavAvatar - } - - if (animateAvatars) { - Glide.with(this) - .load(avatarUrl) - .placeholder(R.drawable.avatar_default) - .into(avatarView) - } else { - Glide.with(this) - .asBitmap() - .load(avatarUrl) - .placeholder(R.drawable.avatar_default) - .into(avatarView) + binding.topNav } } else { - binding.bottomNavAvatar.hide() - binding.topNavAvatar.hide() + binding.mainToolbar + } - val navIconSize = resources.getDimensionPixelSize(R.dimen.avatar_toolbar_nav_icon_size) + val navIconSize = resources.getDimensionPixelSize(R.dimen.avatar_toolbar_nav_icon_size) - if (animateAvatars) { - glide.asDrawable() - .load(avatarUrl) - .transform( - RoundedCorners(resources.getDimensionPixelSize(R.dimen.avatar_radius_36dp)) - ) - .apply { - if (showPlaceholder) { - placeholder(R.drawable.avatar_default) + if (animateAvatars) { + glide.asDrawable().load(avatarUrl).transform(RoundedCorners(resources.getDimensionPixelSize(R.dimen.avatar_radius_36dp))) + .apply { + if (showPlaceholder) placeholder(R.drawable.avatar_default) + } + .into(object : CustomTarget(navIconSize, navIconSize) { + + override fun onLoadStarted(placeholder: Drawable?) { + placeholder?.let { + activeToolbar.navigationIcon = FixedSizeDrawable(it, navIconSize, navIconSize) } } - .into(object : CustomTarget(navIconSize, navIconSize) { - override fun onLoadStarted(placeholder: Drawable?) { - if (placeholder != null) { - binding.mainToolbar.navigationIcon = - FixedSizeDrawable(placeholder, navIconSize, navIconSize) - } - } + override fun onResourceReady( + resource: Drawable, + transition: Transition? + ) { + if (resource is Animatable) resource.start() + activeToolbar.navigationIcon = FixedSizeDrawable(resource, navIconSize, navIconSize) + } - override fun onResourceReady( - resource: Drawable, - transition: Transition? - ) { - if (resource is Animatable) { - resource.start() - } - binding.mainToolbar.navigationIcon = - FixedSizeDrawable(resource, navIconSize, navIconSize) - } - - override fun onLoadCleared(placeholder: Drawable?) { - if (placeholder != null) { - binding.mainToolbar.navigationIcon = - FixedSizeDrawable(placeholder, navIconSize, navIconSize) - } - } - }) - } else { - glide.asBitmap() - .load(avatarUrl) - .transform( - RoundedCorners(resources.getDimensionPixelSize(R.dimen.avatar_radius_36dp)) - ) - .apply { - if (showPlaceholder) { - placeholder(R.drawable.avatar_default) + override fun onLoadCleared(placeholder: Drawable?) { + placeholder?.let { + activeToolbar.navigationIcon = FixedSizeDrawable(it, navIconSize, navIconSize) } } - .into(object : CustomTarget(navIconSize, navIconSize) { - - override fun onLoadStarted(placeholder: Drawable?) { - if (placeholder != null) { - binding.mainToolbar.navigationIcon = - FixedSizeDrawable(placeholder, navIconSize, navIconSize) - } + }) + } else { + glide.asBitmap().load(avatarUrl).transform(RoundedCorners(resources.getDimensionPixelSize(R.dimen.avatar_radius_36dp))) + .apply { + if (showPlaceholder) placeholder(R.drawable.avatar_default) + } + .into(object : CustomTarget(navIconSize, navIconSize) { + override fun onLoadStarted(placeholder: Drawable?) { + placeholder?.let { + activeToolbar.navigationIcon = FixedSizeDrawable(it, navIconSize, navIconSize) } + } - override fun onResourceReady( - resource: Bitmap, - transition: Transition? - ) { - binding.mainToolbar.navigationIcon = FixedSizeDrawable( - BitmapDrawable(resources, resource), - navIconSize, - navIconSize - ) - } + override fun onResourceReady( + resource: Bitmap, + transition: Transition? + ) { + activeToolbar.navigationIcon = FixedSizeDrawable( + BitmapDrawable(resources, resource), + navIconSize, + navIconSize + ) + } - override fun onLoadCleared(placeholder: Drawable?) { - if (placeholder != null) { - binding.mainToolbar.navigationIcon = - FixedSizeDrawable(placeholder, navIconSize, navIconSize) - } + override fun onLoadCleared(placeholder: Drawable?) { + placeholder?.let { + activeToolbar.navigationIcon = FixedSizeDrawable(it, navIconSize, navIconSize) } - }) - } + } + }) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.kt b/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.kt index e7c646991..3c943863d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.kt +++ b/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.kt @@ -130,6 +130,12 @@ class TuskyApplication : Application(), HasAndroidInjector { editor.remove(PrefKeys.MEDIA_PREVIEW_ENABLED) } + if (oldVersion < 2023072401) { + // The notifications filter / clear options are shown on a menu, not a separate bar, + // the preference to display them is not needed. + editor.remove(PrefKeys.Deprecated.SHOW_NOTIFICATIONS_FILTER) + } + editor.putInt(PrefKeys.SCHEMA_VERSION, newVersion) editor.apply() } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsFragment.kt index f26a46528..13d11eeb6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsFragment.kt @@ -28,7 +28,6 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AlertDialog -import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.view.MenuProvider import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment @@ -46,7 +45,6 @@ import androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE import androidx.recyclerview.widget.SimpleItemAnimator import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener import at.connyduck.sparkbutton.helpers.Utils -import com.google.android.material.appbar.AppBarLayout.ScrollingViewBehavior import com.google.android.material.color.MaterialColors import com.google.android.material.snackbar.Snackbar import com.keylesspalace.tusky.R @@ -123,21 +121,6 @@ class NotificationsFragment : return inflater.inflate(R.layout.fragment_timeline_notifications, container, false) } - private fun updateFilterVisibility(showFilter: Boolean) { - val params = binding.swipeRefreshLayout.layoutParams as CoordinatorLayout.LayoutParams - if (showFilter) { - binding.appBarOptions.setExpanded(true, false) - binding.appBarOptions.visibility = View.VISIBLE - // Set content behaviour to hide filter on scroll - params.behavior = ScrollingViewBehavior() - } else { - binding.appBarOptions.setExpanded(false, false) - binding.appBarOptions.visibility = View.GONE - // Clear behaviour to hide app bar - params.behavior = null - } - } - private fun confirmClearNotifications() { AlertDialog.Builder(requireContext()) .setMessage(R.string.notification_clear_text) @@ -215,8 +198,6 @@ class NotificationsFragment : footer = NotificationsLoadStateAdapter { adapter.retry() } ) - binding.buttonClear.setOnClickListener { confirmClearNotifications() } - binding.buttonFilter.setOnClickListener { showFilterDialog() } (binding.recyclerView.itemAnimator as SimpleItemAnimator?)!!.supportsChangeAnimations = false @@ -369,10 +350,10 @@ class NotificationsFragment : } } - // Update filter option visibility from uiState - launch { - viewModel.uiState.collectLatest { updateFilterVisibility(it.showFilterOptions) } - } + // Collect the uiState. Nothing is done with it, but if you don't collect it then + // accessing viewModel.uiState.value (e.g., when the filter dialog is created) + // returns an empty object. + launch { viewModel.uiState.collect() } // Update status display from statusDisplayOptions. If the new options request // relative time display collect the flow to periodically update the timestamp in the list gui elements. @@ -439,10 +420,17 @@ class NotificationsFragment : override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { menuInflater.inflate(R.menu.fragment_notifications, menu) + val iconColor = MaterialColors.getColor(binding.root, android.R.attr.textColorPrimary) menu.findItem(R.id.action_refresh)?.apply { icon = IconicsDrawable(requireContext(), GoogleMaterial.Icon.gmd_refresh).apply { sizeDp = 20 - colorInt = MaterialColors.getColor(binding.root, android.R.attr.textColorPrimary) + colorInt = iconColor + } + } + menu.findItem(R.id.action_edit_notification_filter)?.apply { + icon = IconicsDrawable(requireContext(), GoogleMaterial.Icon.gmd_tune).apply { + sizeDp = 20 + colorInt = iconColor } } } @@ -458,6 +446,14 @@ class NotificationsFragment : viewModel.accept(InfallibleUiAction.LoadNewest) true } + R.id.action_edit_notification_filter -> { + showFilterDialog() + true + } + R.id.action_clear_notifications -> { + confirmClearNotifications() + true + } else -> false } } @@ -625,7 +621,6 @@ class NotificationsFragment : override fun onReselect() { if (isAdded) { - binding.appBarOptions.setExpanded(true, false) layoutManager.scrollToPosition(0) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModel.kt index 1f3f982b9..d06a8bbcf 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationsViewModel.kt @@ -74,23 +74,18 @@ data class UiState( /** Filtered notification types */ val activeFilter: Set = emptySet(), - /** True if the UI to filter and clear notifications should be shown */ - val showFilterOptions: Boolean = false, - /** True if the FAB should be shown while scrolling */ val showFabWhileScrolling: Boolean = true ) /** Preferences the UI reacts to */ data class UiPrefs( - val showFabWhileScrolling: Boolean, - val showFilter: Boolean + val showFabWhileScrolling: Boolean ) { companion object { /** Relevant preference keys. Changes to any of these trigger a display update */ val prefKeys = setOf( - PrefKeys.FAB_HIDE, - PrefKeys.SHOW_NOTIFICATIONS_FILTER + PrefKeys.FAB_HIDE ) } } @@ -495,7 +490,6 @@ class NotificationsViewModel @Inject constructor( uiState = combine(notificationFilter, getUiPrefs()) { filter, prefs -> UiState( activeFilter = filter.filter, - showFilterOptions = prefs.showFilter, showFabWhileScrolling = prefs.showFabWhileScrolling ) }.stateIn( @@ -544,8 +538,7 @@ class NotificationsViewModel @Inject constructor( .onStart { emit(toPrefs()) } private fun toPrefs() = UiPrefs( - showFabWhileScrolling = !preferences.getBoolean(PrefKeys.FAB_HIDE, false), - showFilter = preferences.getBoolean(PrefKeys.SHOW_NOTIFICATIONS_FILTER, true) + showFabWhileScrolling = !preferences.getBoolean(PrefKeys.FAB_HIDE, false) ) companion object { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt index e2d29d495..f6541f1fd 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt @@ -208,13 +208,6 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable { isSingleLineTitle = false } - switchPreference { - setDefaultValue(true) - key = PrefKeys.SHOW_NOTIFICATIONS_FILTER - setTitle(R.string.pref_title_show_notifications_filter) - isSingleLineTitle = false - } - switchPreference { setDefaultValue(true) key = PrefKeys.CONFIRM_REBLOGS 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 1a64f69b0..636c1fc69 100644 --- a/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt +++ b/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt @@ -41,7 +41,7 @@ enum class AppTheme(val value: String) { * * - Adding a new preference that does not change the interpretation of an existing preference */ -const val SCHEMA_VERSION = 2023022701 +const val SCHEMA_VERSION = 2023072401 object PrefKeys { // Note: not all of these keys are actually used as SharedPreferences keys but we must give @@ -61,7 +61,6 @@ object PrefKeys { const val ANIMATE_GIF_AVATARS = "animateGifAvatars" const val USE_BLURHASH = "useBlurhash" const val SHOW_SELF_USERNAME = "showSelfUsername" - const val SHOW_NOTIFICATIONS_FILTER = "showNotificationsFilter" const val SHOW_CARDS_IN_TIMELINES = "showCardsInTimelines" const val CONFIRM_REBLOGS = "confirmReblogs" const val CONFIRM_FAVOURITES = "confirmFavourites" @@ -104,4 +103,9 @@ object PrefKeys { /** UI text scaling factor, stored as float, 100 = 100% = no scaling */ const val UI_TEXT_SCALE_RATIO = "uiTextScaleRatio" + + /** Keys that are no longer used (e.g., the preference has been removed */ + object Deprecated { + const val SHOW_NOTIFICATIONS_FILTER = "showNotificationsFilter" + } } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index dd22e156a..8082e0ce6 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -29,22 +29,14 @@ app:layout_scrollFlags="scroll|enterAlways" app:navigationContentDescription="@string/action_open_drawer" /> - - - + android:layout_height="48dp" + android:orientation="horizontal" + app:contentInsetStart="0dp" + app:contentInsetStartWithNavigation="0dp" + app:navigationContentDescription="@string/action_open_drawer"> - - + @@ -73,33 +64,16 @@ android:layout_height="wrap_content" android:layout_gravity="bottom" app:contentInsetStart="0dp" + app:contentInsetStartWithNavigation="0dp" app:fabAlignmentMode="end"> - - - - - - - + android:layout_height="?attr/actionBarSize" + app:tabGravity="fill" + app:tabIndicatorGravity="top" + app:tabMode="fixed" /> @@ -132,4 +106,3 @@ android:fitsSystemWindows="true" /> - diff --git a/app/src/main/res/layout/fragment_timeline_notifications.xml b/app/src/main/res/layout/fragment_timeline_notifications.xml index 39be8fa8b..4d139b155 100644 --- a/app/src/main/res/layout/fragment_timeline_notifications.xml +++ b/app/src/main/res/layout/fragment_timeline_notifications.xml @@ -18,53 +18,10 @@ - - - - -