Threads notification badge UI

This commit is contained in:
ariskotsomitopoulos 2021-12-03 11:30:30 +00:00
parent 0241d66f8e
commit d1bb96cec0
8 changed files with 183 additions and 14 deletions

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="menu_item_ripple_size">28dp</dimen>
</resources>

View file

@ -41,4 +41,8 @@
<!-- Navigation Drawer --> <!-- Navigation Drawer -->
<dimen name="navigation_drawer_max_width">320dp</dimen> <dimen name="navigation_drawer_max_width">320dp</dimen>
<dimen name="menu_item_icon_size">24dp</dimen>
<dimen name="menu_item_size">48dp</dimen>
<dimen name="menu_item_ripple_size">48dp</dimen>
</resources> </resources>

View file

@ -680,17 +680,17 @@ class RoomDetailViewModel @AssistedInject constructor(
} }
} else { } else {
when (itemId) { when (itemId) {
R.id.timeline_setting -> true R.id.timeline_setting -> true
R.id.invite -> state.canInvite R.id.invite -> state.canInvite
R.id.open_matrix_apps -> true R.id.open_matrix_apps -> true
R.id.voice_call -> state.isWebRTCCallOptionAvailable() R.id.voice_call -> state.isWebRTCCallOptionAvailable()
R.id.video_call -> state.isWebRTCCallOptionAvailable() || state.jitsiState.confId == null || state.jitsiState.hasJoined 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. ^ // 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.join_conference -> !state.isWebRTCCallOptionAvailable() && state.jitsiState.confId != null && !state.jitsiState.hasJoined
R.id.search -> true R.id.search -> true
R.id.threads -> BuildConfig.THREADING_ENABLED R.id.menu_timeline_thread_list -> BuildConfig.THREADING_ENABLED
R.id.dev_tools -> vectorPreferences.developerMode() R.id.dev_tools -> vectorPreferences.developerMode()
else -> false else -> false
} }
} }
} }

View file

