mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-22 17:35:54 +03:00
Threads notification badge UI
This commit is contained in:
parent
0241d66f8e
commit
d1bb96cec0
8 changed files with 183 additions and 14 deletions
4
library/ui-styles/src/main/res/values-v23/dimens.xml
Normal file
4
library/ui-styles/src/main/res/values-v23/dimens.xml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<dimen name="menu_item_ripple_size">28dp</dimen>
|
||||||
|
</resources>
|
|
@ -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>
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
12
vector/src/main/res/drawable/notification_badge.xml
Normal file
12
vector/src/main/res/drawable/notification_badge.xml
Normal 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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in a new issue