Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions library/ui-strings/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2717,6 +2717,8 @@
<string name="review_unverified_sessions_description">Review to ensure your account is safe</string>
<!-- Argument will be replaced by the other session name (e.g, Desktop, mobile) -->
<string name="verify_this_session">Verify the new login accessing your account: %1$s</string>
<string name="verify_this_session_content">As of April 2026 unverified devices will not be able to send and receive messages.</string>
<string name="verify_now">Verify now</string>

<string name="cross_signing_verify_by_text">Manually Verify by Text</string>
<!-- TODO TO BE REMOVED -->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2026 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/

package im.vector.app.core.ui.views

import android.content.Context
import android.util.AttributeSet
import androidx.constraintlayout.widget.ConstraintLayout
import im.vector.app.R
import im.vector.app.databinding.ViewVerifySessionBannerBinding

class VerifyDeviceBanner @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
var delegate: Delegate? = null

init {
setupView()
}

private fun setupView() {
inflate(context, R.layout.view_verify_session_banner, this)
val views = ViewVerifySessionBannerBinding.bind(this)
views.learnMore.setOnClickListener { delegate?.onVerificationLearnMoreClicked() }
views.verifyButton.setOnClickListener { delegate?.onVerifySession() }
}

interface Delegate {
fun onVerificationLearnMoreClicked()
fun onVerifySession()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -362,11 +362,13 @@ class HomeActivityViewModel @AssistedInject constructor(
// cross signing keys have been reset
// Trigger a popup to re-verify
// Note: user can be unknown in case of logout
session.getUserOrDefault(session.myUserId)
.toMatrixItem()
.let { user ->
_viewEvents.post(HomeActivityViewEvents.OnCrossSignedInvalidated(user))
}

// This is now handled by the banner in the HomeFragment
// session.getUserOrDefault(session.myUserId)
// .toMatrixItem()
// .let { user ->
// _viewEvents.post(HomeActivityViewEvents.OnCrossSignedInvalidated(user))
// }
}
}