@ -36,12 +36,14 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.FrameLayout
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.text.buildSpannedString import androidx.core.text.buildSpannedString
import androidx.core.text.toSpannable import androidx.core.text.toSpannable
@ -61,6 +63,7 @@ import com.airbnb.epoxy.OnModelBuildFinishedListener
import com.airbnb.epoxy.addGlidePreloader import com.airbnb.epoxy.addGlidePreloader
import com.airbnb.epoxy.glidePreloader import com.airbnb.epoxy.glidePreloader
import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.Mavericks
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.args import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState 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.keysbackup.restore.KeysBackupRestoreActivity
import im.vector.app.features.crypto.verification.VerificationBottomSheet import im.vector.app.features.crypto.verification.VerificationBottomSheet
import im.vector.app.features.home.AvatarRenderer 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.arguments.TimelineArgs
import im.vector.app.features.home.room.detail.composer.SendMode import im.vector.app.features.home.room.detail.composer.SendMode
import im.vector.app.features.home.room.detail.composer.TextComposerAction 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 roomDetailViewModel: RoomDetailViewModel by fragmentViewModel()
private val textComposerViewModel: TextComposerViewModel by fragmentViewModel() private val textComposerViewModel: TextComposerViewModel by fragmentViewModel()
private val debouncer = Debouncer(createUIHandler()) private val debouncer = Debouncer(createUIHandler())
private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback
@ -907,6 +912,13 @@ class TimelineFragment @Inject constructor(
(joinConfItem.actionView as? JoinConferenceView)?.onJoinClicked = { (joinConfItem.actionView as? JoinConferenceView)?.onJoinClicked = {
roomDetailViewModel.handle(RoomDetailAction.JoinJitsiCall) 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) { override fun onPrepareOptionsMenu(menu: Menu) {
@ -946,6 +958,10 @@ class TimelineFragment @Inject constructor(
actionView.findViewById<TextView>(R.id.cart_badge).setTextOrHide("$widgetsCount") actionView.findViewById<TextView>(R.id.cart_badge).setTextOrHide("$widgetsCount")
matrixAppsMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS) 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() callActionsHandler.onVideoCallClicked()
true true
} }
R.id.threads -> { R.id.menu_timeline_thread_list -> {
navigateToThreadList() navigateToThreadList()
true 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<FrameLayout>(R.id.threadNotificationBadgeFrameLayout)
val badgeTextView = menuThreadList.findViewById<TextView>(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 * View and highlight the original root thread message in the main timeline
*/ */

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- "background shadow" -->
<!-- background color -->
<item>
<shape android:shape="rectangle">
<corners android:radius="15dp"/>
<solid android:color="?vctr_content_secondary"/>
</shape>
</item>
</layer-list>

View file

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="@dimen/menu_item_size"
android:layout_height="@dimen/menu_item_size">
<FrameLayout
android:layout_width="@dimen/menu_item_ripple_size"
android:layout_height="@dimen/menu_item_ripple_size"
android:layout_gravity="center"
android:background="?attr/selectableItemBackgroundBorderless"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:layout_width="@dimen/menu_item_icon_size"
android:layout_height="@dimen/menu_item_icon_size"
android:layout_gravity="center"
android:src="@drawable/ic_thread_menu_item"
tools:ignore="ContentDescription"
app:tint="?colorPrimary" />
<FrameLayout
android:id="@+id/threadNotificationBadgeFrameLayout"
android:layout_width="wrap_content"
android:layout_height="15dp"
android:minWidth="15dp"
android:paddingStart="2dp"
android:paddingEnd="2dp"
android:layout_gravity="top|end"
android:layout_marginTop="1dp"
android:backgroundTint="@color/palette_gray_200"
android:background="@drawable/notification_badge"
android:visibility="visible"
tools:visibility="visible">
<TextView
android:id="@+id/threadNotificationBadgeTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingEnd="2dp"
android:paddingStart="2dp"
android:gravity="center"
android:layout_gravity="center"
android:textColor="@color/palette_white"
android:maxLength="3"
android:textStyle="bold"
android:textSize="10sp"
tools:text="99" />
</FrameLayout>
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/notificationConstraintLayout"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/notificationImageView"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:src="@drawable/ic_thread_menu_item" />
<ImageView
android:id="@+id/notificationBadgeImageView"
app:layout_constraintEnd_toEndOf="@+id/notificationImageView"
app:layout_constraintTop_toTopOf="@+id/notificationImageView"
android:layout_width="15dp"
android:layout_height="15dp"
android:src="@drawable/notification_badge"
android:visibility="gone"
tools:ignore="ContentDescription"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/notificationBadgeTextView"
style="@style/Widget.Vector.TextView.Subtitle"
app:layout_constraintBottom_toBottomOf="@+id/notificationBadgeImageView"
app:layout_constraintEnd_toEndOf="@+id/notificationBadgeImageView"
app:layout_constraintStart_toStartOf="@+id/notificationBadgeImageView"
app:layout_constraintTop_toTopOf="@+id/notificationBadgeImageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="11sp"
android:visibility="gone"
tools:text="8"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -38,14 +38,13 @@
tools:visible="true" /> tools:visible="true" />
<item <item
android:id="@+id/threads" android:id="@+id/menu_timeline_thread_list"
android:icon="@drawable/ic_thread_menu_item"
android:title="@string/action_view_threads" android:title="@string/action_view_threads"
android:visible="false" android:visible="false"
app:iconTint="?colorPrimary" app:iconTint="?colorPrimary"
app:actionLayout="@layout/view_thread_notification_badge"
app:showAsAction="always" app:showAsAction="always"
tools:visible="true" /> tools:visible="true" />
<item <item
android:id="@+id/join_conference" android:id="@+id/join_conference"
android:title="@string/join" android:title="@string/join"