diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt index 770b03d345..73b870fd3a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt @@ -17,16 +17,13 @@ package im.vector.riotx.features.home import android.os.Bundle -import android.view.LayoutInflater import android.view.View import androidx.core.content.ContextCompat -import androidx.core.view.forEachIndexed import androidx.lifecycle.Observer import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState -import com.google.android.material.bottomnavigation.BottomNavigationItemView -import com.google.android.material.bottomnavigation.BottomNavigationMenuView +import com.google.android.material.badge.BadgeDrawable import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.util.toMatrixItem import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo @@ -44,10 +41,11 @@ import im.vector.riotx.features.call.VectorCallActivity import im.vector.riotx.features.call.WebRtcPeerConnectionManager import im.vector.riotx.features.home.room.list.RoomListFragment import im.vector.riotx.features.home.room.list.RoomListParams -import im.vector.riotx.features.home.room.list.UnreadCounterBadgeView import im.vector.riotx.features.popup.PopupAlertManager import im.vector.riotx.features.popup.VerificationVectorAlert +import im.vector.riotx.features.settings.VectorPreferences import im.vector.riotx.features.settings.VectorSettingsActivity.Companion.EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY_MANAGE_SESSIONS +import im.vector.riotx.features.themes.ThemeUtils import im.vector.riotx.features.workers.signout.BannerState import im.vector.riotx.features.workers.signout.ServerBackupStatusViewModel import im.vector.riotx.features.workers.signout.ServerBackupStatusViewState @@ -55,20 +53,19 @@ import kotlinx.android.synthetic.main.fragment_home_detail.* import timber.log.Timber import javax.inject.Inject -private const val INDEX_CATCHUP = 0 -private const val INDEX_PEOPLE = 1 -private const val INDEX_ROOMS = 2 +private const val INDEX_PEOPLE = 0 +private const val INDEX_ROOMS = 1 +private const val INDEX_CATCHUP = 2 class HomeDetailFragment @Inject constructor( val homeDetailViewModelFactory: HomeDetailViewModel.Factory, private val serverBackupStatusViewModelFactory: ServerBackupStatusViewModel.Factory, private val avatarRenderer: AvatarRenderer, private val alertManager: PopupAlertManager, - private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager + private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager, + private val vectorPreferences: VectorPreferences ) : VectorBaseFragment(), KeysBackupBanner.Delegate, ActiveCallView.Callback, ServerBackupStatusViewModel.Factory { - private val unreadCounterBadgeViews = arrayListOf() - private val viewModel: HomeDetailViewModel by fragmentViewModel() private val unknownDeviceDetectorSharedViewModel: UnknownDeviceDetectorSharedViewModel by activityViewModel() private val serverBackupStatusViewModel: ServerBackupStatusViewModel by activityViewModel() @@ -128,6 +125,25 @@ class HomeDetailFragment @Inject constructor( }) } + override fun onResume() { + super.onResume() + // update notification tab if needed + checkNotificationTabStatus() + } + + private fun checkNotificationTabStatus() { + val wasVisible = bottomNavigationView.menu.findItem(R.id.bottom_action_notification).isVisible + bottomNavigationView.menu.findItem(R.id.bottom_action_notification).isVisible = vectorPreferences.labAddNotificationTab() + if (wasVisible && !vectorPreferences.labAddNotificationTab()) { + // As we hide it check if it's not the current item! + withState(viewModel) { + if (it.displayMode.toMenuId() == R.id.bottom_action_notification) { + viewModel.handle(HomeDetailAction.SwitchDisplayMode(RoomListDisplayMode.PEOPLE)) + } + } + } + } + private fun promptForNewUnknownDevices(uid: String, state: UnknownDevicesState, newest: DeviceInfo) { val user = state.myMatrixItem alertManager.postVectorAlert( @@ -226,24 +242,27 @@ class HomeDetailFragment @Inject constructor( } private fun setupBottomNavigationView() { + bottomNavigationView.menu.findItem(R.id.bottom_action_notification).isVisible = vectorPreferences.labAddNotificationTab() bottomNavigationView.setOnNavigationItemSelectedListener { val displayMode = when (it.itemId) { R.id.bottom_action_people -> RoomListDisplayMode.PEOPLE R.id.bottom_action_rooms -> RoomListDisplayMode.ROOMS - else -> RoomListDisplayMode.HOME + else -> RoomListDisplayMode.NOTIFICATIONS } viewModel.handle(HomeDetailAction.SwitchDisplayMode(displayMode)) true } - val menuView = bottomNavigationView.getChildAt(0) as BottomNavigationMenuView - menuView.forEachIndexed { index, view -> - val itemView = view as BottomNavigationItemView - val badgeLayout = LayoutInflater.from(requireContext()).inflate(R.layout.vector_home_badge_unread_layout, menuView, false) - val unreadCounterBadgeView: UnreadCounterBadgeView = badgeLayout.findViewById(R.id.actionUnreadCounterBadgeView) - itemView.addView(badgeLayout) - unreadCounterBadgeViews.add(index, unreadCounterBadgeView) - } +// val menuView = bottomNavigationView.getChildAt(0) as BottomNavigationMenuView + +// bottomNavigationView.getOrCreateBadge() +// menuView.forEachIndexed { index, view -> +// val itemView = view as BottomNavigationItemView +// val badgeLayout = LayoutInflater.from(requireContext()).inflate(R.layout.vector_home_badge_unread_layout, menuView, false) +// val unreadCounterBadgeView: UnreadCounterBadgeView = badgeLayout.findViewById(R.id.actionUnreadCounterBadgeView) +// itemView.addView(badgeLayout) +// unreadCounterBadgeViews.add(index, unreadCounterBadgeView) +// } } private fun switchDisplayMode(displayMode: RoomListDisplayMode) { @@ -283,16 +302,28 @@ class HomeDetailFragment @Inject constructor( override fun invalidate() = withState(viewModel) { Timber.v(it.toString()) - unreadCounterBadgeViews[INDEX_CATCHUP].render(UnreadCounterBadgeView.State(it.notificationCountCatchup, it.notificationHighlightCatchup)) - unreadCounterBadgeViews[INDEX_PEOPLE].render(UnreadCounterBadgeView.State(it.notificationCountPeople, it.notificationHighlightPeople)) - unreadCounterBadgeViews[INDEX_ROOMS].render(UnreadCounterBadgeView.State(it.notificationCountRooms, it.notificationHighlightRooms)) + bottomNavigationView.getOrCreateBadge(R.id.bottom_action_people).render(it.notificationCountPeople, it.notificationHighlightPeople) + bottomNavigationView.getOrCreateBadge(R.id.bottom_action_rooms).render(it.notificationCountRooms, it.notificationHighlightRooms) + bottomNavigationView.getOrCreateBadge(R.id.bottom_action_notification).render(it.notificationCountCatchup, it.notificationHighlightCatchup) syncStateView.render(it.syncState) } + private fun BadgeDrawable.render(count: Int, highlight: Boolean) { + isVisible = count > 0 + number = count + maxCharacterCount = 3 + badgeTextColor = ContextCompat.getColor(requireContext(), R.color.white) + backgroundColor = if (highlight) { + ContextCompat.getColor(requireContext(), R.color.riotx_notice) + } else { + ThemeUtils.getColor(requireContext(), R.attr.riotx_unread_room_badge) + } + } + private fun RoomListDisplayMode.toMenuId() = when (this) { RoomListDisplayMode.PEOPLE -> R.id.bottom_action_people RoomListDisplayMode.ROOMS -> R.id.bottom_action_rooms - else -> R.id.bottom_action_home + else -> R.id.bottom_action_notification } override fun onTapToReturnToCall() { diff --git a/vector/src/main/java/im/vector/riotx/features/home/RoomListDisplayMode.kt b/vector/src/main/java/im/vector/riotx/features/home/RoomListDisplayMode.kt index 6d7f49750d..365eda74a8 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/RoomListDisplayMode.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/RoomListDisplayMode.kt @@ -20,7 +20,7 @@ import androidx.annotation.StringRes import im.vector.riotx.R enum class RoomListDisplayMode(@StringRes val titleRes: Int) { - HOME(R.string.bottom_action_home), + NOTIFICATIONS(R.string.bottom_action_notification), PEOPLE(R.string.bottom_action_people_x), ROOMS(R.string.bottom_action_rooms), FILTERED(/* Not used */ 0) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListDisplayModeFilter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListDisplayModeFilter.kt index 3045987d01..dd75deb8ee 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListDisplayModeFilter.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListDisplayModeFilter.kt @@ -28,9 +28,9 @@ class RoomListDisplayModeFilter(private val displayMode: RoomListDisplayMode) : return false } return when (displayMode) { - RoomListDisplayMode.HOME -> + RoomListDisplayMode.NOTIFICATIONS -> roomSummary.notificationCount > 0 || roomSummary.membership == Membership.INVITE || roomSummary.userDrafts.isNotEmpty() - RoomListDisplayMode.PEOPLE -> roomSummary.isDirect && roomSummary.membership.isActive() + RoomListDisplayMode.PEOPLE -> roomSummary.isDirect && roomSummary.membership.isActive() RoomListDisplayMode.ROOMS -> !roomSummary.isDirect && roomSummary.membership.isActive() RoomListDisplayMode.FILTERED -> roomSummary.membership == Membership.JOIN } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt index b31117f18f..2858097e24 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt @@ -138,8 +138,8 @@ class RoomListFragment @Inject constructor( private fun setupCreateRoomButton() { when (roomListParams.displayMode) { - RoomListDisplayMode.HOME -> createChatFabMenu.isVisible = true - RoomListDisplayMode.PEOPLE -> createChatRoomButton.isVisible = true + RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.isVisible = true + RoomListDisplayMode.PEOPLE -> createChatRoomButton.isVisible = true RoomListDisplayMode.ROOMS -> createGroupRoomButton.isVisible = true else -> Unit // No button in this mode } @@ -164,8 +164,8 @@ class RoomListFragment @Inject constructor( RecyclerView.SCROLL_STATE_DRAGGING, RecyclerView.SCROLL_STATE_SETTLING -> { when (roomListParams.displayMode) { - RoomListDisplayMode.HOME -> createChatFabMenu.hide() - RoomListDisplayMode.PEOPLE -> createChatRoomButton.hide() + RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.hide() + RoomListDisplayMode.PEOPLE -> createChatRoomButton.hide() RoomListDisplayMode.ROOMS -> createGroupRoomButton.hide() else -> Unit } @@ -207,8 +207,8 @@ class RoomListFragment @Inject constructor( private val showFabRunnable = Runnable { if (isAdded) { when (roomListParams.displayMode) { - RoomListDisplayMode.HOME -> createChatFabMenu.show() - RoomListDisplayMode.PEOPLE -> createChatRoomButton.show() + RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.show() + RoomListDisplayMode.PEOPLE -> createChatRoomButton.show() RoomListDisplayMode.ROOMS -> createGroupRoomButton.show() else -> Unit } @@ -258,7 +258,7 @@ class RoomListFragment @Inject constructor( roomController.update(state) // Mark all as read menu when (roomListParams.displayMode) { - RoomListDisplayMode.HOME, + RoomListDisplayMode.NOTIFICATIONS, RoomListDisplayMode.PEOPLE, RoomListDisplayMode.ROOMS -> { val newValue = state.hasUnread @@ -288,7 +288,7 @@ class RoomListFragment @Inject constructor( } .isNullOrEmpty() val emptyState = when (roomListParams.displayMode) { - RoomListDisplayMode.HOME -> { + RoomListDisplayMode.NOTIFICATIONS -> { if (hasNoRoom) { StateView.State.Empty( getString(R.string.room_list_catchup_welcome_title), diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt index 9d08a9f62f..50f4d516bf 100755 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt @@ -147,6 +147,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { private const val SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY = "SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY" // SETTINGS_LABS_HIDE_TECHNICAL_E2E_ERRORS private const val SETTINGS_LABS_MERGE_E2E_ERRORS = "SETTINGS_LABS_MERGE_E2E_ERRORS" + const val SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB = "SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB" // analytics const val SETTINGS_USE_ANALYTICS_KEY = "SETTINGS_USE_ANALYTICS_KEY" @@ -276,6 +277,10 @@ class VectorPreferences @Inject constructor(private val context: Context) { return developerMode() && defaultPrefs.getBoolean(SETTINGS_LABS_ALLOW_EXTENDED_LOGS, false) } + fun labAddNotificationTab(): Boolean { + return defaultPrefs.getBoolean(SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB, false) + } + fun failFast(): Boolean { return BuildConfig.DEBUG || (developerMode() && defaultPrefs.getBoolean(SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY, false)) } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsLabsFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsLabsFragment.kt index eeda0167a3..1ffd80a591 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsLabsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsLabsFragment.kt @@ -34,6 +34,10 @@ class VectorSettingsLabsFragment @Inject constructor( it.isChecked = vectorPreferences.labAllowedExtendedLogging() } + findPreference(VectorPreferences.SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB)?.let { + it.isChecked = vectorPreferences.labAddNotificationTab() + } + // val useCryptoPref = findPreference(VectorPreferences.SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_PREFERENCE_KEY) as SwitchPreference // val cryptoIsEnabledPref = findPreference(VectorPreferences.SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_IS_ACTIVE_PREFERENCE_KEY) diff --git a/vector/src/main/java/im/vector/riotx/features/ui/SharedPreferencesUiStateRepository.kt b/vector/src/main/java/im/vector/riotx/features/ui/SharedPreferencesUiStateRepository.kt index d1a4315cc9..ec1f8e5131 100644 --- a/vector/src/main/java/im/vector/riotx/features/ui/SharedPreferencesUiStateRepository.kt +++ b/vector/src/main/java/im/vector/riotx/features/ui/SharedPreferencesUiStateRepository.kt @@ -19,12 +19,16 @@ package im.vector.riotx.features.ui import android.content.SharedPreferences import androidx.core.content.edit import im.vector.riotx.features.home.RoomListDisplayMode +import im.vector.riotx.features.settings.VectorPreferences import javax.inject.Inject /** * This class is used to persist UI state across application restart */ -class SharedPreferencesUiStateRepository @Inject constructor(private val sharedPreferences: SharedPreferences) : UiStateRepository { +class SharedPreferencesUiStateRepository @Inject constructor( + private val sharedPreferences: SharedPreferences, + private val vectorPreferences: VectorPreferences +) : UiStateRepository { override fun reset() { sharedPreferences.edit { @@ -36,7 +40,11 @@ class SharedPreferencesUiStateRepository @Inject constructor(private val sharedP return when (sharedPreferences.getInt(KEY_DISPLAY_MODE, VALUE_DISPLAY_MODE_CATCHUP)) { VALUE_DISPLAY_MODE_PEOPLE -> RoomListDisplayMode.PEOPLE VALUE_DISPLAY_MODE_ROOMS -> RoomListDisplayMode.ROOMS - else -> RoomListDisplayMode.PEOPLE // RoomListDisplayMode.HOME + else -> if (vectorPreferences.labAddNotificationTab()) { + RoomListDisplayMode.NOTIFICATIONS + } else { + RoomListDisplayMode.PEOPLE + } } } diff --git a/vector/src/main/res/menu/home_bottom_navigation.xml b/vector/src/main/res/menu/home_bottom_navigation.xml index aaf3203fe9..805e93744a 100644 --- a/vector/src/main/res/menu/home_bottom_navigation.xml +++ b/vector/src/main/res/menu/home_bottom_navigation.xml @@ -1,13 +1,6 @@ - - + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 37a849b3fd..32de32e094 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -130,6 +130,7 @@ Home + Notifications Favourites People Rooms @@ -1760,6 +1761,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming Enable swipe to reply in timeline Merge failed to decrypt message in timeline + Add a dedicated tab for unread notifications on main screen. Link copied to clipboard diff --git a/vector/src/main/res/xml/vector_settings_labs.xml b/vector/src/main/res/xml/vector_settings_labs.xml index 9917bb0feb..2c52b2198e 100644 --- a/vector/src/main/res/xml/vector_settings_labs.xml +++ b/vector/src/main/res/xml/vector_settings_labs.xml @@ -44,6 +44,12 @@ android:defaultValue="false" android:key="SETTINGS_LABS_MERGE_E2E_ERRORS" android:title="@string/labs_merge_e2e_in_timeline" /> + + + \ No newline at end of file