Expand Down Expand Up @@ -441,12 +443,13 @@ class HomeActivityViewModel @AssistedInject constructor(
val is4Ssetup = session.sharedSecretStorageService().isRecoverySetup()
if (hasTargetDeviceToVerifyAgainst || is4Ssetup) {
// New session
_viewEvents.post(
HomeActivityViewEvents.CurrentSessionNotVerified(
session.getUserOrDefault(session.myUserId).toMatrixItem(),
vectorPreferences.isOnRustCrypto() && vectorPreferences.hadExistingLegacyData()
)
)
// This is now handled by the banner in the HomeFragment
// _viewEvents.post(
// HomeActivityViewEvents.CurrentSessionNotVerified(
// session.getUserOrDefault(session.myUserId).toMatrixItem(),
// vectorPreferences.isOnRustCrypto() && vectorPreferences.hadExistingLegacyData()
// )
// )
} else {
_viewEvents.post(
HomeActivityViewEvents.CurrentSessionCannotBeVerified(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class HomeDetailViewModel @AssistedInject constructor(
observeRoomSummaries()
updatePstnSupportFlag()
observeDataStore()
observeCrossSigningState()
callManager.addProtocolsCheckerListener(this)
session.flow().liveUser(session.myUserId).execute {
copy(
Expand All @@ -101,6 +102,21 @@ class HomeDetailViewModel @AssistedInject constructor(
}
}

private fun observeCrossSigningState() {
session
.flow()
.liveCrossSigningInfo(session.myUserId)
.onEach { info ->
val isVerified = info.getOrNull()?.isTrusted() ?: false
setState {
copy(
isSessionVerified = isVerified,
)
}
}
.launchIn(viewModelScope)
}

private fun observeDataStore() {
vectorDataStore.pushCounterFlow.setOnEach { nbOfPush ->
copy(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ data class HomeDetailViewState(
val incrementalSyncRequestState: SyncRequestState.IncrementalSyncRequestState? = null,
val pushCounter: Int = 0,
val pstnSupportFlag: Boolean = false,
val forceDialPadTab: Boolean = false
val forceDialPadTab: Boolean = false,
// Set to true by default to avoid glitch
val isSessionVerified: Boolean = true,
) : MavericksState {
val showDialPadTab = forceDialPadTab || pstnSupportFlag
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.activityViewModel
Expand All @@ -33,6 +34,8 @@ import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.ui.views.CurrentCallsView
import im.vector.app.core.ui.views.CurrentCallsViewPresenter
import im.vector.app.core.ui.views.KeysBackupBanner
import im.vector.app.core.ui.views.VerifyDeviceBanner
import im.vector.app.core.utils.openUrlInChromeCustomTab
import im.vector.app.databinding.FragmentNewHomeDetailBinding
import im.vector.app.features.call.SharedKnownCallsViewModel
import im.vector.app.features.call.VectorCallActivity
Expand Down Expand Up @@ -66,7 +69,8 @@ class NewHomeDetailFragment :
KeysBackupBanner.Delegate,
CurrentCallsView.Callback,
OnBackPressed,
VectorMenuProvider {
VectorMenuProvider,
VerifyDeviceBanner.Delegate {

@Inject lateinit var avatarRenderer: AvatarRenderer
@Inject lateinit var colorProvider: ColorProvider
Expand Down Expand Up @@ -132,6 +136,7 @@ class NewHomeDetailFragment :
sharedCallActionViewModel = activityViewModelProvider.get(SharedKnownCallsViewModel::class.java)
setupToolbar()
setupKeysBackupBanner()
setupVerificationBanner()
setupActiveCallView()
setupDebugButton()
setupFabs()
Expand Down Expand Up @@ -323,6 +328,10 @@ class NewHomeDetailFragment :
views.homeKeysBackupBanner.delegate = this
}

private fun setupVerificationBanner() {
views.homeVerifyDevice.delegate = this
}

private fun setupActiveCallView() {
currentCallsViewPresenter.bind(views.currentCallsView, this)
}
Expand Down Expand Up @@ -355,9 +364,9 @@ class NewHomeDetailFragment :
})
}

/* ==========================================================================================
* KeysBackupBanner Listener
* ========================================================================================== */
/* ==========================================================================================
* KeysBackupBanner Listener
* ========================================================================================== */

override fun onCloseClicked() {
serverBackupStatusViewModel.handle(ServerBackupStatusAction.OnBannerClosed)
Expand All @@ -378,7 +387,7 @@ class NewHomeDetailFragment :
it.pushCounter,
vectorPreferences.developerShowDebugInfo()
)

views.homeVerifyDevice.isGone = it.isSessionVerified
refreshAvatar()
hasUnreadRooms = it.hasUnreadMessages
}
Expand Down Expand Up @@ -419,6 +428,21 @@ class NewHomeDetailFragment :

private fun SpaceStateHandler.isRoot() = getSpaceBackstack().isEmpty()

override fun onVerificationLearnMoreClicked() {
openUrlInChromeCustomTab(
context = requireContext(),
session = null,
url = "https://docs.element.io/latest/element-support/device-verification/how-to-verify-devices",
)
}

override fun onVerifySession() {
(activity as? VectorBaseActivity<*>)?.let { vectorBaseActivity ->
vectorBaseActivity.navigator
.requestSelfSessionVerification(vectorBaseActivity)
}
}

companion object {
private const val HOME_ROOM_LIST_FRAGMENT_TAG = "TAG_HOME_ROOM_LIST"
}
Expand Down
9 changes: 9 additions & 0 deletions vector/src/main/res/drawable/banner_background.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">

<solid android:color="?vctr_content_quinary" />

<corners android:radius="12dp" />

</shape>
28 changes: 20 additions & 8 deletions vector/src/main/res/layout/fragment_new_home_detail.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,11 @@
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/syncStateView">
android:layout_height="wrap_content">

<com.google.android.material.appbar.CollapsingToolbarLayout
style="?attr/collapsingToolbarLayoutMediumStyle"
android:id="@+id/collapsing_toolbar"
style="?attr/collapsingToolbarLayoutMediumStyle"
android:layout_width="match_parent"
android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
Expand Down Expand Up @@ -89,13 +88,26 @@

</com.google.android.material.appbar.AppBarLayout>

<androidx.fragment.app.FragmentContainerView
android:id="@+id/roomListContainer"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent" />
android:orientation="vertical"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">

<im.vector.app.core.ui.views.VerifyDeviceBanner
android:id="@+id/homeVerifyDevice"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:visibility="gone"
tools:visibility="visible" />

<androidx.fragment.app.FragmentContainerView
android:id="@+id/roomListContainer"
android:layout_width="match_parent"
android:layout_height="match_parent" />

</LinearLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

Expand Down
62 changes: 62 additions & 0 deletions vector/src/main/res/layout/view_verify_session_banner.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<merge 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="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/banner_background"
android:padding="16dp">

<TextView
android:id="@+id/title"
style="@style/Widget.Vector.TextView.HeadlineMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/crosssigning_verify_this_session"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/content"
style="@style/Widget.Vector.TextView.Body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@string/verify_this_session_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title" />

<TextView
android:id="@+id/learn_more"
style="@style/Widget.Vector.TextView.Subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@string/action_learn_more"
android:textColor="?android:textColorPrimary"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/content" />

<Button
android:id="@+id/verify_button"
style="@style/Widget.Vector.Button.Positive"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/verify_now"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/learn_more" />

</androidx.constraintlayout.widget.ConstraintLayout>

</merge>
Loading