diff --git a/library/ui-styles/src/main/res/values-v23/dimens.xml b/library/ui-styles/src/main/res/values-v23/dimens.xml
new file mode 100644
index 0000000000..18b8a81a7e
--- /dev/null
+++ b/library/ui-styles/src/main/res/values-v23/dimens.xml
@@ -0,0 +1,4 @@
+
+
+ 28dp
+
\ No newline at end of file
diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml
index e2e50449ce..88c3d9d6e4 100644
--- a/library/ui-styles/src/main/res/values/dimens.xml
+++ b/library/ui-styles/src/main/res/values/dimens.xml
@@ -41,4 +41,8 @@
320dp
+ 24dp
+ 48dp
+ 48dp
+
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
index 1f5550b27f..907ca360bb 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
@@ -680,17 +680,17 @@ class RoomDetailViewModel @AssistedInject constructor(
}
} else {
when (itemId) {
- R.id.timeline_setting -> true
- R.id.invite -> state.canInvite
- R.id.open_matrix_apps -> true
- R.id.voice_call -> state.isWebRTCCallOptionAvailable()
- R.id.video_call -> state.isWebRTCCallOptionAvailable() || state.jitsiState.confId == null || state.jitsiState.hasJoined
+ R.id.timeline_setting -> true
+ R.id.invite -> state.canInvite
+ R.id.open_matrix_apps -> true
+ R.id.voice_call -> state.isWebRTCCallOptionAvailable()
+ R.id.video_call -> state.isWebRTCCallOptionAvailable() || state.jitsiState.confId == null || state.jitsiState.hasJoined
// Show Join conference button only if there is an active conf id not joined. Otherwise fallback to default video disabled. ^
- R.id.join_conference -> !state.isWebRTCCallOptionAvailable() && state.jitsiState.confId != null && !state.jitsiState.hasJoined
- R.id.search -> true
- R.id.threads -> BuildConfig.THREADING_ENABLED
- R.id.dev_tools -> vectorPreferences.developerMode()
- else -> false
+ R.id.join_conference -> !state.isWebRTCCallOptionAvailable() && state.jitsiState.confId != null && !state.jitsiState.hasJoined
+ R.id.search -> true
+ R.id.menu_timeline_thread_list -> BuildConfig.THREADING_ENABLED
+ R.id.dev_tools -> vectorPreferences.developerMode()
+ else -> false
}
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
index a7db0af385..b37ba12f37 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
@@ -36,12 +36,14 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
+import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
+import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.net.toUri
import androidx.core.text.buildSpannedString
import androidx.core.text.toSpannable
@@ -61,6 +63,7 @@ import com.airbnb.epoxy.OnModelBuildFinishedListener
import com.airbnb.epoxy.addGlidePreloader
import com.airbnb.epoxy.glidePreloader
import com.airbnb.mvrx.Mavericks
+import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
@@ -133,6 +136,7 @@ import im.vector.app.features.command.Command
import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreActivity
import im.vector.app.features.crypto.verification.VerificationBottomSheet
import im.vector.app.features.home.AvatarRenderer
+import im.vector.app.features.home.UnreadMessagesSharedViewModel
import im.vector.app.features.home.room.detail.arguments.TimelineArgs
import im.vector.app.features.home.room.detail.composer.SendMode
import im.vector.app.features.home.room.detail.composer.TextComposerAction
@@ -285,6 +289,7 @@ class TimelineFragment @Inject constructor(
private val roomDetailViewModel: RoomDetailViewModel by fragmentViewModel()
private val textComposerViewModel: TextComposerViewModel by fragmentViewModel()
+
private val debouncer = Debouncer(createUIHandler())
private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback
@@ -907,6 +912,13 @@ class TimelineFragment @Inject constructor(
(joinConfItem.actionView as? JoinConferenceView)?.onJoinClicked = {
roomDetailViewModel.handle(RoomDetailAction.JoinJitsiCall)
}
+
+ // Custom thread notification menu item
+ menu.findItem(R.id.menu_timeline_thread_list)?.let { menuItem ->
+ menuItem.actionView.setOnClickListener {
+ onOptionsItemSelected(menuItem)
+ }
+ }
}
override fun onPrepareOptionsMenu(menu: Menu) {
@@ -946,6 +958,10 @@ class TimelineFragment @Inject constructor(
actionView.findViewById(R.id.cart_badge).setTextOrHide("$widgetsCount")
matrixAppsMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
}
+
+ // Handle custom threads badge notification
+ updateMenuThreadNotificationBadge(menu, state)
+
}
}
@@ -971,7 +987,7 @@ class TimelineFragment @Inject constructor(
callActionsHandler.onVideoCallClicked()
true
}
- R.id.threads -> {
+ R.id.menu_timeline_thread_list -> {
navigateToThreadList()
true
}
@@ -1007,6 +1023,29 @@ class TimelineFragment @Inject constructor(
}
}
+ /**
+ * Update menu thread notification badge appropriately
+ */
+ private fun updateMenuThreadNotificationBadge(menu: Menu, state: RoomDetailViewState) {
+ val menuThreadList = menu.findItem(R.id.menu_timeline_thread_list).actionView
+ val badgeFrameLayout = menuThreadList.findViewById(R.id.threadNotificationBadgeFrameLayout)
+ val badgeTextView = menuThreadList.findViewById(R.id.threadNotificationBadgeTextView)
+
+ val unreadThreadMessages = 18 + state.pushCounter
+
+ val userIsMentioned = true
+ if (unreadThreadMessages > 0) {
+ badgeFrameLayout.isVisible = true
+ badgeTextView.text = unreadThreadMessages.toString()
+ val badgeDrawable = DrawableCompat.wrap(badgeFrameLayout.background)
+ val color = ContextCompat.getColor(requireContext(), if (userIsMentioned) R.color.palette_vermilion else R.color.palette_gray_200)
+ DrawableCompat.setTint(badgeDrawable, color)
+ badgeFrameLayout.background = badgeDrawable
+ } else {
+ badgeFrameLayout.isVisible = false
+ }
+ }
+
/**
* View and highlight the original root thread message in the main timeline
*/
diff --git a/vector/src/main/res/drawable/notification_badge.xml b/vector/src/main/res/drawable/notification_badge.xml
new file mode 100644
index 0000000000..11f4b1d274
--- /dev/null
+++ b/vector/src/main/res/drawable/notification_badge.xml
@@ -0,0 +1,12 @@
+
+
+
+
+ -
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/layout/view_thread_notification_badge.xml b/vector/src/main/res/layout/view_thread_notification_badge.xml
new file mode 100644
index 0000000000..8e2e098d7b
--- /dev/null
+++ b/vector/src/main/res/layout/view_thread_notification_badge.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/layout/view_thread_notification_badge_old.xml b/vector/src/main/res/layout/view_thread_notification_badge_old.xml
new file mode 100644
index 0000000000..70efceda51
--- /dev/null
+++ b/vector/src/main/res/layout/view_thread_notification_badge_old.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/menu/menu_timeline.xml b/vector/src/main/res/menu/menu_timeline.xml
index 532b63dd38..6b334ab56a 100644
--- a/vector/src/main/res/menu/menu_timeline.xml
+++ b/vector/src/main/res/menu/menu_timeline.xml
@@ -38,14 +38,13 @@
tools:visible="true" />
-