mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 10:25:35 +03:00
Merge pull request #1666 from vector-im/feature/tab_notification_labs
Feature/tab notification labs
This commit is contained in:
commit
39e185576c
11 changed files with 101 additions and 44 deletions
|
@ -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)
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
Loading…
Reference in a new issue