Merge pull request #1666 from vector-im/feature/tab_notification_labs

Feature/tab notification labs
This commit is contained in:
Benoit Marty 2020-07-11 22:05:56 +02:00 committed by GitHub
commit 39e185576c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 101 additions and 44 deletions

View file

@ -11,6 +11,7 @@ Improvements 🙌:
- Improve fullscreen media display (#327)
- Setup server recovery banner (#1648)
- Set up SSSS from security settings (#1567)
- New lab setting to add 'unread notifications' tab to main screen
Bugfix 🐛:
- Integration Manager: Wrong URL to review terms if URL in config contains path (#1606)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -34,6 +34,10 @@ class VectorSettingsLabsFragment @Inject constructor(
it.isChecked = vectorPreferences.labAllowedExtendedLogging()
}
findPreference<VectorSwitchPreference>(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)

View file

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

View file

@ -1,13 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/bottom_action_home"
android:enabled="true"
android:icon="@drawable/ic_home_bottom_catchup"
android:title="@string/bottom_action_home"
android:visible="false" />
<item
android:id="@+id/bottom_action_people"
android:enabled="true"
@ -20,4 +13,11 @@
android:icon="@drawable/ic_home_bottom_group"
android:title="@string/bottom_action_rooms" />
<item
android:id="@+id/bottom_action_notification"
android:enabled="true"
android:icon="@drawable/ic_bell"
android:title="@string/bottom_action_notification"
android:visible="false" />
</menu>

View file

@ -130,6 +130,7 @@
<!-- Bottom navigation buttons -->
<string name="bottom_action_home">Home</string>
<string name="bottom_action_notification">Notifications</string>
<string name="bottom_action_favourites">Favourites</string>
<string name="bottom_action_people">People</string>
<string name="bottom_action_rooms">Rooms</string>
@ -1760,6 +1761,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming
<string name="labs_swipe_to_reply_in_timeline">Enable swipe to reply in timeline</string>
<string name="labs_merge_e2e_in_timeline">Merge failed to decrypt message in timeline</string>
<string name="labs_show_unread_notifications_as_tab">Add a dedicated tab for unread notifications on main screen.</string>
<string name="link_copied_to_clipboard">Link copied to clipboard</string>

View file

@ -44,6 +44,12 @@
android:defaultValue="false"
android:key="SETTINGS_LABS_MERGE_E2E_ERRORS"
android:title="@string/labs_merge_e2e_in_timeline" />
<im.vector.riotx.core.preference.VectorSwitchPreference
android:defaultValue="false"
android:key="SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB"
android:title="@string/labs_show_unread_notifications_as_tab" />
<!--</im.vector.riotx.core.preference.VectorPreferenceCategory>-->
</androidx.preference.PreferenceScreen>