From 135bd22588f363e9a6fa7fda4e4cf3f941609e1c Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 10 Jun 2025 16:24:40 +0200 Subject: [PATCH 01/46] version++ --- matrix-sdk-android/build.gradle | 2 +- vector-app/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index ce4f195637..e2014e3bfd 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -62,7 +62,7 @@ android { // that the app's state is completely cleared between tests. testInstrumentationRunnerArguments clearPackageData: 'true' - buildConfigField "String", "SDK_VERSION", "\"1.6.42\"" + buildConfigField "String", "SDK_VERSION", "\"1.6.44\"" buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\"" diff --git a/vector-app/build.gradle b/vector-app/build.gradle index 895924a712..4faeae495d 100644 --- a/vector-app/build.gradle +++ b/vector-app/build.gradle @@ -37,7 +37,7 @@ ext.versionMinor = 6 // Note: even values are reserved for regular release, odd values for hotfix release. // When creating a hotfix, you should decrease the value, since the current value // is the value for the next regular release. -ext.versionPatch = 42 +ext.versionPatch = 44 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' From 8a9a69906276995664e8ddb83cbbe7ac99a201c1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 7 Jul 2025 17:30:36 +0200 Subject: [PATCH 02/46] Set compileSdk and targetSdk to 35 --- dependencies.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dependencies.gradle b/dependencies.gradle index a0ac311963..ac560cf8ee 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,7 +1,7 @@ ext.versions = [ 'minSdk' : 21, - 'compileSdk' : 34, - 'targetSdk' : 34, + 'compileSdk' : 35, + 'targetSdk' : 35, 'sourceCompat' : JavaVersion.VERSION_21, 'targetCompat' : JavaVersion.VERSION_21, 'jvmTarget' : "21", From a283eea620f12c46782d4d1725383467dfad8b88 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 7 Jul 2025 17:50:53 +0200 Subject: [PATCH 03/46] Fix compilation issue. --- .../VectorActivityLifecycleCallbacks.kt | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt b/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt index fcb0dc22a8..968c422f43 100644 --- a/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt +++ b/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt @@ -32,7 +32,7 @@ class VectorActivityLifecycleCallbacks constructor(private val popupAlertManager /** * The activities information collected from the app manifest. */ - private var activitiesInfo: Array = emptyArray() + private var activitiesInfo: List? = null private val coroutineScope = CoroutineScope(SupervisorJob()) @@ -51,24 +51,32 @@ class VectorActivityLifecycleCallbacks constructor(private val popupAlertManager override fun onActivityStopped(activity: Activity) {} override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { - if (activitiesInfo.isEmpty()) { + if (activitiesInfo == null) { val context = activity.applicationContext val packageManager: PackageManager = context.packageManager // Get all activities from element android - activitiesInfo = packageManager.getPackageInfoCompat(context.packageName, PackageManager.GET_ACTIVITIES).activities - + val activities = packageManager + .getPackageInfoCompat(context.packageName, PackageManager.GET_ACTIVITIES) + .activities + .orEmpty() + .toList() // Get all activities from PermissionController module // See https://source.android.com/docs/core/architecture/modular-system/permissioncontroller#package-format - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.S_V2) { - activitiesInfo += tryOrNull { + val otherActivities = if (Build.VERSION.SDK_INT > Build.VERSION_CODES.S_V2) { + (tryOrNull { packageManager.getPackageInfoCompat("com.google.android.permissioncontroller", PackageManager.GET_ACTIVITIES).activities } ?: tryOrNull { packageManager.getModuleInfo("com.google.android.permission", 1).packageName?.let { packageManager.getPackageInfoCompat(it, PackageManager.GET_ACTIVITIES or PackageManager.MATCH_APEX).activities } - }.orEmpty() + }) + .orEmpty() + .toList() + } else { + emptyList() } + activitiesInfo = activities + otherActivities } // restart the app if the task contains an unknown activity @@ -144,5 +152,5 @@ class VectorActivityLifecycleCallbacks constructor(private val popupAlertManager * @param activity the activity of the task * @return true if the activity is potentially malicious */ - private fun isPotentialMaliciousActivity(activity: ComponentName): Boolean = activitiesInfo.none { it.name == activity.className } + private fun isPotentialMaliciousActivity(activity: ComponentName): Boolean = activitiesInfo.orEmpty().none { it.name == activity.className } } From 3652ae5a90ffd5ba92970c558a5c4e30b0d40a69 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Jul 2025 14:35:18 +0200 Subject: [PATCH 04/46] Upgrade Android Gradle Plugin to 8.11.0 --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index ac560cf8ee..0fd445b543 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -7,7 +7,7 @@ ext.versions = [ 'jvmTarget' : "21", ] -def gradle = "8.4.2" +def gradle = "8.11.0" // Ref: https://kotlinlang.org/releases.html def kotlin = "1.9.24" def kotlinCoroutines = "1.8.1" From 390ae5622d147b56a599aac5b0af5a114495245a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Jul 2025 14:41:35 +0200 Subject: [PATCH 05/46] Update gradle wraaper using command line ./gradlew wrapper --gradle-version 8.14.3 --gradle-distribution-sha256-sum bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531 --- gradle/wrapper/gradle-wrapper.properties | 4 +-- gradlew | 31 ++++++++++++++---------- gradlew.bat | 20 +++++++-------- 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 515ab9d5f1..78cb6e16a4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=f8b4f4772d302c8ff580bc40d0f56e715de69b163546944f787c87abf209c961 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-all.zip +distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 65dcd68d65..b740cf1339 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -83,10 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 6689b85bee..7101f8e467 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail From da983b4cf564191dc124fff85dfbd06b146adb2e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Jul 2025 14:42:57 +0200 Subject: [PATCH 06/46] Update lint version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 8cde2fc687..2433ae78cb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -42,4 +42,4 @@ signing.element.nightly.keyPassword=Secret # Customise the Lint version to use a more recent version than the one bundled with AGP # https://googlesamples.github.io/android-custom-lint-rules/usage/newer-lint.md.html -android.experimental.lint.version=8.6.0-alpha08 +android.experimental.lint.version=8.12.0-alpha08 From 06721da575d5e6738bddce3c673dbcf5cdaa5a0a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Jul 2025 14:43:51 +0200 Subject: [PATCH 07/46] Update gradle wraaper using command line ./gradlew wrapper --gradle-version 8.14.3 --gradle-distribution-sha256-sum bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531 --- gradle/wrapper/gradle-wrapper.jar | Bin 43453 -> 43764 bytes gradlew | 10 ++++++---- gradlew.bat | 6 ++++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e6441136f3d4ba8a0da8d277868979cfbc8ad796..1b33c55baabb587c669f562ae36f953de2481846 100644 GIT binary patch delta 35073 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 delta 34912 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

Gn4F+B~IivciU9^t0j-Mgao3 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 dDuazZG^u*-) zCV{{p$D*4m=Z;!QJ0C3EBfaUv#@3CmRgS-#a4+Uk9LtR(mzdSt-?*B-Q{Jgs!F;+_ zT&Jc~{N951OskjVtnWOLnfUacmSEkQ#fQojqa=HN?aN#ubFA){K&TrfoSnBphJ<43S zS5?OUrqPYpEqf;(zc;IIty0d`-n-Ke%PlArJt^DN>ZYW)ZS5z$Rnx=cxbM1#zPRNS1N0u>c z*gpBeTvyKAjdr#Uz%$J3C+p2~l9`-P6`=_1Y83#nAp`Kd5jfb*H#u;=%w#=&g~|Q% z1!N9S7n+#|tj#t98*^$X3S9)i3I*n?N+YghXJ9Z!QN%wvV7|W$;;Lx|1{)Ol%c4L- zlz|txtAK4ozLL`xMPZR7Sm6grRj@+Db&BXF9#sG|gMywm=IO1O01imHv3 zFjX9ri>su;HX}wK85k5$R0A&?fdtA5_Q_vX$jc(elo%M~QB>A-K~#2igOjH$d^80Z zC8#NjzZarXrcVZJJEF73z+ix4=7c`5N>TJt7>~rfl$^E}UAMlP2$P)vS26{cE?ONf_&Vd@-E2?PMU+S~pB diff --git a/gradlew b/gradlew index b740cf1339..23d15a9367 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 7101f8e467..5eed7ee845 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -68,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell From 78e100d9a92ba0c1a2871d791cb2c32b2949cef2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Jul 2025 15:03:59 +0200 Subject: [PATCH 08/46] Supress deprecation warning. --- .../lib/attachmentviewer/AttachmentViewerActivity.kt | 8 ++++++++ .../im/vector/app/core/platform/VectorBaseActivity.kt | 5 +++++ .../attachments/preview/AttachmentsPreviewFragment.kt | 1 + .../im/vector/app/features/call/VectorCallActivity.kt | 2 ++ .../vector/app/features/call/dialpad/DialPadFragment.kt | 4 ++-- .../app/features/crypto/recover/BootstrapBottomSheet.kt | 1 + .../app/features/home/room/detail/RoomDetailActivity.kt | 1 + .../app/features/media/VectorAttachmentViewerActivity.kt | 2 ++ 8 files changed, 22 insertions(+), 2 deletions(-) diff --git a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentViewerActivity.kt b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentViewerActivity.kt index 09785e50e7..55a3b5d511 100644 --- a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentViewerActivity.kt +++ b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentViewerActivity.kt @@ -131,12 +131,15 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi // the touch coordinates if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // New API instead of SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN and SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + @Suppress("DEPRECATION") window.setDecorFitsSystemWindows(false) // New API instead of SYSTEM_UI_FLAG_IMMERSIVE window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE // New API instead of FLAG_TRANSLUCENT_STATUS + @Suppress("DEPRECATION") window.statusBarColor = ContextCompat.getColor(this, R.color.half_transparent_status_bar) // new API instead of FLAG_TRANSLUCENT_NAVIGATION + @Suppress("DEPRECATION") window.navigationBarColor = ContextCompat.getColor(this, R.color.half_transparent_status_bar) } else { @Suppress("DEPRECATION") @@ -318,6 +321,7 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi protected open fun shouldAnimateDismiss(): Boolean = true protected open fun animateClose() { + @Suppress("DEPRECATION") window.statusBarColor = Color.TRANSPARENT finish() } @@ -334,14 +338,17 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi // Or for "sticky immersive," replace it with SYSTEM_UI_FLAG_IMMERSIVE_STICKY if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // New API instead of SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN and SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + @Suppress("DEPRECATION") window.setDecorFitsSystemWindows(false) // new API instead of SYSTEM_UI_FLAG_HIDE_NAVIGATION window.decorView.windowInsetsController?.hide(WindowInsets.Type.navigationBars()) // New API instead of SYSTEM_UI_FLAG_IMMERSIVE window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE // New API instead of FLAG_TRANSLUCENT_STATUS + @Suppress("DEPRECATION") window.statusBarColor = ContextCompat.getColor(this, R.color.half_transparent_status_bar) // New API instead of FLAG_TRANSLUCENT_NAVIGATION + @Suppress("DEPRECATION") window.navigationBarColor = ContextCompat.getColor(this, R.color.half_transparent_status_bar) } else { @Suppress("DEPRECATION") @@ -363,6 +370,7 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi systemUiVisibility = true if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // New API instead of SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN and SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + @Suppress("DEPRECATION") window.setDecorFitsSystemWindows(false) } else { @Suppress("DEPRECATION") diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt index 355a164ab0..0d1bb6eb0d 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt @@ -247,7 +247,9 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver if (vectorPreferences.isNewAppLayoutEnabled()) { tryOrNull { // Add to XML theme when feature flag is removed val toolbarBackground = MaterialColors.getColor(views.root, im.vector.lib.ui.styles.R.attr.vctr_toolbar_background) + @Suppress("DEPRECATION") window.statusBarColor = toolbarBackground + @Suppress("DEPRECATION") window.navigationBarColor = toolbarBackground } } @@ -467,12 +469,15 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver private fun setFullScreen() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // New API instead of SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN and SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + @Suppress("DEPRECATION") window.setDecorFitsSystemWindows(false) // New API instead of SYSTEM_UI_FLAG_IMMERSIVE window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE // New API instead of FLAG_TRANSLUCENT_STATUS + @Suppress("DEPRECATION") window.statusBarColor = ContextCompat.getColor(this, im.vector.lib.ui.styles.R.color.half_transparent_status_bar) // New API instead of FLAG_TRANSLUCENT_NAVIGATION + @Suppress("DEPRECATION") window.navigationBarColor = ContextCompat.getColor(this, im.vector.lib.ui.styles.R.color.half_transparent_status_bar) } else { @Suppress("DEPRECATION") diff --git a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewFragment.kt b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewFragment.kt index 999b76295d..a4f1dafda1 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewFragment.kt @@ -163,6 +163,7 @@ class AttachmentsPreviewFragment : private fun applyInsets() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + @Suppress("DEPRECATION") activity?.window?.setDecorFitsSystemWindows(false) } else { @Suppress("DEPRECATION") diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt index 47db3dccc7..86ecfba6b6 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt @@ -128,7 +128,9 @@ class VectorCallActivity : window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + @Suppress("DEPRECATION") window.statusBarColor = Color.TRANSPARENT + @Suppress("DEPRECATION") window.navigationBarColor = Color.BLACK super.onCreate(savedInstanceState) addOnPictureInPictureModeChangedListener(pictureInPictureModeChangedInfoConsumer) diff --git a/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadFragment.kt b/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadFragment.kt index 7efe7b8df9..5bb1b5d0dd 100644 --- a/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadFragment.kt +++ b/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadFragment.kt @@ -12,7 +12,6 @@ import android.content.ClipboardManager import android.content.Context import android.content.res.ColorStateList import android.os.Bundle -import android.telephony.PhoneNumberFormattingTextWatcher import android.telephony.PhoneNumberUtils import android.text.Editable import android.text.InputType @@ -78,7 +77,8 @@ class DialPadFragment : Fragment(), TextWatcher { digits.inputType = InputType.TYPE_CLASS_PHONE digits.keyListener = DialerKeyListener.getInstance() digits.setTextColor(ThemeUtils.getColor(requireContext(), im.vector.lib.ui.styles.R.attr.vctr_content_primary)) - digits.addTextChangedListener(PhoneNumberFormattingTextWatcher(if (formatAsYouType) regionCode else "")) + @Suppress("DEPRECATION") + digits.addTextChangedListener(android.telephony.PhoneNumberFormattingTextWatcher(if (formatAsYouType) regionCode else "")) digits.addTextChangedListener(this) dialpadView.findViewById(R.id.zero).setOnClickListener { keyPressed(KeyEvent.KEYCODE_0, "0") } dialpadView.findViewById(R.id.one).setOnClickListener { keyPressed(KeyEvent.KEYCODE_1, "1") } diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt index b1072576bc..8a29bdd9e8 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt @@ -122,6 +122,7 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment= Build.VERSION_CODES.R) { + @Suppress("DEPRECATION") dialog?.window?.setDecorFitsSystemWindows(false) } else { @Suppress("DEPRECATION") diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt index e65e6c17f0..fe185eca79 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt @@ -94,6 +94,7 @@ class RoomDetailActivity : // For dealing with insets and status bar background color WindowCompat.setDecorFitsSystemWindows(window, false) + @Suppress("DEPRECATION") window.statusBarColor = Color.TRANSPARENT supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, false) diff --git a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt index 058372feff..e6e2209e42 100644 --- a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt @@ -138,7 +138,9 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), AttachmentInt } } + @Suppress("DEPRECATION") window.statusBarColor = ContextCompat.getColor(this, im.vector.lib.ui.styles.R.color.black_alpha) + @Suppress("DEPRECATION") window.navigationBarColor = ContextCompat.getColor(this, im.vector.lib.ui.styles.R.color.black_alpha) observeViewEvents() From 6e56c386dbd3b6d953df206e804999f07644f5e3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 9 Jul 2025 09:59:32 +0200 Subject: [PATCH 09/46] Fix lint warning --- vector/src/main/AndroidManifest.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index fab5be1257..ce1b2537f1 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -369,8 +369,8 @@ + android:exported="false" + android:foregroundServiceType="phoneCall"> @@ -387,7 +387,8 @@ + android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" + tools:targetApi="M"> @@ -413,8 +414,7 @@ android:name=".features.call.audio.MicrophoneAccessService" android:exported="false" android:foregroundServiceType="microphone" - android:permission="android.permission.FOREGROUND_SERVICE_MICROPHONE"> - + android:permission="android.permission.FOREGROUND_SERVICE_MICROPHONE" /> From 27fc09bb8a4dd7d1e6572d0eb56e19610d4a8fbb Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 9 Jul 2025 10:01:03 +0200 Subject: [PATCH 10/46] Fix lint warning --- vector/src/main/java/im/vector/app/SpaceStateHandlerImpl.kt | 2 +- .../app/features/pin/lockscreen/views/LockScreenCodeView.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/SpaceStateHandlerImpl.kt b/vector/src/main/java/im/vector/app/SpaceStateHandlerImpl.kt index 97b6eb6209..06abc16cbf 100644 --- a/vector/src/main/java/im/vector/app/SpaceStateHandlerImpl.kt +++ b/vector/src/main/java/im/vector/app/SpaceStateHandlerImpl.kt @@ -137,7 +137,7 @@ class SpaceStateHandlerImpl @Inject constructor( override fun popSpaceBackstack(): String? { vectorPreferences.getSpaceBackstack().toMutableList().apply { - val poppedSpaceId = removeLast() + val poppedSpaceId = removeAt(lastIndex) vectorPreferences.setSpaceBackstack(this) return poppedSpaceId } diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/views/LockScreenCodeView.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/views/LockScreenCodeView.kt index 5187b8ba73..6efe59b09a 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/views/LockScreenCodeView.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/views/LockScreenCodeView.kt @@ -92,7 +92,7 @@ class LockScreenCodeView @JvmOverloads constructor( */ fun deleteLast(): Int { if (code.size == 0) return code.size - code.removeLast() + code.removeAt(code.lastIndex) getCodeView(code.size)?.toggle() return code.size } From 41b1ded92bd64bd15e51206d3001d95f86b77c2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 10 Jul 2025 11:21:23 +0200 Subject: [PATCH 11/46] Use `ubuntu-22.04` runner instead of buildjet for the UI tests --- .github/workflows/post-pr.yml | 2 +- .github/workflows/tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/post-pr.yml b/.github/workflows/post-pr.yml index c0e7caba76..ab0d0da9ba 100644 --- a/.github/workflows/post-pr.yml +++ b/.github/workflows/post-pr.yml @@ -31,7 +31,7 @@ jobs: ui-tests: name: UI Tests (Synapse) needs: should-i-run - runs-on: buildjet-4vcpu-ubuntu-2204 + runs-on: ubuntu-22.04 timeout-minutes: 90 # We might need to increase it if the time for tests grows strategy: fail-fast: false diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 678ceaf11d..43c1a543f7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,7 +15,7 @@ env: jobs: tests: name: Runs all tests - runs-on: buildjet-4vcpu-ubuntu-2204 + runs-on: ubuntu-22.04 timeout-minutes: 90 # We might need to increase it if the time for tests grows strategy: matrix: From c10e6beb6593ad10d2d64288ced30a7c3c2442cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 10 Jul 2025 11:21:47 +0200 Subject: [PATCH 12/46] Run UI tests only when manually dispatched --- .github/workflows/tests.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 43c1a543f7..d090d80513 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,11 +1,7 @@ name: Test on: - pull_request: { } - push: - branches: [ main, develop ] - paths-ignore: - - '.github/**' + workflow_dispatch: # Enrich gradle.properties for CI/CD env: From e44d2e46353f8321aa0f551edf96a4a59d2a1945 Mon Sep 17 00:00:00 2001 From: mxandreas Date: Fri, 11 Jul 2025 10:08:19 +0100 Subject: [PATCH 13/46] Updated the status of the app to make clear it is not receiving further updates. --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 9be359428e..77342f493f 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,7 @@ # Element Android -Element Android is an Android Matrix Client provided by [Element](https://element.io/). The app can be run on every Android devices with Android OS Lollipop and more (API 21). - -It is a total rewrite of [Riot-Android](https://github.com/element-hq/riot-android) with a new user experience. +Element Classic Android is a previous-generation [Matrix](https://matrix.org/) client provided by [Element](https://element.io/). The app can be run on every Android devices with Android OS Lollipop and more (API 21). This client is still supported and receives security updates but no new features or usability enhancements are made. It is recommended to use [Element X](https://github.com/element-hq/element-x-android) that is the next-generation mobile app. [Get it on Google Play](https://play.google.com/store/apps/details?id=im.vector.app) [Get it on F-Droid](https://f-droid.org/app/im.vector.app) From cf01890e3a20b915c420f7fe57b1960116403ff9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 9 Jul 2025 09:54:36 +0200 Subject: [PATCH 14/46] Fix lint warning: ignore impossible SecurityException --- .../vector/app/core/services/BluetoothHeadsetReceiver.kt | 7 +++++++ .../app/features/call/audio/API21AudioDeviceDetector.kt | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/vector/src/main/java/im/vector/app/core/services/BluetoothHeadsetReceiver.kt b/vector/src/main/java/im/vector/app/core/services/BluetoothHeadsetReceiver.kt index dd04e5664e..a978b90a97 100644 --- a/vector/src/main/java/im/vector/app/core/services/BluetoothHeadsetReceiver.kt +++ b/vector/src/main/java/im/vector/app/core/services/BluetoothHeadsetReceiver.kt @@ -18,6 +18,10 @@ import androidx.core.content.ContextCompat import im.vector.lib.core.utils.compat.getParcelableExtraCompat import java.lang.ref.WeakReference +/** + * It's only used in API 21 and 22 so we will not have security exception on these OS, + * so it's safe to use @Suppress("MissingPermission") + */ class BluetoothHeadsetReceiver : BroadcastReceiver() { interface EventListener { @@ -53,12 +57,15 @@ class BluetoothHeadsetReceiver : BroadcastReceiver() { } val device = intent.getParcelableExtraCompat(BluetoothDevice.EXTRA_DEVICE) + @Suppress("MissingPermission") val deviceName = device?.name + @Suppress("MissingPermission") when (device?.bluetoothClass?.deviceClass) { BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE, BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO, BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET -> { // filter only device that we care about for + @Suppress("MissingPermission") delegate?.get()?.onBTHeadsetEvent( BTHeadsetPlugEvent( plugged = headsetConnected, diff --git a/vector/src/main/java/im/vector/app/features/call/audio/API21AudioDeviceDetector.kt b/vector/src/main/java/im/vector/app/features/call/audio/API21AudioDeviceDetector.kt index 30d6507380..69adc08c63 100644 --- a/vector/src/main/java/im/vector/app/features/call/audio/API21AudioDeviceDetector.kt +++ b/vector/src/main/java/im/vector/app/features/call/audio/API21AudioDeviceDetector.kt @@ -43,6 +43,11 @@ internal class API21AudioDeviceDetector( return HashSet().apply { if (isBluetoothHeadsetOn()) { connectedBlueToothHeadset?.connectedDevices?.forEach { + // Call requires permission which may be rejected by user: code should explicitly + // check to see if permission is available (with checkPermission) or explicitly + // handle a potential SecurityException + // But it should not happen on API 21/22. + @Suppress("MissingPermission") add(CallAudioManager.Device.WirelessHeadset(it.name)) } } From de3874ed7911f3e1d4824e71f9a9359a646e6666 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 11 Jul 2025 17:19:49 +0200 Subject: [PATCH 15/46] Fix lint warning: check permission before notifying with notification --- .../app/core/services/CallAndroidService.kt | 24 ++- .../tracking/LocationSharingAndroidService.kt | 21 ++- .../notifications/NotificationDisplayer.kt | 13 +- .../notifications/NotificationUtils.kt | 149 ++++++++++-------- 4 files changed, 129 insertions(+), 78 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/services/CallAndroidService.kt b/vector/src/main/java/im/vector/app/core/services/CallAndroidService.kt index f818ab412e..9e0a718b15 100644 --- a/vector/src/main/java/im/vector/app/core/services/CallAndroidService.kt +++ b/vector/src/main/java/im/vector/app/core/services/CallAndroidService.kt @@ -8,11 +8,14 @@ package im.vector.app.core.services +import android.Manifest import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import android.os.Binder import android.support.v4.media.session.MediaSessionCompat import android.view.KeyEvent +import androidx.core.app.ActivityCompat import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat import androidx.media.session.MediaButtonReceiver @@ -150,7 +153,8 @@ class CallAndroidService : VectorAndroidService() { val isVideoCall = call.mxCall.isVideoCall val fromBg = intent.getBooleanExtra(EXTRA_IS_IN_BG, false) Timber.tag(loggerTag.value).v("displayIncomingCallNotification : display the dedicated notification") - val incomingCallAlert = IncomingCallAlert(callId, + val incomingCallAlert = IncomingCallAlert( + callId, shouldBeDisplayedIn = { activity -> if (activity is VectorCallActivity) { activity.intent.getParcelableExtraCompat(Mavericks.KEY_ARG)?.callId != call.callId @@ -176,7 +180,11 @@ class CallAndroidService : VectorAndroidService() { if (knownCalls.isEmpty()) { startForegroundCompat(callId.hashCode(), notification) } else { - notificationManager.notify(callId.hashCode(), notification) + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + Timber.w("Not allowed to notify.") + } else { + notificationManager.notify(callId.hashCode(), notification) + } } knownCalls[callId] = callInformation } @@ -234,7 +242,11 @@ class CallAndroidService : VectorAndroidService() { if (knownCalls.isEmpty()) { startForegroundCompat(callId.hashCode(), notification) } else { - notificationManager.notify(callId.hashCode(), notification) + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + Timber.w("Not allowed to notify.") + } else { + notificationManager.notify(callId.hashCode(), notification) + } } knownCalls[callId] = callInformation } @@ -258,7 +270,11 @@ class CallAndroidService : VectorAndroidService() { if (knownCalls.isEmpty()) { startForegroundCompat(callId.hashCode(), notification) } else { - notificationManager.notify(callId.hashCode(), notification) + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + Timber.w("Not allowed to notify.") + } else { + notificationManager.notify(callId.hashCode(), notification) + } } knownCalls[callId] = callInformation } diff --git a/vector/src/main/java/im/vector/app/features/location/live/tracking/LocationSharingAndroidService.kt b/vector/src/main/java/im/vector/app/features/location/live/tracking/LocationSharingAndroidService.kt index 315a555703..4b4aa0c19d 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/tracking/LocationSharingAndroidService.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/tracking/LocationSharingAndroidService.kt @@ -7,9 +7,12 @@ package im.vector.app.features.location.live.tracking +import android.Manifest import android.content.Intent +import android.content.pm.PackageManager import android.os.IBinder import android.os.Parcelable +import androidx.core.app.ActivityCompat import androidx.core.app.NotificationManagerCompat import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.di.ActiveSessionHolder @@ -95,7 +98,11 @@ class LocationSharingAndroidService : VectorAndroidService(), LocationTracker.Ca // Show a sticky notification val notification = liveLocationNotificationBuilder.buildLiveLocationSharingNotification(roomArgs.roomId) if (foregroundModeStarted) { - NotificationManagerCompat.from(this).notify(FOREGROUND_SERVICE_NOTIFICATION_ID, notification) + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + Timber.w("Not allowed to notify.") + } else { + NotificationManagerCompat.from(this).notify(FOREGROUND_SERVICE_NOTIFICATION_ID, notification) + } } else { startForegroundCompat(FOREGROUND_SERVICE_NOTIFICATION_ID, notification) foregroundModeStarted = true @@ -146,10 +153,14 @@ class LocationSharingAndroidService : VectorAndroidService(), LocationTracker.Ca } private fun updateNotification() { - if (liveInfoSet.isNotEmpty()) { - val roomId = liveInfoSet.last().roomArgs.roomId - val notification = liveLocationNotificationBuilder.buildLiveLocationSharingNotification(roomId) - NotificationManagerCompat.from(this).notify(FOREGROUND_SERVICE_NOTIFICATION_ID, notification) + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + Timber.w("Not allowed to notify.") + } else { + if (liveInfoSet.isNotEmpty()) { + val roomId = liveInfoSet.last().roomArgs.roomId + val notification = liveLocationNotificationBuilder.buildLiveLocationSharingNotification(roomId) + NotificationManagerCompat.from(this).notify(FOREGROUND_SERVICE_NOTIFICATION_ID, notification) + } } } diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationDisplayer.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationDisplayer.kt index 5106eb1dfc..29eeb04f9d 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationDisplayer.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationDisplayer.kt @@ -7,18 +7,27 @@ package im.vector.app.features.notifications +import android.Manifest import android.app.Notification import android.content.Context +import android.content.pm.PackageManager +import androidx.core.app.ActivityCompat import androidx.core.app.NotificationManagerCompat import timber.log.Timber import javax.inject.Inject -class NotificationDisplayer @Inject constructor(context: Context) { +class NotificationDisplayer @Inject constructor( + private val context: Context, +) { private val notificationManager = NotificationManagerCompat.from(context) fun showNotificationMessage(tag: String?, id: Int, notification: Notification) { - notificationManager.notify(tag, id, notification) + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + Timber.w("Not allowed to notify.") + } else { + notificationManager.notify(tag, id, notification) + } } fun cancelNotificationMessage(tag: String?, id: Int) { diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt index 985d35961f..aa6010c31e 100755 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt @@ -9,6 +9,7 @@ package im.vector.app.features.notifications +import android.Manifest import android.annotation.SuppressLint import android.app.Notification import android.app.NotificationChannel @@ -16,6 +17,7 @@ import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import android.graphics.Bitmap import android.graphics.Canvas import android.net.Uri @@ -27,6 +29,7 @@ import androidx.annotation.AttrRes import androidx.annotation.ChecksSdkIntAtLeast import androidx.annotation.DrawableRes import androidx.annotation.StringRes +import androidx.core.app.ActivityCompat import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.app.RemoteInput @@ -153,55 +156,59 @@ class NotificationUtils @Inject constructor( * Default notification importance: shows everywhere, makes noise, but does not visually * intrude. */ - notificationManager.createNotificationChannel(NotificationChannel( - NOISY_NOTIFICATION_CHANNEL_ID, - stringProvider.getString(CommonStrings.notification_noisy_notifications).ifEmpty { "Noisy notifications" }, - NotificationManager.IMPORTANCE_DEFAULT - ) - .apply { - description = stringProvider.getString(CommonStrings.notification_noisy_notifications) - enableVibration(true) - enableLights(true) - lightColor = accentColor - }) + notificationManager.createNotificationChannel( + NotificationChannel( + NOISY_NOTIFICATION_CHANNEL_ID, + stringProvider.getString(CommonStrings.notification_noisy_notifications).ifEmpty { "Noisy notifications" }, + NotificationManager.IMPORTANCE_DEFAULT + ) + .apply { + description = stringProvider.getString(CommonStrings.notification_noisy_notifications) + enableVibration(true) + enableLights(true) + lightColor = accentColor + }) /** * Low notification importance: shows everywhere, but is not intrusive. */ - notificationManager.createNotificationChannel(NotificationChannel( - SILENT_NOTIFICATION_CHANNEL_ID, - stringProvider.getString(CommonStrings.notification_silent_notifications).ifEmpty { "Silent notifications" }, - NotificationManager.IMPORTANCE_LOW - ) - .apply { - description = stringProvider.getString(CommonStrings.notification_silent_notifications) - setSound(null, null) - enableLights(true) - lightColor = accentColor - }) + notificationManager.createNotificationChannel( + NotificationChannel( + SILENT_NOTIFICATION_CHANNEL_ID, + stringProvider.getString(CommonStrings.notification_silent_notifications).ifEmpty { "Silent notifications" }, + NotificationManager.IMPORTANCE_LOW + ) + .apply { + description = stringProvider.getString(CommonStrings.notification_silent_notifications) + setSound(null, null) + enableLights(true) + lightColor = accentColor + }) - notificationManager.createNotificationChannel(NotificationChannel( - LISTENING_FOR_EVENTS_NOTIFICATION_CHANNEL_ID, - stringProvider.getString(CommonStrings.notification_listening_for_events).ifEmpty { "Listening for events" }, - NotificationManager.IMPORTANCE_MIN - ) - .apply { - description = stringProvider.getString(CommonStrings.notification_listening_for_events) - setSound(null, null) - setShowBadge(false) - }) + notificationManager.createNotificationChannel( + NotificationChannel( + LISTENING_FOR_EVENTS_NOTIFICATION_CHANNEL_ID, + stringProvider.getString(CommonStrings.notification_listening_for_events).ifEmpty { "Listening for events" }, + NotificationManager.IMPORTANCE_MIN + ) + .apply { + description = stringProvider.getString(CommonStrings.notification_listening_for_events) + setSound(null, null) + setShowBadge(false) + }) - notificationManager.createNotificationChannel(NotificationChannel( - CALL_NOTIFICATION_CHANNEL_ID, - stringProvider.getString(CommonStrings.call).ifEmpty { "Call" }, - NotificationManager.IMPORTANCE_HIGH - ) - .apply { - description = stringProvider.getString(CommonStrings.call) - setSound(null, null) - enableLights(true) - lightColor = accentColor - }) + notificationManager.createNotificationChannel( + NotificationChannel( + CALL_NOTIFICATION_CHANNEL_ID, + stringProvider.getString(CommonStrings.call).ifEmpty { "Call" }, + NotificationManager.IMPORTANCE_HIGH + ) + .apply { + description = stringProvider.getString(CommonStrings.call) + setSound(null, null) + enableLights(true) + lightColor = accentColor + }) } fun getChannel(channelId: String): NotificationChannel? { @@ -997,7 +1004,11 @@ class NotificationUtils @Inject constructor( } fun showNotificationMessage(tag: String?, id: Int, notification: Notification) { - notificationManager.notify(tag, id, notification) + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + Timber.w("Not allowed to notify.") + } else { + notificationManager.notify(tag, id, notification) + } } fun cancelNotificationMessage(tag: String?, id: Int) { @@ -1025,30 +1036,34 @@ class NotificationUtils @Inject constructor( @SuppressLint("LaunchActivityFromNotification") fun displayDiagnosticNotification() { - val testActionIntent = Intent(context, TestNotificationReceiver::class.java) - testActionIntent.action = actionIds.diagnostic - val testPendingIntent = PendingIntent.getBroadcast( - context, - 0, - testActionIntent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE - ) + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + Timber.w("Not allowed to notify.") + } else { + val testActionIntent = Intent(context, TestNotificationReceiver::class.java) + testActionIntent.action = actionIds.diagnostic + val testPendingIntent = PendingIntent.getBroadcast( + context, + 0, + testActionIntent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + ) - notificationManager.notify( - "DIAGNOSTIC", - 888, - NotificationCompat.Builder(context, NOISY_NOTIFICATION_CHANNEL_ID) - .setContentTitle(buildMeta.applicationName) - .setContentText(stringProvider.getString(CommonStrings.settings_troubleshoot_test_push_notification_content)) - .setSmallIcon(R.drawable.ic_notification) - .setLargeIcon(getBitmap(context, im.vector.lib.ui.styles.R.drawable.element_logo_green)) - .setColor(ContextCompat.getColor(context, im.vector.lib.ui.styles.R.color.notification_accent_color)) - .setPriority(NotificationCompat.PRIORITY_MAX) - .setCategory(NotificationCompat.CATEGORY_STATUS) - .setAutoCancel(true) - .setContentIntent(testPendingIntent) - .build() - ) + notificationManager.notify( + "DIAGNOSTIC", + 888, + NotificationCompat.Builder(context, NOISY_NOTIFICATION_CHANNEL_ID) + .setContentTitle(buildMeta.applicationName) + .setContentText(stringProvider.getString(CommonStrings.settings_troubleshoot_test_push_notification_content)) + .setSmallIcon(R.drawable.ic_notification) + .setLargeIcon(getBitmap(context, im.vector.lib.ui.styles.R.drawable.element_logo_green)) + .setColor(ContextCompat.getColor(context, im.vector.lib.ui.styles.R.color.notification_accent_color)) + .setPriority(NotificationCompat.PRIORITY_MAX) + .setCategory(NotificationCompat.CATEGORY_STATUS) + .setAutoCancel(true) + .setContentIntent(testPendingIntent) + .build() + ) + } } private fun getBitmap(context: Context, @DrawableRes drawableRes: Int): Bitmap? { From fd67cacd253db3f74530b636a3cef7e8cb7c4957 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 11 Jul 2025 17:41:04 +0200 Subject: [PATCH 16/46] Fix lint warning: check permission before using location API --- .../app/core/utils/PermissionChecker.kt | 23 +++++++++++++++++++ .../location/LocationSharingViewModel.kt | 13 ++++++++++- .../app/features/location/LocationTracker.kt | 12 +++++++++- .../live/map/LiveLocationMapViewModel.kt | 14 ++++++++++- .../tracking/LocationSharingAndroidService.kt | 12 +++++++++- .../preview/LocationPreviewViewModel.kt | 14 ++++++++++- 6 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/core/utils/PermissionChecker.kt diff --git a/vector/src/main/java/im/vector/app/core/utils/PermissionChecker.kt b/vector/src/main/java/im/vector/app/core/utils/PermissionChecker.kt new file mode 100644 index 0000000000..3840e028c0 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/utils/PermissionChecker.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2025 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.utils + +import android.content.Context +import android.content.pm.PackageManager +import androidx.core.app.ActivityCompat +import javax.inject.Inject + +class PermissionChecker @Inject constructor( + private val applicationContext: Context, +) { + fun checkPermission(vararg permissions: String): Boolean { + return permissions.any { permission -> + ActivityCompat.checkSelfPermission(applicationContext, permission) != PackageManager.PERMISSION_GRANTED + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt index d9256e7685..82820e90cb 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt @@ -7,6 +7,7 @@ package im.vector.app.features.location +import android.Manifest import android.graphics.drawable.Drawable import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted @@ -15,6 +16,7 @@ import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel +import im.vector.app.core.utils.PermissionChecker import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider import im.vector.app.features.location.domain.usecase.CompareLocationsUseCase import im.vector.app.features.powerlevel.PowerLevelsFlowFactory @@ -48,6 +50,7 @@ class LocationSharingViewModel @AssistedInject constructor( private val session: Session, private val compareLocationsUseCase: CompareLocationsUseCase, private val vectorPreferences: VectorPreferences, + private val permissionChecker: PermissionChecker, ) : VectorViewModel(initialState), LocationTracker.Callback { private val room = session.getRoom(initialState.roomId)!! @@ -88,7 +91,15 @@ class LocationSharingViewModel @AssistedInject constructor( locationTracker.locations .onEach(::onLocationUpdate) .launchIn(viewModelScope) - locationTracker.start() + if (permissionChecker.checkPermission( + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + ) + ) { + locationTracker.start() + } else { + Timber.w("Not allowed to use location api.") + } } private fun setUserItem() { diff --git a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt index 70e909de27..1baebec617 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt @@ -17,6 +17,7 @@ import androidx.core.content.getSystemService import androidx.core.location.LocationListenerCompat import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.resources.BuildMeta +import im.vector.app.core.utils.PermissionChecker import im.vector.app.features.session.coroutineScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow @@ -37,6 +38,7 @@ class LocationTracker @Inject constructor( context: Context, private val activeSessionHolder: ActiveSessionHolder, private val buildMeta: BuildMeta, + private val permissionChecker: PermissionChecker, ) : LocationListenerCompat { private val locationManager = context.getSystemService() @@ -173,7 +175,15 @@ class LocationTracker @Inject constructor( fun removeCallback(callback: Callback) { callbacks.remove(callback) if (callbacks.size == 0) { - stop() + if (permissionChecker.checkPermission( + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + ) + ) { + stop() + } else { + Timber.w("Not allowed to use location api.") + } } } diff --git a/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewModel.kt b/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewModel.kt index 2fbd0b3d95..27ac31b785 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewModel.kt @@ -7,6 +7,7 @@ package im.vector.app.features.location.live.map +import android.Manifest import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -14,6 +15,7 @@ import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel +import im.vector.app.core.utils.PermissionChecker import im.vector.app.features.location.LocationData import im.vector.app.features.location.LocationTracker import im.vector.app.features.location.live.StopLiveLocationShareUseCase @@ -23,6 +25,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult +import timber.log.Timber class LiveLocationMapViewModel @AssistedInject constructor( @Assisted private val initialState: LiveLocationMapViewState, @@ -31,6 +34,7 @@ class LiveLocationMapViewModel @AssistedInject constructor( private val locationSharingServiceConnection: LocationSharingServiceConnection, private val stopLiveLocationShareUseCase: StopLiveLocationShareUseCase, private val locationTracker: LocationTracker, + private val permissionChecker: PermissionChecker, ) : VectorViewModel(initialState), LocationSharingServiceConnection.Callback, @@ -123,7 +127,15 @@ class LiveLocationMapViewModel @AssistedInject constructor( copy(isLoadingUserLocation = true) } viewModelScope.launch(session.coroutineDispatchers.main) { - locationTracker.start() + if (permissionChecker.checkPermission( + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + ) + ) { + locationTracker.start() + } else { + Timber.w("Not allowed to use location api.") + } locationTracker.requestLastKnownLocation() } } diff --git a/vector/src/main/java/im/vector/app/features/location/live/tracking/LocationSharingAndroidService.kt b/vector/src/main/java/im/vector/app/features/location/live/tracking/LocationSharingAndroidService.kt index 4b4aa0c19d..76205f98cf 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/tracking/LocationSharingAndroidService.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/tracking/LocationSharingAndroidService.kt @@ -18,6 +18,7 @@ import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.extensions.startForegroundCompat import im.vector.app.core.services.VectorAndroidService +import im.vector.app.core.utils.PermissionChecker import im.vector.app.features.location.LocationData import im.vector.app.features.location.LocationTracker import im.vector.app.features.location.live.GetLiveLocationShareSummaryUseCase @@ -55,6 +56,7 @@ class LocationSharingAndroidService : VectorAndroidService(), LocationTracker.Ca @Inject lateinit var activeSessionHolder: ActiveSessionHolder @Inject lateinit var getLiveLocationShareSummaryUseCase: GetLiveLocationShareSummaryUseCase @Inject lateinit var checkIfEventIsRedactedUseCase: CheckIfEventIsRedactedUseCase + @Inject lateinit var permissionChecker: PermissionChecker private var binder: LocationSharingAndroidServiceBinder? = null @@ -77,7 +79,15 @@ class LocationSharingAndroidService : VectorAndroidService(), LocationTracker.Ca private fun initLocationTracking() { // Start tracking location locationTracker.addCallback(this) - locationTracker.start() + if (permissionChecker.checkPermission( + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + ) + ) { + locationTracker.start() + } else { + Timber.w("Not allowed to use location api.") + } launchWithActiveSession { session -> val job = locationTracker.locations diff --git a/vector/src/main/java/im/vector/app/features/location/preview/LocationPreviewViewModel.kt b/vector/src/main/java/im/vector/app/features/location/preview/LocationPreviewViewModel.kt index aacff1f745..ae091adaae 100644 --- a/vector/src/main/java/im/vector/app/features/location/preview/LocationPreviewViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/preview/LocationPreviewViewModel.kt @@ -7,6 +7,7 @@ package im.vector.app.features.location.preview +import android.Manifest import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -14,6 +15,7 @@ import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel +import im.vector.app.core.utils.PermissionChecker import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider import im.vector.app.features.location.LocationData import im.vector.app.features.location.LocationTracker @@ -23,12 +25,14 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem +import timber.log.Timber class LocationPreviewViewModel @AssistedInject constructor( @Assisted private val initialState: LocationPreviewViewState, private val session: Session, private val locationPinProvider: LocationPinProvider, private val locationTracker: LocationTracker, + private val permissionChecker: PermissionChecker, ) : VectorViewModel(initialState), LocationTracker.Callback { @AssistedFactory @@ -89,7 +93,15 @@ class LocationPreviewViewModel @AssistedInject constructor( copy(isLoadingUserLocation = true) } viewModelScope.launch(session.coroutineDispatchers.main) { - locationTracker.start() + if (permissionChecker.checkPermission( + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + ) + ) { + locationTracker.start() + } else { + Timber.w("Not allowed to use location api.") + } locationTracker.requestLastKnownLocation() } } From e5622084ce91dab77fbd638c9598ab8972830f2f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 11 Jul 2025 18:12:57 +0200 Subject: [PATCH 17/46] Fix lint warning: check permission before recording audio --- .../im/vector/app/features/voice/VoiceRecorderL.kt | 10 +++++++++- .../vector/app/features/voice/VoiceRecorderProvider.kt | 4 +++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderL.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderL.kt index f53571e753..b905ff4447 100644 --- a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderL.kt +++ b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderL.kt @@ -7,6 +7,7 @@ package im.vector.app.features.voice +import android.Manifest import android.content.Context import android.media.AudioFormat import android.media.AudioRecord @@ -15,6 +16,7 @@ import android.media.audiofx.AutomaticGainControl import android.media.audiofx.NoiseSuppressor import android.os.Build import android.widget.Toast +import im.vector.app.core.utils.PermissionChecker import io.element.android.opusencoder.OggOpusEncoder import io.element.android.opusencoder.configuration.SampleRate import kotlinx.coroutines.CoroutineScope @@ -22,6 +24,7 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull +import timber.log.Timber import kotlin.coroutines.CoroutineContext /** @@ -31,6 +34,7 @@ class VoiceRecorderL( private val context: Context, coroutineContext: CoroutineContext, private val codec: OggOpusEncoder, + private val permissionChecker: PermissionChecker, ) : AbstractVoiceRecorder(context) { companion object { @@ -127,7 +131,11 @@ class VoiceRecorderL( bufferSizeInShorts = AudioRecord.getMinBufferSize(SAMPLE_RATE.value, channelConfig, format) // Buffer is created as a ShortArray, but AudioRecord needs the size in bytes val bufferSizeInBytes = bufferSizeInShorts * 2 - audioRecorder = AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE.value, channelConfig, format, bufferSizeInBytes) + if (permissionChecker.checkPermission(Manifest.permission.RECORD_AUDIO)) { + audioRecorder = AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE.value, channelConfig, format, bufferSizeInBytes) + } else { + Timber.w("Not allowed to record audio.") + } } private fun calculateMaxAmplitude(buffer: ShortArray) { diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderProvider.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderProvider.kt index 0b971907a2..62309aa59b 100644 --- a/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderProvider.kt +++ b/vector/src/main/java/im/vector/app/features/voice/VoiceRecorderProvider.kt @@ -13,6 +13,7 @@ import android.media.MediaFormat import android.os.Build import androidx.annotation.ChecksSdkIntAtLeast import androidx.annotation.VisibleForTesting +import im.vector.app.core.utils.PermissionChecker import im.vector.app.features.VectorFeatures import io.element.android.opusencoder.OggOpusEncoder import kotlinx.coroutines.Dispatchers @@ -23,12 +24,13 @@ class VoiceRecorderProvider @Inject constructor( private val context: Context, private val vectorFeatures: VectorFeatures, private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider, + private val permissionChecker: PermissionChecker, ) { fun provideVoiceRecorder(): VoiceRecorder { return if (useNativeRecorder()) { VoiceRecorderQ(context) } else { - VoiceRecorderL(context, Dispatchers.IO, OggOpusEncoder.create()) + VoiceRecorderL(context, Dispatchers.IO, OggOpusEncoder.create(), permissionChecker) } } From 1d08018e20e67dbcdf10a0b7af67c0e90dbc2118 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 11 Jul 2025 20:05:33 +0200 Subject: [PATCH 18/46] A final period. --- .../im/vector/app/core/services/BluetoothHeadsetReceiver.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/core/services/BluetoothHeadsetReceiver.kt b/vector/src/main/java/im/vector/app/core/services/BluetoothHeadsetReceiver.kt index a978b90a97..031ea2268d 100644 --- a/vector/src/main/java/im/vector/app/core/services/BluetoothHeadsetReceiver.kt +++ b/vector/src/main/java/im/vector/app/core/services/BluetoothHeadsetReceiver.kt @@ -20,7 +20,7 @@ import java.lang.ref.WeakReference /** * It's only used in API 21 and 22 so we will not have security exception on these OS, - * so it's safe to use @Suppress("MissingPermission") + * so it's safe to use @Suppress("MissingPermission"). */ class BluetoothHeadsetReceiver : BroadcastReceiver() { From 9b0d22f83f90ded8055903a91eec930e60ba1177 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 15 Jul 2025 11:14:51 +0200 Subject: [PATCH 19/46] Avoid crash for long logs. --- .../internal/network/interceptors/FormattedJsonHttpLogger.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt index 334a8c5076..f11b8a031c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt @@ -38,7 +38,8 @@ internal class FormattedJsonHttpLogger( */ @Synchronized override fun log(message: String) { - Timber.v(message) + Timber.v(message.take(20_000)) + if (message.length > 20_000) return // Try to log formatted Json only if there is a chance that [message] contains Json. // It can be only the case if we log the bodies of Http requests. From 3d7bf510295bfe409f408c3c98a26ec7ab66f533 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 15 Jul 2025 15:06:53 +0200 Subject: [PATCH 20/46] Target API 35: set `android:fitsSystemWindows="true"` to Activities. --- vector/src/main/res/layout/activity.xml | 5 +++-- vector/src/main/res/layout/activity_bug_report.xml | 1 + vector/src/main/res/layout/activity_call.xml | 4 ++-- vector/src/main/res/layout/activity_call_transfer.xml | 7 ++++--- .../src/main/res/layout/activity_emoji_reaction_picker.xml | 1 + vector/src/main/res/layout/activity_filtered_rooms.xml | 3 ++- vector/src/main/res/layout/activity_home.xml | 1 + vector/src/main/res/layout/activity_jitsi.xml | 5 +++-- vector/src/main/res/layout/activity_location_sharing.xml | 3 ++- vector/src/main/res/layout/activity_login.xml | 5 +++-- vector/src/main/res/layout/activity_room_detail.xml | 1 + vector/src/main/res/layout/activity_search.xml | 5 +++-- vector/src/main/res/layout/activity_signed_out.xml | 3 ++- vector/src/main/res/layout/activity_simple.xml | 5 +++-- vector/src/main/res/layout/activity_threads.xml | 3 ++- vector/src/main/res/layout/activity_vector_settings.xml | 3 ++- vector/src/main/res/layout/activity_vector_web_view.xml | 1 + vector/src/main/res/layout/activity_widget.xml | 3 ++- 18 files changed, 38 insertions(+), 21 deletions(-) diff --git a/vector/src/main/res/layout/activity.xml b/vector/src/main/res/layout/activity.xml index c8dd97cd89..343248d778 100644 --- a/vector/src/main/res/layout/activity.xml +++ b/vector/src/main/res/layout/activity.xml @@ -3,7 +3,8 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/coordinatorLayout" android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="match_parent" + android:fitsSystemWindows="true"> - \ No newline at end of file + diff --git a/vector/src/main/res/layout/activity_bug_report.xml b/vector/src/main/res/layout/activity_bug_report.xml index 1c019c858d..63ba0718a4 100644 --- a/vector/src/main/res/layout/activity_bug_report.xml +++ b/vector/src/main/res/layout/activity_bug_report.xml @@ -4,6 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" + android:fitsSystemWindows="true" android:orientation="vertical"> - + android:layout_height="match_parent" + android:fitsSystemWindows="true"> + android:layout_height="wrap_content" + app:title="@string/call_transfer_title" /> + android:layout_height="match_parent" + android:fitsSystemWindows="true"> + android:background="@android:color/black" + android:fitsSystemWindows="true"> @@ -24,4 +25,4 @@ - \ No newline at end of file + diff --git a/vector/src/main/res/layout/activity_location_sharing.xml b/vector/src/main/res/layout/activity_location_sharing.xml index bbb46de8c7..988f8e89f2 100755 --- a/vector/src/main/res/layout/activity_location_sharing.xml +++ b/vector/src/main/res/layout/activity_location_sharing.xml @@ -2,6 +2,7 @@ - \ No newline at end of file + diff --git a/vector/src/main/res/layout/activity_login.xml b/vector/src/main/res/layout/activity_login.xml index 7f5f4a96a1..afa34ff202 100644 --- a/vector/src/main/res/layout/activity_login.xml +++ b/vector/src/main/res/layout/activity_login.xml @@ -4,7 +4,8 @@ xmlns:tools="http://schemas.android.com/tools" android:id="@+id/coordinatorLayout" android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="match_parent" + android:fitsSystemWindows="true"> - \ No newline at end of file + diff --git a/vector/src/main/res/layout/activity_room_detail.xml b/vector/src/main/res/layout/activity_room_detail.xml index c3d34363c6..06f35a8ab9 100644 --- a/vector/src/main/res/layout/activity_room_detail.xml +++ b/vector/src/main/res/layout/activity_room_detail.xml @@ -4,6 +4,7 @@ android:id="@+id/drawerLayout" android:layout_width="match_parent" android:layout_height="match_parent" + android:fitsSystemWindows="true" tools:openDrawer="start"> + android:layout_height="match_parent" + android:fitsSystemWindows="true"> - \ No newline at end of file + diff --git a/vector/src/main/res/layout/activity_signed_out.xml b/vector/src/main/res/layout/activity_signed_out.xml index afbd6fa90f..eaa66a2a7e 100644 --- a/vector/src/main/res/layout/activity_signed_out.xml +++ b/vector/src/main/res/layout/activity_signed_out.xml @@ -4,7 +4,8 @@ android:id="@+id/signedOut" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="?android:colorBackground"> + android:background="?android:colorBackground" + android:fitsSystemWindows="true"> + android:layout_height="match_parent" + android:fitsSystemWindows="true"> - \ No newline at end of file + diff --git a/vector/src/main/res/layout/activity_threads.xml b/vector/src/main/res/layout/activity_threads.xml index 64d4583489..bc64055d3a 100644 --- a/vector/src/main/res/layout/activity_threads.xml +++ b/vector/src/main/res/layout/activity_threads.xml @@ -3,7 +3,8 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/coordinatorLayout" android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="match_parent" + android:fitsSystemWindows="true"> + android:layout_height="match_parent" + android:fitsSystemWindows="true"> - \ No newline at end of file + From 0a69a336a733f6f9c9c4c2188b3b1bac4f7fb606 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 15 Jul 2025 15:08:36 +0200 Subject: [PATCH 21/46] Remove dead code. --- .../app/core/platform/VectorBaseActivity.kt | 43 +------------------ 1 file changed, 2 insertions(+), 41 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt index 0d1bb6eb0d..dcec88bdbc 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt @@ -10,13 +10,11 @@ package im.vector.app.core.platform import android.annotation.SuppressLint import android.app.Activity import android.content.Context -import android.os.Build import android.os.Bundle import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View -import android.view.WindowInsetsController import android.view.WindowManager import android.widget.TextView import androidx.annotation.CallSuper @@ -25,7 +23,6 @@ import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatActivity import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.app.MultiWindowModeChangedInfo -import androidx.core.content.ContextCompat import androidx.core.util.Consumer import androidx.core.view.MenuProvider import androidx.core.view.isVisible @@ -336,7 +333,8 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver private fun handleCertificateError(certificateError: GlobalError.CertificateError) { singletonEntryPoint() .unrecognizedCertificateDialog() - .show(this, + .show( + this, certificateError.fingerprint, object : UnrecognizedCertificateDialog.Callback { override fun onAccept() { @@ -446,14 +444,6 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver mdmService.unregisterListener(this) } - override fun onWindowFocusChanged(hasFocus: Boolean) { - super.onWindowFocusChanged(hasFocus) - - if (hasFocus && displayInFullscreen()) { - setFullScreen() - } - } - private val onMultiWindowModeChangedListener = Consumer { Timber.w("onMultiWindowModeChanged. isInMultiWindowMode: ${it.isInMultiWindowMode}") bugReporter.inMultiWindowMode = it.isInMultiWindowMode @@ -463,33 +453,6 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver * PRIVATE METHODS * ========================================================================================== */ - /** - * Force to render the activity in fullscreen. - */ - private fun setFullScreen() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - // New API instead of SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN and SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - @Suppress("DEPRECATION") - window.setDecorFitsSystemWindows(false) - // New API instead of SYSTEM_UI_FLAG_IMMERSIVE - window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE - // New API instead of FLAG_TRANSLUCENT_STATUS - @Suppress("DEPRECATION") - window.statusBarColor = ContextCompat.getColor(this, im.vector.lib.ui.styles.R.color.half_transparent_status_bar) - // New API instead of FLAG_TRANSLUCENT_NAVIGATION - @Suppress("DEPRECATION") - window.navigationBarColor = ContextCompat.getColor(this, im.vector.lib.ui.styles.R.color.half_transparent_status_bar) - } else { - @Suppress("DEPRECATION") - window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE - or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION - or View.SYSTEM_UI_FLAG_FULLSCREEN - or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) - } - } - private fun handleMenuItemHome(item: MenuItem): Boolean { return when (item.itemId) { android.R.id.home -> { @@ -591,8 +554,6 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver abstract fun getBinding(): VB - open fun displayInFullscreen() = false - open fun doBeforeSetContentView() = Unit open fun initUiAndData() = Unit From 58de848cd000318b63ed3480eb277ce7029c6728 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 15 Jul 2025 15:19:06 +0200 Subject: [PATCH 22/46] Fix UI issue --- .../vector/app/features/home/room/detail/RoomDetailActivity.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt index fe185eca79..749092f20b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt @@ -14,7 +14,6 @@ import android.os.Bundle import android.view.View import android.widget.Toast import androidx.core.view.GravityCompat -import androidx.core.view.WindowCompat import androidx.drawerlayout.widget.DrawerLayout import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager @@ -93,7 +92,6 @@ class RoomDetailActivity : super.onCreate(savedInstanceState) // For dealing with insets and status bar background color - WindowCompat.setDecorFitsSystemWindows(window, false) @Suppress("DEPRECATION") window.statusBarColor = Color.TRANSPARENT From 66ec11049797b25446c3789c2ae7573f3f1534df Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 15 Jul 2025 16:05:36 +0200 Subject: [PATCH 23/46] Remove useless margin --- vector/src/main/res/layout/fragment_room_polls.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/src/main/res/layout/fragment_room_polls.xml b/vector/src/main/res/layout/fragment_room_polls.xml index 396d6fd8c5..7564c54afe 100644 --- a/vector/src/main/res/layout/fragment_room_polls.xml +++ b/vector/src/main/res/layout/fragment_room_polls.xml @@ -25,7 +25,6 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginHorizontal="4dp" - android:layout_marginTop="20dp" android:background="?android:colorBackground" app:layout_constraintBottom_toTopOf="@id/roomPollsViewPager" app:layout_constraintEnd_toEndOf="parent" From 82317bf2bc8bf7c7bfa7526cc8ac337adf141409 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 15 Jul 2025 16:30:48 +0200 Subject: [PATCH 24/46] Fix color issue on HomeActivity and RoomDetailActivity (others do not have the problem, not sure why :/) --- library/ui-styles/src/main/res/values/theme_light.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/ui-styles/src/main/res/values/theme_light.xml b/library/ui-styles/src/main/res/values/theme_light.xml index bd397d1bc6..68a0e8a5fe 100644 --- a/library/ui-styles/src/main/res/values/theme_light.xml +++ b/library/ui-styles/src/main/res/values/theme_light.xml @@ -64,6 +64,9 @@ @color/element_accent_light + + + ?vctr_toolbar_background @color/element_accent_light @android:color/white @color/element_accent_light From 3f22ddb065ba8094914786160dae67f4ee565e1c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 16 Jul 2025 11:29:22 +0200 Subject: [PATCH 25/46] Fix regression on timeline for thread. --- .../vector/app/features/home/room/detail/TimelineFragment.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 8e7b9ca951..56db3a84ba 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 @@ -411,8 +411,7 @@ class TimelineFragment : } ViewCompat.setOnApplyWindowInsetsListener(views.coordinatorLayout) { _, insets -> - val imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime() or WindowInsetsCompat.Type.systemBars()) - views.appBarLayout.updatePadding(top = imeInsets.top) + val imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime()) views.voiceMessageRecorderContainer.updatePadding(bottom = imeInsets.bottom) insets } From daadbbd76b606669275d09bf9f5cd118d70d57fe Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 16 Jul 2025 12:52:06 +0200 Subject: [PATCH 26/46] Revert "Target API 35: set `android:fitsSystemWindows="true"` to Activities." This reverts commit 3d7bf510295bfe409f408c3c98a26ec7ab66f533. --- vector/src/main/res/layout/activity.xml | 5 ++--- vector/src/main/res/layout/activity_bug_report.xml | 1 - vector/src/main/res/layout/activity_call.xml | 4 ++-- vector/src/main/res/layout/activity_call_transfer.xml | 7 +++---- .../src/main/res/layout/activity_emoji_reaction_picker.xml | 1 - vector/src/main/res/layout/activity_filtered_rooms.xml | 3 +-- vector/src/main/res/layout/activity_home.xml | 1 - vector/src/main/res/layout/activity_jitsi.xml | 5 ++--- vector/src/main/res/layout/activity_location_sharing.xml | 3 +-- vector/src/main/res/layout/activity_login.xml | 5 ++--- vector/src/main/res/layout/activity_room_detail.xml | 1 - vector/src/main/res/layout/activity_search.xml | 5 ++--- vector/src/main/res/layout/activity_signed_out.xml | 3 +-- vector/src/main/res/layout/activity_simple.xml | 5 ++--- vector/src/main/res/layout/activity_threads.xml | 3 +-- vector/src/main/res/layout/activity_vector_settings.xml | 3 +-- vector/src/main/res/layout/activity_vector_web_view.xml | 1 - vector/src/main/res/layout/activity_widget.xml | 3 +-- 18 files changed, 21 insertions(+), 38 deletions(-) diff --git a/vector/src/main/res/layout/activity.xml b/vector/src/main/res/layout/activity.xml index 343248d778..c8dd97cd89 100644 --- a/vector/src/main/res/layout/activity.xml +++ b/vector/src/main/res/layout/activity.xml @@ -3,8 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/coordinatorLayout" android:layout_width="match_parent" - android:layout_height="match_parent" - android:fitsSystemWindows="true"> + android:layout_height="match_parent"> - + \ No newline at end of file diff --git a/vector/src/main/res/layout/activity_bug_report.xml b/vector/src/main/res/layout/activity_bug_report.xml index 63ba0718a4..1c019c858d 100644 --- a/vector/src/main/res/layout/activity_bug_report.xml +++ b/vector/src/main/res/layout/activity_bug_report.xml @@ -4,7 +4,6 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:fitsSystemWindows="true" android:orientation="vertical"> + android:layout_height="match_parent"> + android:layout_height="wrap_content" /> + android:layout_height="match_parent"> + android:background="@android:color/black"> @@ -25,4 +24,4 @@ - + \ No newline at end of file diff --git a/vector/src/main/res/layout/activity_location_sharing.xml b/vector/src/main/res/layout/activity_location_sharing.xml index 988f8e89f2..bbb46de8c7 100755 --- a/vector/src/main/res/layout/activity_location_sharing.xml +++ b/vector/src/main/res/layout/activity_location_sharing.xml @@ -2,7 +2,6 @@ - + \ No newline at end of file diff --git a/vector/src/main/res/layout/activity_login.xml b/vector/src/main/res/layout/activity_login.xml index afa34ff202..7f5f4a96a1 100644 --- a/vector/src/main/res/layout/activity_login.xml +++ b/vector/src/main/res/layout/activity_login.xml @@ -4,8 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:id="@+id/coordinatorLayout" android:layout_width="match_parent" - android:layout_height="match_parent" - android:fitsSystemWindows="true"> + android:layout_height="match_parent"> - + \ No newline at end of file diff --git a/vector/src/main/res/layout/activity_room_detail.xml b/vector/src/main/res/layout/activity_room_detail.xml index 06f35a8ab9..c3d34363c6 100644 --- a/vector/src/main/res/layout/activity_room_detail.xml +++ b/vector/src/main/res/layout/activity_room_detail.xml @@ -4,7 +4,6 @@ android:id="@+id/drawerLayout" android:layout_width="match_parent" android:layout_height="match_parent" - android:fitsSystemWindows="true" tools:openDrawer="start"> + android:layout_height="match_parent"> - + \ No newline at end of file diff --git a/vector/src/main/res/layout/activity_signed_out.xml b/vector/src/main/res/layout/activity_signed_out.xml index eaa66a2a7e..afbd6fa90f 100644 --- a/vector/src/main/res/layout/activity_signed_out.xml +++ b/vector/src/main/res/layout/activity_signed_out.xml @@ -4,8 +4,7 @@ android:id="@+id/signedOut" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="?android:colorBackground" - android:fitsSystemWindows="true"> + android:background="?android:colorBackground"> + android:layout_height="match_parent"> - + \ No newline at end of file diff --git a/vector/src/main/res/layout/activity_threads.xml b/vector/src/main/res/layout/activity_threads.xml index bc64055d3a..64d4583489 100644 --- a/vector/src/main/res/layout/activity_threads.xml +++ b/vector/src/main/res/layout/activity_threads.xml @@ -3,8 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/coordinatorLayout" android:layout_width="match_parent" - android:layout_height="match_parent" - android:fitsSystemWindows="true"> + android:layout_height="match_parent"> + android:layout_height="match_parent"> - + \ No newline at end of file From c79f2ff4d12c67b4a680bc3626e198e20861768f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 16 Jul 2025 15:03:13 +0200 Subject: [PATCH 27/46] Enable edgeToEdge --- .../app/features/debug/DebugMenuActivity.kt | 5 ++++- .../features/debug/DebugPermissionActivity.kt | 4 ++++ .../debug/analytics/DebugAnalyticsActivity.kt | 5 +++++ .../features/DebugFeaturesSettingsActivity.kt | 4 ++++ .../debug/jitsi/DebugJitsiActivity.kt | 3 +++ .../debug/leak/DebugMemoryLeaksActivity.kt | 5 +++++ .../settings/DebugPrivateSettingsActivity.kt | 5 +++++ vector/src/main/AndroidManifest.xml | 3 +-- .../core/platform/SimpleFragmentActivity.kt | 4 ++++ .../app/core/platform/VectorBaseActivity.kt | 21 +++++++++++++++++++ .../im/vector/app/features/MainActivity.kt | 4 ++++ .../ui/consent/AnalyticsOptInActivity.kt | 4 ++++ .../preview/AttachmentsPreviewActivity.kt | 4 ++++ .../app/features/call/VectorCallActivity.kt | 3 +++ .../call/conference/VectorJitsiActivity.kt | 4 ++++ .../call/transfer/CallTransferActivity.kt | 4 ++++ .../vector/app/features/home/HomeActivity.kt | 3 +++ .../home/room/detail/RoomDetailActivity.kt | 3 +++ .../home/room/detail/TimelineFragment.kt | 10 ++++----- .../home/room/detail/search/SearchActivity.kt | 4 ++++ .../room/filtered/FilteredRoomsActivity.kt | 4 ++++ .../room/list/home/invites/InvitesActivity.kt | 5 +++++ .../list/home/release/ReleaseNotesActivity.kt | 4 ++++ .../home/room/threads/ThreadsActivity.kt | 4 ++++ .../app/features/link/LinkHandlerActivity.kt | 4 ++++ .../location/LocationSharingActivity.kt | 4 ++++ .../live/map/LiveLocationMapViewActivity.kt | 4 ++++ .../app/features/login/LoginActivity.kt | 3 +++ .../features/media/BigImageViewerActivity.kt | 4 ++++ .../features/onboarding/OnboardingActivity.kt | 4 ++++ .../im/vector/app/features/pin/PinActivity.kt | 4 ++++ .../features/qrcode/QrCodeScannerActivity.kt | 4 ++++ .../features/rageshake/BugReportActivity.kt | 4 ++++ .../reactions/EmojiReactionPickerActivity.kt | 4 ++++ .../roomdirectory/RoomDirectoryActivity.kt | 4 ++++ .../createroom/CreateRoomActivity.kt | 4 ++++ .../roompreview/RoomPreviewActivity.kt | 4 ++++ .../RoomMemberProfileActivity.kt | 6 ++++++ .../roomprofile/RoomProfileActivity.kt | 4 ++++ .../polls/detail/ui/RoomPollDetailActivity.kt | 6 ++++++ .../settings/joinrule/RoomJoinRuleActivity.kt | 6 ++++++ .../settings/VectorSettingsActivity.kt | 4 ++++ .../v2/rename/RenameSessionActivity.kt | 6 ++++++ .../settings/font/FontScaleSettingActivity.kt | 6 ++++++ .../features/share/IncomingShareActivity.kt | 4 ++++ .../signout/hard/SignedOutActivity.kt | 4 ++++ .../features/spaces/SpaceExploreActivity.kt | 6 ++++++ .../features/spaces/SpacePreviewActivity.kt | 6 ++++++ .../leave/SpaceLeaveAdvancedActivity.kt | 6 ++++++ .../spaces/manage/SpaceManageActivity.kt | 6 ++++++ .../spaces/people/SpacePeopleActivity.kt | 6 ++++++ .../app/features/usercode/UserCodeActivity.kt | 4 ++++ .../features/webview/VectorWebViewActivity.kt | 4 ++++ .../app/features/widgets/WidgetActivity.kt | 4 ++++ .../res/layout/activity_big_image_viewer.xml | 3 ++- .../main/res/layout/activity_bug_report.xml | 1 + vector/src/main/res/layout/activity_call.xml | 1 - .../res/layout/activity_location_sharing.xml | 3 ++- vector/src/main/res/layout/activity_main.xml | 1 + .../src/main/res/layout/activity_progress.xml | 1 + .../res/layout/activity_vector_web_view.xml | 1 + .../src/main/res/layout/activity_widget.xml | 3 ++- .../res/layout/fragment_generic_recycler.xml | 1 + .../res/layout/fragment_new_home_detail.xml | 1 - 64 files changed, 261 insertions(+), 13 deletions(-) diff --git a/vector-app/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt b/vector-app/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt index 288b7a89bd..876baf69d9 100644 --- a/vector-app/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt +++ b/vector-app/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt @@ -12,6 +12,7 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.content.Intent import android.os.Build +import android.view.View import androidx.core.app.NotificationCompat import androidx.core.app.Person import androidx.core.content.getSystemService @@ -49,7 +50,9 @@ import javax.inject.Inject class DebugMenuActivity : VectorBaseActivity() { override fun getBinding() = ActivityDebugMenuBinding.inflate(layoutInflater) - + override fun getCoordinatorLayout() = views.coordinatorLayout + override val rootView: View + get() = views.coordinatorLayout @Inject lateinit var clock: Clock private lateinit var buffer: ByteArray diff --git a/vector-app/src/debug/java/im/vector/app/features/debug/DebugPermissionActivity.kt b/vector-app/src/debug/java/im/vector/app/features/debug/DebugPermissionActivity.kt index cae9f63b22..9f5622713b 100644 --- a/vector-app/src/debug/java/im/vector/app/features/debug/DebugPermissionActivity.kt +++ b/vector-app/src/debug/java/im/vector/app/features/debug/DebugPermissionActivity.kt @@ -10,6 +10,7 @@ package im.vector.app.features.debug import android.Manifest import android.content.pm.PackageManager import android.os.Build +import android.view.View import android.widget.Toast import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat @@ -32,6 +33,9 @@ class DebugPermissionActivity : VectorBaseActivity() { override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater) + override fun getCoordinatorLayout() = views.coordinatorLayout + override val rootView: View + get() = views.coordinatorLayout + override fun initUiAndData() { if (isFirstCreation()) { addFragment( diff --git a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesSettingsActivity.kt b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesSettingsActivity.kt index 2f7ff692af..b4a7793962 100644 --- a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesSettingsActivity.kt +++ b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesSettingsActivity.kt @@ -8,6 +8,7 @@ package im.vector.app.features.debug.features import android.os.Bundle +import android.view.View import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith @@ -24,6 +25,9 @@ class DebugFeaturesSettingsActivity : VectorBaseActivity() { override fun getBinding() = ActivityDebugJitsiBinding.inflate(layoutInflater) override fun getCoordinatorLayout() = views.coordinatorLayout + override val rootView: View + get() = views.coordinatorLayout @SuppressLint("SetTextI18n") override fun initUiAndData() { diff --git a/vector-app/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksActivity.kt b/vector-app/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksActivity.kt index f942fc342d..7eb9ea99d6 100644 --- a/vector-app/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksActivity.kt +++ b/vector-app/src/debug/java/im/vector/app/features/debug/leak/DebugMemoryLeaksActivity.kt @@ -7,6 +7,7 @@ package im.vector.app.features.debug.leak +import android.view.View import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.addFragment import im.vector.app.core.platform.VectorBaseActivity @@ -17,6 +18,10 @@ class DebugMemoryLeaksActivity : VectorBaseActivity() { override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater) + override fun getCoordinatorLayout() = views.coordinatorLayout + override val rootView: View + get() = views.coordinatorLayout + override fun initUiAndData() { if (isFirstCreation()) { addFragment( diff --git a/vector-app/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsActivity.kt b/vector-app/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsActivity.kt index 017bde5eb7..2a539a21ca 100644 --- a/vector-app/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsActivity.kt +++ b/vector-app/src/debug/java/im/vector/app/features/debug/settings/DebugPrivateSettingsActivity.kt @@ -7,6 +7,7 @@ package im.vector.app.features.debug.settings +import android.view.View import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.addFragment import im.vector.app.core.platform.VectorBaseActivity @@ -17,6 +18,10 @@ class DebugPrivateSettingsActivity : VectorBaseActivity() override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater) + override fun getCoordinatorLayout() = views.coordinatorLayout + override val rootView: View + get() = views.coordinatorLayout + override fun initUiAndData() { if (isFirstCreation()) { addFragment( diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index ce1b2537f1..7455c0847f 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -167,8 +167,7 @@ + android:parentActivityName=".features.home.HomeActivity"> diff --git a/vector/src/main/java/im/vector/app/core/platform/SimpleFragmentActivity.kt b/vector/src/main/java/im/vector/app/core/platform/SimpleFragmentActivity.kt index 06eecf1d41..12ed8418bc 100644 --- a/vector/src/main/java/im/vector/app/core/platform/SimpleFragmentActivity.kt +++ b/vector/src/main/java/im/vector/app/core/platform/SimpleFragmentActivity.kt @@ -6,6 +6,7 @@ */ package im.vector.app.core.platform +import android.view.View import androidx.core.view.isGone import androidx.core.view.isVisible import im.vector.app.core.extensions.hideKeyboard @@ -20,6 +21,9 @@ abstract class SimpleFragmentActivity : VectorBaseActivity() { final override fun getCoordinatorLayout() = views.coordinatorLayout + override val rootView: View + get() = views.coordinatorLayout + override fun initUiAndData() { setupToolbar(views.toolbar) .allowBack(true) diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt index dcec88bdbc..a6b9d77930 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt @@ -17,6 +17,7 @@ import android.view.MenuItem import android.view.View import android.view.WindowManager import android.widget.TextView +import androidx.activity.enableEdgeToEdge import androidx.annotation.CallSuper import androidx.annotation.MainThread import androidx.annotation.StringRes @@ -25,7 +26,10 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.app.MultiWindowModeChangedInfo import androidx.core.util.Consumer import androidx.core.view.MenuProvider +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat import androidx.core.view.isVisible +import androidx.core.view.updatePadding import androidx.fragment.app.FragmentManager import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider @@ -205,6 +209,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver val activityEntryPoint = EntryPointAccessors.fromActivity(this, ActivityEntryPoint::class.java) ThemeUtils.setActivityTheme(this, getOtherThemes()) viewModelFactory = activityEntryPoint.viewModelFactory() + enableEdgeToEdge() super.onCreate(savedInstanceState) addOnMultiWindowModeChangedListener(onMultiWindowModeChangedListener) setupMenu() @@ -411,6 +416,20 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver // Just log that a change occurred. Timber.w("MDM data has been updated") } + ViewCompat.setOnApplyWindowInsetsListener(rootView) { v, insets -> + val systemBars = insets.getInsets( + WindowInsetsCompat.Type.systemBars() or + WindowInsetsCompat.Type.displayCutout() or + WindowInsetsCompat.Type.ime() + ) + v.updatePadding( + systemBars.left, + systemBars.top, + systemBars.right, + systemBars.bottom, + ) + insets + } } private val postResumeScheduledActions = mutableListOf<() -> Unit>() @@ -592,6 +611,8 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver open fun getCoordinatorLayout(): CoordinatorLayout? = null + abstract val rootView: View + /* ========================================================================================== * User Consent * ========================================================================================== */ diff --git a/vector/src/main/java/im/vector/app/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt index 6b16c2deb8..9c95a5d835 100644 --- a/vector/src/main/java/im/vector/app/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt @@ -12,6 +12,7 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.os.Parcelable +import android.view.View import androidx.core.content.ContextCompat import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle @@ -392,4 +393,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity val className = componentName.className return packageName == buildMeta.applicationId && className in allowList } + + override val rootView: View + get() = views.mainRoot } diff --git a/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInActivity.kt b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInActivity.kt index e3cf06bd87..42598f4356 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInActivity.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/ui/consent/AnalyticsOptInActivity.kt @@ -7,6 +7,7 @@ package im.vector.app.features.analytics.ui.consent +import android.view.View import com.airbnb.mvrx.viewModel import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.addFragment @@ -29,6 +30,9 @@ class AnalyticsOptInActivity : VectorBaseActivity() { override fun getCoordinatorLayout() = views.coordinatorLayout + override val rootView: View + get() = views.coordinatorLayout + override fun initUiAndData() { orientationLocker.lockPhonesToPortrait(this) if (isFirstCreation()) { diff --git a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewActivity.kt b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewActivity.kt index 487e116fb5..5382d54670 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewActivity.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewActivity.kt @@ -9,6 +9,7 @@ package im.vector.app.features.attachments.preview import android.content.Context import android.content.Intent +import android.view.View import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.addFragment import im.vector.app.core.platform.VectorBaseActivity @@ -47,6 +48,9 @@ class AttachmentsPreviewActivity : VectorBaseActivity() { override fun getCoordinatorLayout() = views.coordinatorLayout + override val rootView: View + get() = views.coordinatorLayout + override fun initUiAndData() { if (isFirstCreation()) { val fragmentArgs: AttachmentsPreviewArgs = intent?.extras?.getParcelableCompat(EXTRA_FRAGMENT_ARGS) ?: return diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt index 86ecfba6b6..03a3aaf8c9 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt @@ -187,6 +187,9 @@ class VectorCallActivity : override fun getMenuRes() = R.menu.vector_call + override val rootView: View + get() = views.constraintLayout + override fun onUserLeaveHint() { super.onUserLeaveHint() enterPictureInPictureIfRequired() diff --git a/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt b/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt index 4a0ccf28e3..a86b5e34c9 100644 --- a/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt +++ b/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt @@ -15,6 +15,7 @@ import android.content.res.Configuration import android.os.Build import android.os.Bundle import android.os.Parcelable +import android.view.View import android.widget.FrameLayout import android.widget.Toast import androidx.core.app.PictureInPictureModeChangedInfo @@ -58,6 +59,9 @@ class VectorJitsiActivity : VectorBaseActivity(), JitsiMee override fun getBinding() = ActivityJitsiBinding.inflate(layoutInflater) + override val rootView: View + get() = views.jitsiLayout + private var jitsiMeetView: JitsiMeetView? = null private val jitsiViewModel: JitsiCallViewModel by viewModel() diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt index 6a647bd501..1925ce854e 100644 --- a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt +++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt @@ -11,6 +11,7 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.os.Parcelable +import android.view.View import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel import com.google.android.material.tabs.TabLayoutMediator @@ -37,6 +38,9 @@ class CallTransferActivity : VectorBaseActivity() { override fun getCoordinatorLayout() = views.vectorCoordinatorLayout + override val rootView: View + get() = views.vectorCoordinatorLayout + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) waitingView = views.waitingView.waitingView diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index d22ea90856..bea9d1303b 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -192,6 +192,9 @@ class HomeActivity : override fun getCoordinatorLayout() = views.coordinatorLayout + override val rootView: View + get() = views.coordinatorLayout + override fun getBinding() = ActivityHomeBinding.inflate(layoutInflater) override fun onCreate(savedInstanceState: Bundle?) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt index 749092f20b..e5d785af63 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt @@ -81,6 +81,9 @@ class RoomDetailActivity : override fun getCoordinatorLayout() = views.coordinatorLayout + override val rootView: View + get() = views.coordinatorLayout + @Inject lateinit var playbackTracker: AudioMessagePlaybackTracker private lateinit var sharedActionViewModel: RoomDetailSharedActionViewModel private val requireActiveMembershipViewModel: RequireActiveMembershipViewModel by viewModel() 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 56db3a84ba..32f0c4e9c2 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 @@ -410,11 +410,11 @@ class TimelineFragment : } } - ViewCompat.setOnApplyWindowInsetsListener(views.coordinatorLayout) { _, insets -> - val imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime()) - views.voiceMessageRecorderContainer.updatePadding(bottom = imeInsets.bottom) - insets - } + //ViewCompat.setOnApplyWindowInsetsListener(views.coordinatorLayout) { _, insets -> + // val imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime()) + // views.voiceMessageRecorderContainer.updatePadding(bottom = imeInsets.bottom) + // insets + //} } private fun setupBackPressHandling() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchActivity.kt index 0d674174c8..f262f5b044 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchActivity.kt @@ -10,6 +10,7 @@ package im.vector.app.features.home.room.detail.search import android.content.Context import android.content.Intent import android.os.Bundle +import android.view.View import androidx.appcompat.widget.SearchView import com.airbnb.mvrx.Mavericks import dagger.hilt.android.AndroidEntryPoint @@ -30,6 +31,9 @@ class SearchActivity : VectorBaseActivity() { override fun getCoordinatorLayout() = views.coordinatorLayout + override val rootView: View + get() = views.coordinatorLayout + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setupToolbar(views.searchToolbar) diff --git a/vector/src/main/java/im/vector/app/features/home/room/filtered/FilteredRoomsActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/filtered/FilteredRoomsActivity.kt index b2402ee439..7e10e69797 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/filtered/FilteredRoomsActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/filtered/FilteredRoomsActivity.kt @@ -10,6 +10,7 @@ package im.vector.app.features.home.room.filtered import android.content.Context import android.content.Intent import android.os.Bundle +import android.view.View import androidx.appcompat.widget.SearchView import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.replaceFragment @@ -32,6 +33,9 @@ class FilteredRoomsActivity : VectorBaseActivity() override fun getCoordinatorLayout() = views.coordinatorLayout + override val rootView: View + get() = views.coordinatorLayout + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) analyticsScreenName = MobileScreen.ScreenName.RoomFilter diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesActivity.kt index f2a19545fe..8976e580c5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesActivity.kt @@ -7,6 +7,7 @@ package im.vector.app.features.home.room.list.home.invites +import android.view.View import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.addFragment import im.vector.app.core.platform.VectorBaseActivity @@ -22,4 +23,8 @@ class InvitesActivity : VectorBaseActivity() { addFragment(views.simpleFragmentContainer, InvitesFragment::class.java) } } + + override fun getCoordinatorLayout() = views.coordinatorLayout + override val rootView: View + get() = views.coordinatorLayout } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesActivity.kt index 0a3fa4393a..e7c104ff05 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesActivity.kt @@ -7,6 +7,7 @@ package im.vector.app.features.home.room.list.home.release +import android.view.View import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.addFragment @@ -26,6 +27,9 @@ class ReleaseNotesActivity : VectorBaseActivity() { override fun getCoordinatorLayout() = views.coordinatorLayout + override val rootView: View + get() = views.coordinatorLayout + override fun initUiAndData() { orientationLocker.lockPhonesToPortrait(this) if (isFirstCreation()) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt index e0b773c2b6..9174f60e5b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt @@ -10,6 +10,7 @@ package im.vector.app.features.home.room.threads import android.content.Context import android.content.Intent import android.os.Bundle +import android.view.View import androidx.fragment.app.FragmentTransaction import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.addFragmentToBackstack @@ -42,6 +43,9 @@ class ThreadsActivity : VectorBaseActivity() { override fun getCoordinatorLayout() = views.coordinatorLayout + override val rootView: View + get() = views.coordinatorLayout + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) initFragment() diff --git a/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt b/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt index 5c3ba3fbc3..f8c9057571 100644 --- a/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt @@ -10,6 +10,7 @@ package im.vector.app.features.link import android.content.Intent import android.net.Uri import android.os.Bundle +import android.view.View import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.viewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -41,6 +42,9 @@ class LinkHandlerActivity : VectorBaseActivity() { override fun getBinding() = ActivityProgressBinding.inflate(layoutInflater) + override val rootView: View + get() = views.mainRoot + override fun initUiAndData() { handleIntent() } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt index d9d3ee0731..70ef34d03b 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingActivity.kt @@ -10,6 +10,7 @@ package im.vector.app.features.location import android.content.Context import android.content.Intent import android.os.Parcelable +import android.view.View import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.addFragment import im.vector.app.core.platform.VectorBaseActivity @@ -31,6 +32,9 @@ class LocationSharingActivity : VectorBaseActivity(), UnlockedA override fun getCoordinatorLayout() = views.coordinatorLayout + override val rootView: View + get() = views.coordinatorLayout + override fun initUiAndData() { analyticsScreenName = MobileScreen.ScreenName.Login diff --git a/vector/src/main/java/im/vector/app/features/media/BigImageViewerActivity.kt b/vector/src/main/java/im/vector/app/features/media/BigImageViewerActivity.kt index 05c331d1d0..57d631b5be 100644 --- a/vector/src/main/java/im/vector/app/features/media/BigImageViewerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/media/BigImageViewerActivity.kt @@ -10,6 +10,7 @@ package im.vector.app.features.media import android.content.Context import android.content.Intent import android.os.Bundle +import android.view.View import androidx.core.net.toUri import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.di.ActiveSessionHolder @@ -26,6 +27,9 @@ class BigImageViewerActivity : VectorBaseActivity override fun getBinding() = ActivityBigImageViewerBinding.inflate(layoutInflater) + override val rootView: View + get() = views.mainRoot + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingActivity.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingActivity.kt index f62989fec6..dd8d287bad 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingActivity.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingActivity.kt @@ -10,6 +10,7 @@ package im.vector.app.features.onboarding import android.content.Context import android.content.Intent import android.net.Uri +import android.view.View import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.lazyViewModel import im.vector.app.core.extensions.validateBackPressed @@ -33,6 +34,9 @@ class OnboardingActivity : VectorBaseActivity(), UnlockedA override fun getCoordinatorLayout() = views.coordinatorLayout + override val rootView: View + get() = views.coordinatorLayout + override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) onboardingVariant.onNewIntent(intent) diff --git a/vector/src/main/java/im/vector/app/features/pin/PinActivity.kt b/vector/src/main/java/im/vector/app/features/pin/PinActivity.kt index a002bc0329..b5c70fddd9 100644 --- a/vector/src/main/java/im/vector/app/features/pin/PinActivity.kt +++ b/vector/src/main/java/im/vector/app/features/pin/PinActivity.kt @@ -9,6 +9,7 @@ package im.vector.app.features.pin import android.content.Context import android.content.Intent +import android.view.View import com.airbnb.mvrx.Mavericks import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.addFragment @@ -31,6 +32,9 @@ class PinActivity : VectorBaseActivity(), UnlockedActivit override fun getCoordinatorLayout() = views.coordinatorLayout + override val rootView: View + get() = views.coordinatorLayout + override fun initUiAndData() { if (isFirstCreation()) { val fragmentArgs: PinArgs = intent?.extras?.getParcelableCompat(Mavericks.KEY_ARG) ?: return diff --git a/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerActivity.kt b/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerActivity.kt index d2f8016bda..5325a3f043 100644 --- a/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerActivity.kt @@ -10,6 +10,7 @@ package im.vector.app.features.qrcode import android.app.Activity import android.content.Intent import android.os.Bundle +import android.view.View import android.widget.Toast import androidx.activity.result.ActivityResultLauncher import com.airbnb.mvrx.viewModel @@ -26,6 +27,9 @@ class QrCodeScannerActivity : VectorBaseActivity() { override fun getCoordinatorLayout() = views.coordinatorLayout + override val rootView: View + get() = views.coordinatorLayout + private val qrViewModel: QrCodeScannerViewModel by viewModel() override fun onCreate(savedInstanceState: Bundle?) { diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt index 71b5895573..b4024fa124 100755 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReportActivity.kt @@ -11,6 +11,7 @@ import android.content.Context import android.content.Intent import android.view.Menu import android.view.MenuItem +import android.view.View import android.widget.Toast import androidx.core.view.isVisible import androidx.core.widget.doOnTextChanged @@ -37,6 +38,9 @@ class BugReportActivity : private val viewModel: BugReportViewModel by viewModel() + override val rootView: View + get() = views.mainRoot + private var reportType: ReportType = ReportType.BUG_REPORT override fun initUiAndData() { diff --git a/vector/src/main/java/im/vector/app/features/reactions/EmojiReactionPickerActivity.kt b/vector/src/main/java/im/vector/app/features/reactions/EmojiReactionPickerActivity.kt index 7cd7b79b11..774b41245c 100644 --- a/vector/src/main/java/im/vector/app/features/reactions/EmojiReactionPickerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/reactions/EmojiReactionPickerActivity.kt @@ -13,6 +13,7 @@ import android.graphics.Typeface import android.util.TypedValue import android.view.Menu import android.view.MenuItem +import android.view.View import android.widget.SearchView import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope @@ -55,6 +56,9 @@ class EmojiReactionPickerActivity : override fun getCoordinatorLayout() = views.coordinatorLayout + override val rootView: View + get() = views.coordinatorLayout + override fun getTitleRes() = CommonStrings.title_activity_emoji_reaction_picker @Inject lateinit var emojiCompatFontProvider: EmojiCompatFontProvider diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt index 3940404af9..8a1c75ad61 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt @@ -10,6 +10,7 @@ package im.vector.app.features.roomdirectory import android.content.Context import android.content.Intent import android.os.Bundle +import android.view.View import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.viewModel import com.airbnb.mvrx.withState @@ -41,6 +42,9 @@ class RoomDirectoryActivity : VectorBaseActivity(), Matri override fun getCoordinatorLayout() = views.coordinatorLayout + override val rootView: View + get() = views.coordinatorLayout + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) analyticsScreenName = MobileScreen.ScreenName.RoomDirectory diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt index c3ba0e7041..90be84d848 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt @@ -11,6 +11,7 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.os.Bundle +import android.view.View import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Mavericks import dagger.hilt.android.AndroidEntryPoint @@ -36,6 +37,9 @@ class CreateRoomActivity : VectorBaseActivity() { override fun getCoordinatorLayout() = views.coordinatorLayout + override val rootView: View + get() = views.coordinatorLayout + override fun initUiAndData() { if (isFirstCreation()) { val fragmentArgs: CreateRoomArgs = intent?.extras?.getParcelableCompat(Mavericks.KEY_ARG) ?: return diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewActivity.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewActivity.kt index 77bac7dfa1..b6859f36df 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewActivity.kt @@ -10,6 +10,7 @@ package im.vector.app.features.roomdirectory.roompreview import android.content.Context import android.content.Intent import android.os.Parcelable +import android.view.View import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.addFragment import im.vector.app.core.platform.VectorBaseActivity @@ -73,6 +74,9 @@ class RoomPreviewActivity : VectorBaseActivity() { override fun getCoordinatorLayout() = views.coordinatorLayout + override val rootView: View + get() = views.simpleFragmentContainer + override fun initUiAndData() { if (isFirstCreation()) { val args = intent.getParcelableExtraCompat(ARG) diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileActivity.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileActivity.kt index cf593c039e..43ee03fd58 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileActivity.kt @@ -9,6 +9,7 @@ package im.vector.app.features.roommemberprofile import android.content.Context import android.content.Intent +import android.view.View import android.widget.Toast import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel @@ -37,6 +38,11 @@ class RoomMemberProfileActivity : VectorBaseActivity() { return ActivitySimpleBinding.inflate(layoutInflater) } + override fun getCoordinatorLayout() = views.coordinatorLayout + + override val rootView: View + get() = views.coordinatorLayout + override fun initUiAndData() { if (isFirstCreation()) { val fragmentArgs: RoomMemberProfileArgs = intent?.extras?.getParcelableCompat(Mavericks.KEY_ARG) ?: return diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt index 5bbfab6e18..db5cd3b9cb 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt @@ -9,6 +9,7 @@ package im.vector.app.features.roomprofile import android.content.Context import android.content.Intent +import android.view.View import android.widget.Toast import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Mavericks @@ -68,6 +69,9 @@ class RoomProfileActivity : override fun getCoordinatorLayout() = views.coordinatorLayout + override val rootView: View + get() = views.coordinatorLayout + override fun initUiAndData() { sharedActionViewModel = viewModelProvider.get(RoomProfileSharedActionViewModel::class.java) roomProfileArgs = intent?.extras?.getParcelableCompat(Mavericks.KEY_ARG) ?: return diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailActivity.kt index dc3db0401e..a8a1c31862 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/detail/ui/RoomPollDetailActivity.kt @@ -10,6 +10,7 @@ package im.vector.app.features.roomprofile.polls.detail.ui import android.content.Context import android.content.Intent import android.os.Bundle +import android.view.View import com.airbnb.mvrx.Mavericks import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.addFragment @@ -25,6 +26,11 @@ class RoomPollDetailActivity : VectorBaseActivity() { override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater) + override fun getCoordinatorLayout() = views.coordinatorLayout + + override val rootView: View + get() = views.coordinatorLayout + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt index 30d2e688b6..868564737f 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt @@ -10,6 +10,7 @@ package im.vector.app.features.roomprofile.settings.joinrule import android.content.Context import android.content.Intent import android.os.Bundle +import android.view.View import androidx.core.view.isVisible import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading @@ -40,6 +41,11 @@ class RoomJoinRuleActivity : VectorBaseActivity() { override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater) + override fun getCoordinatorLayout() = views.coordinatorLayout + + override val rootView: View + get() = views.coordinatorLayout + private lateinit var roomProfileArgs: RoomProfileArgs val viewModel: RoomJoinRuleChooseRestrictedViewModel by viewModel() diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsActivity.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsActivity.kt index 62d0917490..c942d8f06a 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsActivity.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsActivity.kt @@ -11,6 +11,7 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.os.Parcelable +import android.view.View import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.preference.Preference @@ -47,6 +48,9 @@ class VectorSettingsActivity : VectorBaseActivity override fun getCoordinatorLayout() = views.coordinatorLayout + override val rootView: View + get() = views.coordinatorLayout + override fun getTitleRes() = CommonStrings.title_activity_settings private var keyToHighlight: String? = null diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/rename/RenameSessionActivity.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/rename/RenameSessionActivity.kt index 3625dee311..7a556e18eb 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/rename/RenameSessionActivity.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/rename/RenameSessionActivity.kt @@ -10,6 +10,7 @@ package im.vector.app.features.settings.devices.v2.rename import android.content.Context import android.content.Intent import android.os.Bundle +import android.view.View import android.view.WindowManager import com.airbnb.mvrx.Mavericks import dagger.hilt.android.AndroidEntryPoint @@ -26,6 +27,11 @@ class RenameSessionActivity : VectorBaseActivity() { override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater) + override fun getCoordinatorLayout() = views.coordinatorLayout + + override val rootView: View + get() = views.coordinatorLayout + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingActivity.kt b/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingActivity.kt index d2e8ffccbf..1ffb93d840 100644 --- a/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingActivity.kt +++ b/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingActivity.kt @@ -7,6 +7,7 @@ package im.vector.app.features.settings.font +import android.view.View import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.addFragment import im.vector.app.core.platform.VectorBaseActivity @@ -17,6 +18,11 @@ class FontScaleSettingActivity : VectorBaseActivity() { override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater) + override fun getCoordinatorLayout() = views.coordinatorLayout + + override val rootView: View + get() = views.coordinatorLayout + override fun initUiAndData() { if (isFirstCreation()) { addFragment(views.simpleFragmentContainer, FontScaleSettingFragment::class.java) diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareActivity.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareActivity.kt index e2a00c516f..3d89a6ebc9 100644 --- a/vector/src/main/java/im/vector/app/features/share/IncomingShareActivity.kt +++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareActivity.kt @@ -9,6 +9,7 @@ package im.vector.app.features.share import android.content.Intent import android.os.Bundle +import android.view.View import com.airbnb.mvrx.viewModel import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.addFragment @@ -46,6 +47,9 @@ class IncomingShareActivity : VectorBaseActivity() { override fun getCoordinatorLayout() = views.coordinatorLayout + override val rootView: View + get() = views.coordinatorLayout + private fun handleAppStarted() { // If we are not logged in, stop the sharing process and open login screen. // In the future, we might want to relaunch the sharing process after login. diff --git a/vector/src/main/java/im/vector/app/features/signout/hard/SignedOutActivity.kt b/vector/src/main/java/im/vector/app/features/signout/hard/SignedOutActivity.kt index c94d9ab7b9..e6b3b817ea 100644 --- a/vector/src/main/java/im/vector/app/features/signout/hard/SignedOutActivity.kt +++ b/vector/src/main/java/im/vector/app/features/signout/hard/SignedOutActivity.kt @@ -10,6 +10,7 @@ package im.vector.app.features.signout.hard import android.content.Context import android.content.Intent import android.os.Bundle +import android.view.View import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivitySignedOutBinding @@ -26,6 +27,9 @@ class SignedOutActivity : VectorBaseActivity() { override fun getBinding() = ActivitySignedOutBinding.inflate(layoutInflater) + override val rootView: View + get() = views.signedOut + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceExploreActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceExploreActivity.kt index 5f060adeb9..7e5d776ca2 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceExploreActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceExploreActivity.kt @@ -11,6 +11,7 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.os.Bundle +import android.view.View import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import com.airbnb.mvrx.Mavericks @@ -38,6 +39,11 @@ class SpaceExploreActivity : VectorBaseActivity(), Matrix override fun getBinding(): ActivitySimpleBinding = ActivitySimpleBinding.inflate(layoutInflater) + override fun getCoordinatorLayout() = views.coordinatorLayout + + override val rootView: View + get() = views.coordinatorLayout + override fun getTitleRes(): Int = CommonStrings.space_explore_activity_title val sharedViewModel: SpaceDirectoryViewModel by viewModel() diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpacePreviewActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/SpacePreviewActivity.kt index d698f2ed35..6b60253b4a 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpacePreviewActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpacePreviewActivity.kt @@ -10,6 +10,7 @@ package im.vector.app.features.spaces import android.content.Context import android.content.Intent import android.os.Bundle +import android.view.View import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Mavericks import dagger.hilt.android.AndroidEntryPoint @@ -29,6 +30,11 @@ class SpacePreviewActivity : VectorBaseActivity() { override fun getBinding(): ActivitySimpleBinding = ActivitySimpleBinding.inflate(layoutInflater) + override fun getCoordinatorLayout() = views.coordinatorLayout + + override val rootView: View + get() = views.coordinatorLayout + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) sharedActionViewModel = viewModelProvider.get(SpacePreviewSharedActionViewModel::class.java) diff --git a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedActivity.kt index cf7b706adf..84c3c78b2a 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedActivity.kt @@ -10,6 +10,7 @@ package im.vector.app.features.spaces.leave import android.content.Context import android.content.Intent import android.os.Bundle +import android.view.View import androidx.core.view.isGone import androidx.core.view.isVisible import com.airbnb.mvrx.Fail @@ -33,6 +34,11 @@ class SpaceLeaveAdvancedActivity : VectorBaseActivity() { override fun getBinding(): ActivitySimpleLoadingBinding = ActivitySimpleLoadingBinding.inflate(layoutInflater) + override fun getCoordinatorLayout() = views.coordinatorLayout + + override val rootView: View + get() = views.coordinatorLayout + override fun getTitleRes(): Int = CommonStrings.space_add_existing_rooms val sharedViewModel: SpaceManageSharedViewModel by viewModel() diff --git a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleActivity.kt index 7a12dedd56..4bb2f0c44a 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleActivity.kt @@ -10,6 +10,7 @@ package im.vector.app.features.spaces.people import android.content.Context import android.content.Intent import android.os.Bundle +import android.view.View import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope @@ -32,6 +33,11 @@ class SpacePeopleActivity : VectorBaseActivity() { override fun getBinding() = ActivitySimpleLoadingBinding.inflate(layoutInflater) + override fun getCoordinatorLayout() = views.coordinatorLayout + + override val rootView: View + get() = views.coordinatorLayout + private lateinit var sharedActionViewModel: SpacePeopleSharedActionViewModel override fun initUiAndData() { diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt index 06fc143e21..7b27ffdd1e 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt @@ -11,6 +11,7 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.os.Parcelable +import android.view.View import android.widget.Toast import androidx.core.app.ActivityCompat import androidx.core.view.isVisible @@ -51,6 +52,9 @@ class UserCodeActivity : VectorBaseActivity(), override fun getCoordinatorLayout() = views.coordinatorLayout + override val rootView: View + get() = views.coordinatorLayout + private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() { override fun onFragmentResumed(fm: FragmentManager, f: Fragment) { if (f is MatrixToBottomSheet) { diff --git a/vector/src/main/java/im/vector/app/features/webview/VectorWebViewActivity.kt b/vector/src/main/java/im/vector/app/features/webview/VectorWebViewActivity.kt index 7af0dc62dc..6e7cb9e468 100644 --- a/vector/src/main/java/im/vector/app/features/webview/VectorWebViewActivity.kt +++ b/vector/src/main/java/im/vector/app/features/webview/VectorWebViewActivity.kt @@ -9,6 +9,7 @@ package im.vector.app.features.webview import android.content.Context import android.content.Intent +import android.view.View import android.webkit.WebChromeClient import android.webkit.WebView import dagger.hilt.android.AndroidEntryPoint @@ -28,6 +29,9 @@ class VectorWebViewActivity : VectorBaseActivity() override fun getBinding() = ActivityVectorWebViewBinding.inflate(layoutInflater) + override val rootView: View + get() = views.mainRoot + val session: Session by lazy { activeSessionHolder.getActiveSession() } diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt index 099f39f67a..917fc02440 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt @@ -20,6 +20,7 @@ import android.content.IntentFilter import android.graphics.drawable.Icon import android.os.Build import android.util.Rational +import android.view.View import androidx.annotation.RequiresApi import androidx.core.app.PictureInPictureModeChangedInfo import androidx.core.content.ContextCompat @@ -77,6 +78,9 @@ class WidgetActivity : VectorBaseActivity() { override fun getBinding() = ActivityWidgetBinding.inflate(layoutInflater) + override val rootView: View + get() = views.mainRoot + override fun getTitleRes() = CommonStrings.room_widget_activity_title override fun initUiAndData() { diff --git a/vector/src/main/res/layout/activity_big_image_viewer.xml b/vector/src/main/res/layout/activity_big_image_viewer.xml index 8f1cf88aff..cd254db50d 100644 --- a/vector/src/main/res/layout/activity_big_image_viewer.xml +++ b/vector/src/main/res/layout/activity_big_image_viewer.xml @@ -1,6 +1,7 @@ @@ -28,4 +29,4 @@ app:layout_constraintTop_toBottomOf="@id/appBarLayout" app:optimizeDisplay="true" /> - \ No newline at end of file + diff --git a/vector/src/main/res/layout/activity_bug_report.xml b/vector/src/main/res/layout/activity_bug_report.xml index 1c019c858d..799ab2fb5f 100644 --- a/vector/src/main/res/layout/activity_bug_report.xml +++ b/vector/src/main/res/layout/activity_bug_report.xml @@ -2,6 +2,7 @@ diff --git a/vector/src/main/res/layout/activity_call.xml b/vector/src/main/res/layout/activity_call.xml index 5734e5f92a..02c90f8e54 100644 --- a/vector/src/main/res/layout/activity_call.xml +++ b/vector/src/main/res/layout/activity_call.xml @@ -97,7 +97,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@android:color/transparent" - android:fitsSystemWindows="true" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" diff --git a/vector/src/main/res/layout/activity_location_sharing.xml b/vector/src/main/res/layout/activity_location_sharing.xml index bbb46de8c7..9e44c72c83 100755 --- a/vector/src/main/res/layout/activity_location_sharing.xml +++ b/vector/src/main/res/layout/activity_location_sharing.xml @@ -1,5 +1,6 @@ @@ -21,4 +22,4 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> - \ No newline at end of file + diff --git a/vector/src/main/res/layout/activity_main.xml b/vector/src/main/res/layout/activity_main.xml index ba5925f000..626eb100d4 100644 --- a/vector/src/main/res/layout/activity_main.xml +++ b/vector/src/main/res/layout/activity_main.xml @@ -3,6 +3,7 @@ diff --git a/vector/src/main/res/layout/activity_widget.xml b/vector/src/main/res/layout/activity_widget.xml index b278bb5a1a..f1222d11df 100755 --- a/vector/src/main/res/layout/activity_widget.xml +++ b/vector/src/main/res/layout/activity_widget.xml @@ -1,6 +1,7 @@ @@ -23,4 +24,4 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> - \ No newline at end of file + diff --git a/vector/src/main/res/layout/fragment_generic_recycler.xml b/vector/src/main/res/layout/fragment_generic_recycler.xml index 472f05092e..1c2eb2fc40 100644 --- a/vector/src/main/res/layout/fragment_generic_recycler.xml +++ b/vector/src/main/res/layout/fragment_generic_recycler.xml @@ -2,6 +2,7 @@ diff --git a/vector/src/main/res/layout/fragment_new_home_detail.xml b/vector/src/main/res/layout/fragment_new_home_detail.xml index d20223a382..32d1dfc5d2 100644 --- a/vector/src/main/res/layout/fragment_new_home_detail.xml +++ b/vector/src/main/res/layout/fragment_new_home_detail.xml @@ -45,7 +45,6 @@ android:id="@+id/appBarLayout" android:layout_width="match_parent" android:layout_height="wrap_content" - android:fitsSystemWindows="true" app:layout_constraintTop_toBottomOf="@id/syncStateView"> Date: Wed, 16 Jul 2025 16:07:49 +0200 Subject: [PATCH 28/46] Fix issue in timeline screen. --- .../im/vector/app/core/utils/ExpandingBottomSheetBehavior.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/core/utils/ExpandingBottomSheetBehavior.kt b/vector/src/main/java/im/vector/app/core/utils/ExpandingBottomSheetBehavior.kt index 7834d6a014..5b84579b2b 100644 --- a/vector/src/main/java/im/vector/app/core/utils/ExpandingBottomSheetBehavior.kt +++ b/vector/src/main/java/im/vector/app/core/utils/ExpandingBottomSheetBehavior.kt @@ -658,7 +658,8 @@ class ExpandingBottomSheetBehavior : CoordinatorLayout.Behavior { val insetsType = WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.ime() val imeInsets = insets.getInsets(insetsType) insetTop = imeInsets.top - insetBottom = imeInsets.bottom + // Now that edgeToEdge is enabled, disable the bottom padding. + insetBottom = 0 insetLeft = imeInsets.left insetRight = imeInsets.right From f421eadd01de0d4eeafd6948367812d636fc4430 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 16 Jul 2025 16:40:56 +0200 Subject: [PATCH 29/46] Code cleanup --- .../app/features/home/room/detail/TimelineFragment.kt | 8 -------- 1 file changed, 8 deletions(-) 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 32f0c4e9c2..01be29d84f 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 @@ -32,11 +32,9 @@ import androidx.core.net.toUri import androidx.core.text.toSpannable import androidx.core.util.Pair import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat import androidx.core.view.forEach import androidx.core.view.isInvisible import androidx.core.view.isVisible -import androidx.core.view.updatePadding import androidx.fragment.app.setFragmentResultListener import androidx.lifecycle.lifecycleScope import androidx.lifecycle.withResumed @@ -409,12 +407,6 @@ class TimelineFragment : is RoomDetailViewEvents.RevokeFilePermission -> revokeFilePermission(it) } } - - //ViewCompat.setOnApplyWindowInsetsListener(views.coordinatorLayout) { _, insets -> - // val imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime()) - // views.voiceMessageRecorderContainer.updatePadding(bottom = imeInsets.bottom) - // insets - //} } private fun setupBackPressHandling() { From 856592f9a88e2dcb84fda3464c84e1e23387ee58 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 17 Jul 2025 11:11:37 +0200 Subject: [PATCH 30/46] Gate the Manually verify device behind devtool flag --- ...ceVerificationInfoBottomSheetController.kt | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt index 05732508b2..d786bf8a60 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetController.kt @@ -17,6 +17,7 @@ import im.vector.app.core.ui.list.genericFooterItem import im.vector.app.core.ui.list.genericItem import im.vector.app.core.ui.views.toDrawableRes import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationActionItem +import im.vector.app.features.settings.VectorPreferences import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import im.vector.lib.strings.CommonStrings import org.matrix.android.sdk.api.extensions.orFalse @@ -26,7 +27,8 @@ import javax.inject.Inject class DeviceVerificationInfoBottomSheetController @Inject constructor( private val stringProvider: StringProvider, - private val colorProvider: ColorProvider + private val colorProvider: ColorProvider, + private val vectorPreferences: VectorPreferences ) : TypedEpoxyController() { @@ -244,17 +246,19 @@ class DeviceVerificationInfoBottomSheetController @Inject constructor( private fun addVerifyActions(cryptoDeviceInfo: CryptoDeviceInfo) { val host = this - bottomSheetDividerItem { - id("verifyDiv") - } - bottomSheetVerificationActionItem { - id("verify_text") - title(host.stringProvider.getString(CommonStrings.cross_signing_verify_by_text)) - titleColor(host.colorProvider.getColorFromAttribute(com.google.android.material.R.attr.colorPrimary)) - iconRes(R.drawable.ic_arrow_right) - iconColor(host.colorProvider.getColorFromAttribute(com.google.android.material.R.attr.colorPrimary)) - listener { - host.callback?.onAction(DevicesAction.VerifyMyDeviceManually(cryptoDeviceInfo.deviceId)) + if (vectorPreferences.developerMode()) { + bottomSheetDividerItem { + id("verifyDiv") + } + bottomSheetVerificationActionItem { + id("verify_text") + title(host.stringProvider.getString(CommonStrings.cross_signing_verify_by_text)) + titleColor(host.colorProvider.getColorFromAttribute(com.google.android.material.R.attr.colorPrimary)) + iconRes(R.drawable.ic_arrow_right) + iconColor(host.colorProvider.getColorFromAttribute(com.google.android.material.R.attr.colorPrimary)) + listener { + host.callback?.onAction(DevicesAction.VerifyMyDeviceManually(cryptoDeviceInfo.deviceId)) + } } } bottomSheetDividerItem { From d26a25a4f85d6e50a7a53bcf99a1914d96e442f4 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 17 Jul 2025 11:49:58 +0200 Subject: [PATCH 31/46] add changelog --- changelog.d/9058.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/9058.misc diff --git a/changelog.d/9058.misc b/changelog.d/9058.misc new file mode 100644 index 0000000000..47ae9f75fe --- /dev/null +++ b/changelog.d/9058.misc @@ -0,0 +1 @@ + Hide the "Manually Verify by Text" option behind devtool flag. From 926e64bb6ea3798c24ab7d088b2ceb24a1a306d9 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 23 Jul 2025 13:30:34 +0200 Subject: [PATCH 32/46] misc (sdk) : update matrix patterns to handle new roomId format (and so fix permalinks) --- .../matrix/android/sdk/api/MatrixPatterns.kt | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt index 0f7e9ca6a8..4ec809a6d0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt @@ -30,15 +30,21 @@ object MatrixPatterns { // Note: TLD is not mandatory (localhost, IP address...) private const val DOMAIN_REGEX = ":[A-Z0-9.-]+(:[0-9]{2,5})?" + private const val BASE_64_ALPHABET = "[0-9A-Za-z/\\+=]+" + private const val BASE_64_URL_SAFE_ALPHABET = "[0-9A-Za-z/\\-_]+" + // regex pattern to find matrix user ids in a string. // See https://matrix.org/docs/spec/appendices#historical-user-ids private const val MATRIX_USER_IDENTIFIER_REGEX = "@[A-Z0-9\\x21-\\x39\\x3B-\\x7F]+$DOMAIN_REGEX" val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = MATRIX_USER_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE) // regex pattern to find room ids in a string. - private const val MATRIX_ROOM_IDENTIFIER_REGEX = "![A-Z0-9]+$DOMAIN_REGEX" + private const val MATRIX_ROOM_IDENTIFIER_REGEX = "^!.+$DOMAIN_REGEX$" private val PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER = MATRIX_ROOM_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE) + private const val MATRIX_ROOM_IDENTIFIER_DOMAINLESS_REGEX = "!$BASE_64_URL_SAFE_ALPHABET" + private val PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER_DOMAINLESS = MATRIX_ROOM_IDENTIFIER_DOMAINLESS_REGEX.toRegex() + // regex pattern to find room aliases in a string. private const val MATRIX_ROOM_ALIAS_REGEX = "#[A-Z0-9._%#@=+-]+$DOMAIN_REGEX" private val PATTERN_CONTAIN_MATRIX_ALIAS = MATRIX_ROOM_ALIAS_REGEX.toRegex(RegexOption.IGNORE_CASE) @@ -48,11 +54,11 @@ object MatrixPatterns { private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER = MATRIX_EVENT_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE) // regex pattern to find message ids in a string. - private const val MATRIX_EVENT_IDENTIFIER_V3_REGEX = "\\$[A-Z0-9/+]+" + private const val MATRIX_EVENT_IDENTIFIER_V3_REGEX = "\\$$BASE_64_ALPHABET" private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3 = MATRIX_EVENT_IDENTIFIER_V3_REGEX.toRegex(RegexOption.IGNORE_CASE) // Ref: https://matrix.org/docs/spec/rooms/v4#event-ids - private const val MATRIX_EVENT_IDENTIFIER_V4_REGEX = "\\$[A-Z0-9\\-_]+" + private const val MATRIX_EVENT_IDENTIFIER_V4_REGEX = "\\$$BASE_64_URL_SAFE_ALPHABET" private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4 = MATRIX_EVENT_IDENTIFIER_V4_REGEX.toRegex(RegexOption.IGNORE_CASE) // regex pattern to find group ids in a string. @@ -76,7 +82,10 @@ object MatrixPatterns { PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER, PATTERN_CONTAIN_MATRIX_ALIAS, PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER, + PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER_DOMAINLESS, PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER, + PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3, + PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4, PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER ) @@ -97,7 +106,9 @@ object MatrixPatterns { * @return true if the string is a valid room Id */ fun isRoomId(str: String?): Boolean { - return str != null && str matches PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER + return str != null && + (str matches PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER || + str matches PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER_DOMAINLESS) } /** From fd3f7e3a240f287b6bd10fac033e88f268f3d57c Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 24 Jul 2025 22:25:10 +0200 Subject: [PATCH 33/46] change (power level) : support new InfinitePowerLevel (first draft) --- .../src/main/res/values/strings.xml | 2 + .../sdk/session/space/SpaceHierarchyTest.kt | 8 +-- .../SenderNotificationPermissionCondition.kt | 8 +-- .../sdk/api/session/room/RoomExtensions.kt | 18 ++++++ .../session/room/model/PowerLevelsContent.kt | 32 ++++++---- .../room/model/create/RoomCreateContent.kt | 26 +++++++- .../sdk/api/session/room/powerlevels/Role.kt | 30 ++++----- ...owerLevelsHelper.kt => RoomPowerLevels.kt} | 60 ++++++++++++------ .../room/powerlevels/UserPowerLevel.kt | 35 +++++++++++ .../session/permalinks/ViaParameterFinder.kt | 13 +--- .../pushers/DefaultConditionResolver.kt | 16 ++--- .../EventRelationsAggregationProcessor.kt | 16 ++--- .../poll/DefaultPollAggregationProcessor.kt | 6 +- .../poll/PollAggregationProcessor.kt | 4 +- .../room/powerlevels/RoomPowerLevels.kt | 23 +++++++ .../room/state/StateEventDataSource.kt | 1 + .../room/summary/RoomSummaryUpdater.kt | 21 +++++-- .../room/version/DefaultRoomVersionService.kt | 15 +++-- .../session/space/DefaultSpaceService.kt | 18 +++--- .../internal/session/widgets/WidgetManager.kt | 13 +--- .../DefaultPollAggregationProcessorTest.kt | 10 +-- ...faultCreateLocalRoomStateEventsTaskTest.kt | 4 +- .../home/room/detail/TimelineViewModel.kt | 9 ++- .../composer/MessageComposerViewModel.kt | 3 +- .../action/MessageActionsViewModel.kt | 11 ++-- .../factory/MergedHeaderItemFactory.kt | 15 +++-- .../timeline/format/NoticeEventFormatter.kt | 6 +- .../location/LocationSharingViewModel.kt | 7 +-- .../powerlevel/PowerLevelsFlowFactory.kt | 25 ++++++-- .../RoomMemberProfileAction.kt | 3 +- .../RoomMemberProfileController.kt | 11 ++-- .../RoomMemberProfileFragment.kt | 8 +-- .../RoomMemberProfileViewEvents.kt | 5 +- .../RoomMemberProfileViewModel.kt | 38 ++++++------ .../RoomMemberProfileViewState.kt | 4 +- .../powerlevel/EditPowerLevelDialogs.kt | 27 ++++---- .../roomprofile/RoomProfileViewModel.kt | 12 ++-- .../roomprofile/alias/RoomAliasViewModel.kt | 7 +-- .../banned/RoomBannedMemberListViewModel.kt | 12 ++-- .../members/RoomMemberListComparator.kt | 53 ++++++++++++++++ .../members/RoomMemberListController.kt | 37 ++++++----- .../members/RoomMemberListViewModel.kt | 62 +++++++++---------- .../members/RoomMemberListViewState.kt | 11 +++- .../members/RoomMemberSummaryComparator.kt | 52 ---------------- .../roomprofile/permissions/RoleFormatter.kt | 5 +- .../permissions/RoomPermissionsAction.kt | 3 +- .../permissions/RoomPermissionsController.kt | 26 +++----- .../permissions/RoomPermissionsFragment.kt | 6 +- .../permissions/RoomPermissionsViewModel.kt | 42 +++++++------ .../settings/RoomSettingsViewModel.kt | 23 +++---- .../app/features/spaces/SpaceMenuViewModel.kt | 20 +++--- .../spaces/create/CreateSpaceViewModelTask.kt | 5 +- .../spaces/explore/SpaceDirectoryViewModel.kt | 13 ++-- .../leave/SpaceLeaveAdvancedViewModel.kt | 15 +++-- .../people/SpacePeopleListController.kt | 12 ++-- .../spaces/share/ShareSpaceViewModel.kt | 7 +-- .../usecase/StartVoiceBroadcastUseCase.kt | 11 ++-- .../features/widgets/WidgetPostAPIHandler.kt | 12 ++-- .../app/features/widgets/WidgetViewModel.kt | 13 ++-- .../app/features/MemberListViewModelTest.kt | 4 +- 60 files changed, 566 insertions(+), 448 deletions(-) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/{PowerLevelsHelper.kt => RoomPowerLevels.kt} (59%) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/UserPowerLevel.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/powerlevels/RoomPowerLevels.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListComparator.kt delete mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberSummaryComparator.kt diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 803510ed76..02fef53306 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -120,6 +120,7 @@ %1$s modified %2$s widget You modified %1$s widget + Owner Admin Moderator Default @@ -2383,6 +2384,7 @@ Invites Users + Owner in %1$s Admin in %1$s Moderator in %1$s Default in %1$s diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt index de661275a7..6c9d0ad983 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt @@ -40,7 +40,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.RestrictedRoomPreset -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest @@ -500,12 +500,12 @@ class SpaceHierarchyTest : InstrumentedTest { room.stateService().sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent!!) commonTestHelper.retryPeriodically { - val powerLevelsHelper = aliceSession.getRoom(bobRoomId)!! + val roomPowerLevels = aliceSession.getRoom(bobRoomId)!! .getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) ?.content ?.toModel() - ?.let { PowerLevelsHelper(it) } - powerLevelsHelper!!.isUserAllowedToSend(aliceSession.myUserId, true, EventType.STATE_SPACE_PARENT) + ?.let { RoomPowerLevels(it) } + roomPowerLevels!!.isUserAllowedToSend(aliceSession.myUserId, true, EventType.STATE_SPACE_PARENT) } aliceSession.spaceService().setSpaceParent(bobRoomId, spaceAInfo.spaceId, false, listOf(bobSession.sessionParams.homeServerHost ?: "")) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/SenderNotificationPermissionCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/SenderNotificationPermissionCondition.kt index 82f5023c2f..c3e9b9f527 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/SenderNotificationPermissionCondition.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/SenderNotificationPermissionCondition.kt @@ -16,8 +16,7 @@ package org.matrix.android.sdk.api.session.pushrules import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels class SenderNotificationPermissionCondition( /** @@ -35,8 +34,7 @@ class SenderNotificationPermissionCondition( override fun technicalDescription() = "User power level <$key>" - fun isSatisfied(event: Event, powerLevels: PowerLevelsContent): Boolean { - val powerLevelsHelper = PowerLevelsHelper(powerLevels) - return event.senderId != null && powerLevelsHelper.getUserPowerLevelValue(event.senderId) >= powerLevels.notificationLevel(key) + fun isSatisfied(event: Event, roomPowerLevels: RoomPowerLevels): Boolean { + return event.senderId != null && roomPowerLevels.isUserAbleToTriggerNotification(event.senderId, key) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt index b30c60554f..29638857bc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt @@ -17,7 +17,13 @@ package org.matrix.android.sdk.api.session.room import org.matrix.android.sdk.api.query.QueryStateEventValue +import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent +import org.matrix.android.sdk.api.session.room.model.create.getRoomCreateContentWithSender +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent /** @@ -34,3 +40,15 @@ fun Room.getTimelineEvent(eventId: String): TimelineEvent? = */ fun Room.getStateEvent(eventType: String, stateKey: QueryStateEventValue): Event? = stateService().getStateEvent(eventType, stateKey) + +/** + * Get the current RoomPowerLevels of the room. + */ +fun Room.getRoomPowerLevels(): RoomPowerLevels { + val powerLevelsContent = getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty)?.content?.toModel() + val roomCreateContent = getStateEvent(EventType.STATE_ROOM_CREATE, QueryStringValue.IsEmpty)?.getRoomCreateContentWithSender() + return RoomPowerLevels( + powerLevelsContent, + roomCreateContent + ) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt index 0329828130..894640a488 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt @@ -18,7 +18,8 @@ package org.matrix.android.sdk.api.session.room.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.api.session.room.powerlevels.Role +import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent.Companion.NOTIFICATIONS_ROOM_KEY +import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel /** * Class representing the EventType.EVENT_TYPE_STATE_ROOM_POWER_LEVELS state event content. @@ -88,7 +89,7 @@ data class PowerLevelsContent( * Get the notification level for a dedicated key. * * @param key the notification key - * @return the level, default to Moderator if the key is not found + * @return the level */ fun notificationLevel(key: String): Int { return when (val value = notifications.orEmpty()[key]) { @@ -96,10 +97,9 @@ data class PowerLevelsContent( is String -> value.toInt() is Double -> value.toInt() is Int -> value - else -> Role.Moderator.value + else -> defaultNotificationLevel(key) } } - companion object { /** * Key to use for content.notifications and get the level required to trigger an @room notification. Defaults to 50 if unspecified. @@ -108,11 +108,21 @@ data class PowerLevelsContent( } } +private fun defaultNotificationLevel(key: String): Int { + return when (key) { + NOTIFICATIONS_ROOM_KEY -> UserPowerLevel.Moderator.value + else -> UserPowerLevel.User.value + } +} + + // Fallback to default value, defined in the Matrix specification -fun PowerLevelsContent.banOrDefault() = ban ?: Role.Moderator.value -fun PowerLevelsContent.kickOrDefault() = kick ?: Role.Moderator.value -fun PowerLevelsContent.inviteOrDefault() = invite ?: Role.Moderator.value -fun PowerLevelsContent.redactOrDefault() = redact ?: Role.Moderator.value -fun PowerLevelsContent.eventsDefaultOrDefault() = eventsDefault ?: Role.Default.value -fun PowerLevelsContent.usersDefaultOrDefault() = usersDefault ?: Role.Default.value -fun PowerLevelsContent.stateDefaultOrDefault() = stateDefault ?: Role.Moderator.value +fun PowerLevelsContent?.banOrDefault() = this?.ban ?: UserPowerLevel.Moderator.value +fun PowerLevelsContent?.kickOrDefault() = this?.kick ?: UserPowerLevel.Moderator.value +fun PowerLevelsContent?.inviteOrDefault() = this?.invite ?: UserPowerLevel.User.value +fun PowerLevelsContent?.redactOrDefault() = this?.redact ?: UserPowerLevel.Moderator.value +fun PowerLevelsContent?.eventsDefaultOrDefault() = this?.eventsDefault ?: UserPowerLevel.User.value +fun PowerLevelsContent?.usersDefaultOrDefault() = this?.usersDefault ?: UserPowerLevel.User.value +fun PowerLevelsContent?.stateDefaultOrDefault() = this?.stateDefault ?: UserPowerLevel.Moderator.value + +fun PowerLevelsContent?.notificationLevelOrDefault(key: String) = this?.notificationLevel(key) ?: defaultNotificationLevel(key) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomCreateContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomCreateContent.kt index d73c941a86..2408f4a004 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomCreateContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomCreateContent.kt @@ -18,15 +18,39 @@ package org.matrix.android.sdk.api.session.room.model.create import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toModel /** * Content of a m.room.create type event. */ @JsonClass(generateAdapter = true) data class RoomCreateContent( + // Creator should be replaced by the sender of the event @Json(name = "creator") val creator: String? = null, @Json(name = "room_version") val roomVersion: String? = null, @Json(name = "predecessor") val predecessor: Predecessor? = null, // Defines the room type, see #RoomType (user extensible) - @Json(name = "type") val type: String? = null + @Json(name = "type") val type: String? = null, + @Json(name = "additional_creators") val additionalCreators: List? = null, ) + +data class RoomCreateContentWithSender( + val senderId: String, + val inner: RoomCreateContent +) { + val creators = setOf(senderId) + inner.additionalCreators.orEmpty().toSet() +} + +fun Event.getRoomCreateContentWithSender(): RoomCreateContentWithSender? { + if (this.type != EventType.STATE_ROOM_CREATE) return null + val innerContent = getClearContent().toModel() ?: return null + val senderId = senderId ?: return null + return RoomCreateContentWithSender(senderId, innerContent) +} + +fun RoomCreateContent.explicitlyPrivilegeRoomCreators(): Boolean { + val supportedRoomVersions = listOf("org.matrix.hydra.11", "12") + return supportedRoomVersions.contains(roomVersion) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/Role.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/Role.kt index c5cc573458..57cd75e70d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/Role.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/Role.kt @@ -17,26 +17,22 @@ package org.matrix.android.sdk.api.session.room.powerlevels -sealed class Role(open val value: Int) : Comparable { - object Admin : Role(100) - object Moderator : Role(50) - object Default : Role(0) - data class Custom(override val value: Int) : Role(value) +enum class Role { + Creator, + SuperAdmin, + Admin, + Moderator, + User; - override fun compareTo(other: Role): Int { - return value.compareTo(other.value) - } companion object { - - // Order matters, default value should be checked after defined roles - fun fromValue(value: Int, default: Int): Role { - return when (value) { - Admin.value -> Admin - Moderator.value -> Moderator - Default.value, - default -> Default - else -> Custom(value) + fun getSuggestedRole(userPowerLevel: UserPowerLevel): Role { + return when { + userPowerLevel == UserPowerLevel.Infinite -> Creator + userPowerLevel >= UserPowerLevel.SuperAdmin -> SuperAdmin + userPowerLevel >= UserPowerLevel.Admin -> Admin + userPowerLevel >= UserPowerLevel.Moderator -> Moderator + else -> User } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/RoomPowerLevels.kt similarity index 59% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/RoomPowerLevels.kt index 36993074aa..d7fbeb8769 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/RoomPowerLevels.kt @@ -19,17 +19,23 @@ package org.matrix.android.sdk.api.session.room.powerlevels import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.banOrDefault +import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContentWithSender +import org.matrix.android.sdk.api.session.room.model.create.explicitlyPrivilegeRoomCreators import org.matrix.android.sdk.api.session.room.model.eventsDefaultOrDefault import org.matrix.android.sdk.api.session.room.model.inviteOrDefault import org.matrix.android.sdk.api.session.room.model.kickOrDefault +import org.matrix.android.sdk.api.session.room.model.notificationLevelOrDefault import org.matrix.android.sdk.api.session.room.model.redactOrDefault import org.matrix.android.sdk.api.session.room.model.stateDefaultOrDefault import org.matrix.android.sdk.api.session.room.model.usersDefaultOrDefault /** - * This class is an helper around PowerLevelsContent. + * This class is an helper around PowerLevelsContent and RoomCreateContent. */ -class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) { +class RoomPowerLevels( + val powerLevelsContent: PowerLevelsContent?, + private val roomCreateContent: RoomCreateContentWithSender?, +) { /** * Returns the user power level of a dedicated user Id. @@ -37,10 +43,14 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) { * @param userId the user id * @return the power level */ - fun getUserPowerLevelValue(userId: String): Int { - return powerLevelsContent.users + fun getUserPowerLevel(userId: String): UserPowerLevel { + if (shouldGiveInfinitePowerLevel(userId)) return UserPowerLevel.Infinite + if (powerLevelsContent == null) return UserPowerLevel.User + val value = powerLevelsContent.users ?.get(userId) ?: powerLevelsContent.usersDefaultOrDefault() + + return UserPowerLevel.Value(value) } /** @@ -50,9 +60,8 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) { * @return the power level */ fun getUserRole(userId: String): Role { - val value = getUserPowerLevelValue(userId) - // I think we should use powerLevelsContent.usersDefault, but Ganfra told me that it was like that on riot-Web - return Role.fromValue(value, powerLevelsContent.eventsDefaultOrDefault()) + val value = getUserPowerLevel(userId) + return Role.getSuggestedRole(value) } /** @@ -65,14 +74,14 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) { */ fun isUserAllowedToSend(userId: String, isState: Boolean, eventType: String?): Boolean { return if (userId.isNotEmpty()) { - val powerLevel = getUserPowerLevelValue(userId) - val minimumPowerLevel = powerLevelsContent.events?.get(eventType) + val powerLevel = getUserPowerLevel(userId) + val minimumPowerLevel = powerLevelsContent?.events?.get(eventType) ?: if (isState) { powerLevelsContent.stateDefaultOrDefault() } else { powerLevelsContent.eventsDefaultOrDefault() } - powerLevel >= minimumPowerLevel + powerLevel >= UserPowerLevel.Value(minimumPowerLevel) } else false } @@ -82,8 +91,8 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) { * @return true if able to invite */ fun isUserAbleToInvite(userId: String): Boolean { - val powerLevel = getUserPowerLevelValue(userId) - return powerLevel >= powerLevelsContent.inviteOrDefault() + val powerLevel = getUserPowerLevel(userId) + return powerLevel >= UserPowerLevel.Value(powerLevelsContent.inviteOrDefault()) } /** @@ -92,8 +101,8 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) { * @return true if able to ban */ fun isUserAbleToBan(userId: String): Boolean { - val powerLevel = getUserPowerLevelValue(userId) - return powerLevel >= powerLevelsContent.banOrDefault() + val powerLevel = getUserPowerLevel(userId) + return powerLevel >= UserPowerLevel.Value(powerLevelsContent.banOrDefault()) } /** @@ -102,8 +111,8 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) { * @return true if able to kick */ fun isUserAbleToKick(userId: String): Boolean { - val powerLevel = getUserPowerLevelValue(userId) - return powerLevel >= powerLevelsContent.kickOrDefault() + val powerLevel = getUserPowerLevel(userId) + return powerLevel >= UserPowerLevel.Value(powerLevelsContent.kickOrDefault()) } /** @@ -112,7 +121,22 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) { * @return true if able to redact */ fun isUserAbleToRedact(userId: String): Boolean { - val powerLevel = getUserPowerLevelValue(userId) - return powerLevel >= powerLevelsContent.redactOrDefault() + val powerLevel = getUserPowerLevel(userId) + return powerLevel >= UserPowerLevel.Value(powerLevelsContent.redactOrDefault()) + } + + fun isUserAbleToTriggerNotification(userId: String, notificationKey: String): Boolean { + val userPowerLevel = getUserPowerLevel(userId) + val notificationPowerLevel = UserPowerLevel.Value(powerLevelsContent.notificationLevelOrDefault(key = notificationKey)) + return userPowerLevel >= notificationPowerLevel + } + + private fun shouldGiveInfinitePowerLevel(userId: String): Boolean { + if (roomCreateContent == null) return false + return if (roomCreateContent.inner.explicitlyPrivilegeRoomCreators()) { + roomCreateContent.creators.contains(userId) + } else { + false + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/UserPowerLevel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/UserPowerLevel.kt new file mode 100644 index 0000000000..8932f16685 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/UserPowerLevel.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2025 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 org.matrix.android.sdk.api.session.room.powerlevels + +sealed interface UserPowerLevel : Comparable { + data object Infinite : UserPowerLevel + + @JvmInline + value class Value(val value: Int) : UserPowerLevel + + override fun compareTo(other: UserPowerLevel): Int { + return when (this) { + Infinite -> when (other) { + Infinite -> 0 + is Value -> 1 + } + is Value -> when (other) { + Infinite -> -1 + is Value -> value.compareTo(other.value) + } + } + } + + companion object { + val User = Value(0) + val Moderator = Value(50) + val Admin = Value(100) + val SuperAdmin = Value(150) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt index 5fb20bb259..7f1277864c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt @@ -17,15 +17,11 @@ package org.matrix.android.sdk.internal.session.permalinks import org.matrix.android.sdk.api.MatrixPatterns.getServerName -import org.matrix.android.sdk.api.query.QueryStringValue -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.room.RoomGetter +import org.matrix.android.sdk.internal.session.room.powerlevels.getRoomPowerLevels import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import java.net.URLEncoder import javax.inject.Inject @@ -101,10 +97,7 @@ internal class ViaParameterFinder @Inject constructor( } fun userCanInvite(userId: String, roomId: String): Boolean { - val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) - ?.content?.toModel() - ?.let { PowerLevelsHelper(it) } - - return powerLevelsHelper?.isUserAbleToInvite(userId) ?: false + val roomPowerLevels = stateEventDataSource.getRoomPowerLevels(roomId) + return roomPowerLevels.isUserAbleToInvite(userId) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt index c2310f4fda..c1fb93b3ff 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt @@ -24,15 +24,20 @@ import org.matrix.android.sdk.api.session.pushrules.ContainsDisplayNameCondition import org.matrix.android.sdk.api.session.pushrules.EventMatchCondition import org.matrix.android.sdk.api.session.pushrules.RoomMemberCountCondition import org.matrix.android.sdk.api.session.pushrules.SenderNotificationPermissionCondition +import org.matrix.android.sdk.api.session.room.getRoomPowerLevels import org.matrix.android.sdk.api.session.room.getStateEvent import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent +import org.matrix.android.sdk.api.session.room.model.create.getRoomCreateContentWithSender +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.room.RoomGetter +import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import javax.inject.Inject internal class DefaultConditionResolver @Inject constructor( private val roomGetter: RoomGetter, - @UserId private val userId: String + @UserId private val userId: String, + private val stateEventDataSource: StateEventDataSource, ) : ConditionResolver { override fun resolveEventMatchCondition( @@ -55,13 +60,8 @@ internal class DefaultConditionResolver @Inject constructor( ): Boolean { val roomId = event.roomId ?: return false val room = roomGetter.getRoom(roomId) ?: return false - - val powerLevelsContent = room.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) - ?.content - ?.toModel() - ?: PowerLevelsContent() - - return condition.isSatisfied(event, powerLevelsContent) + val roomPowerLevels = room.getRoomPowerLevels() + return condition.isSatisfied(event, roomPowerLevels) } override fun resolveContainsDisplayNameCondition( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt index edc10bd187..3b963c13cb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.session.room import io.realm.Realm -import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.crypto.verification.VerificationState import org.matrix.android.sdk.api.session.events.model.AggregatedAnnotation import org.matrix.android.sdk.api.session.events.model.Event @@ -27,7 +26,6 @@ import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventCon import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedContent import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent @@ -36,7 +34,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.crypto.verification.toState import org.matrix.android.sdk.internal.database.helper.findRootThreadEvent @@ -62,6 +60,7 @@ import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.LiveLocationAggregationProcessor import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollAggregationProcessor import org.matrix.android.sdk.internal.session.room.aggregation.utd.EncryptedReferenceAggregationProcessor +import org.matrix.android.sdk.internal.session.room.powerlevels.getRoomPowerLevels import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber @@ -216,9 +215,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } in EventType.POLL_END.values -> { sessionManager.getSessionComponent(sessionId)?.session()?.let { session -> - getPowerLevelsHelper(event.roomId)?.let { - pollAggregationProcessor.handlePollEndEvent(session, it, realm, event) - } + val roomPowerLevels = stateEventDataSource.getRoomPowerLevels(event.roomId) + pollAggregationProcessor.handlePollEndEvent(session, roomPowerLevels, realm, event) } } in EventType.STATE_ROOM_BEACON_INFO.values -> { @@ -381,12 +379,6 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } } - private fun getPowerLevelsHelper(roomId: String): PowerLevelsHelper? { - return stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) - ?.content?.toModel() - ?.let { PowerLevelsHelper(it) } - } - private fun handleInitialAggregatedRelations( realm: Realm, event: Event, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt index ca224cd543..5a6648934e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt @@ -32,7 +32,7 @@ import org.matrix.android.sdk.api.session.room.model.VoteInfo import org.matrix.android.sdk.api.session.room.model.VoteSummary import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.internal.database.mapper.ContentMapper @@ -160,13 +160,13 @@ internal class DefaultPollAggregationProcessor @Inject constructor( return true } - override fun handlePollEndEvent(session: Session, powerLevelsHelper: PowerLevelsHelper, realm: Realm, event: Event): Boolean { + override fun handlePollEndEvent(session: Session, roomPowerLevels: RoomPowerLevels, realm: Realm, event: Event): Boolean { val roomId = event.roomId ?: return false val pollEventId = event.getRelationContent()?.eventId ?: return false val pollOwnerId = getPollEvent(session, roomId, pollEventId)?.root?.senderId val isPollOwner = pollOwnerId == event.senderId - if (!isPollOwner && !powerLevelsHelper.isUserAbleToRedact(event.senderId ?: "")) { + if (!isPollOwner && !roomPowerLevels.isUserAbleToRedact(event.senderId ?: "")) { return false } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessor.kt index 33a69b720a..578ba6064e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessor.kt @@ -19,7 +19,7 @@ package org.matrix.android.sdk.internal.session.room.aggregation.poll import io.realm.Realm import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels internal interface PollAggregationProcessor { /** @@ -48,7 +48,7 @@ internal interface PollAggregationProcessor { */ fun handlePollEndEvent( session: Session, - powerLevelsHelper: PowerLevelsHelper, + roomPowerLevels: RoomPowerLevels, realm: Realm, event: Event ): Boolean diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/powerlevels/RoomPowerLevels.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/powerlevels/RoomPowerLevels.kt new file mode 100644 index 0000000000..d3556324e7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/powerlevels/RoomPowerLevels.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2025 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 org.matrix.android.sdk.internal.session.room.powerlevels + +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent +import org.matrix.android.sdk.api.session.room.model.create.getRoomCreateContentWithSender +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels +import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource + +internal fun StateEventDataSource.getRoomPowerLevels(roomId: String): RoomPowerLevels { + val powerLevelsContent = getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) + ?.content?.toModel() + val roomCreateContent = getStateEvent(roomId, EventType.STATE_ROOM_CREATE, QueryStringValue.IsEmpty)?.getRoomCreateContentWithSender() + return RoomPowerLevels(powerLevelsContent, roomCreateContent) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt index 723d604ad4..433a4399a3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt @@ -96,3 +96,4 @@ internal class StateEventDataSource @Inject constructor( } } } + diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index cbb75398c4..8820af2034 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -34,7 +34,8 @@ import org.matrix.android.sdk.api.session.room.model.RoomTopicContent import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.VersioningState import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContentWithSender +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.sync.model.RoomSyncSummary import org.matrix.android.sdk.api.session.sync.model.RoomSyncUnreadNotifications @@ -313,13 +314,25 @@ internal class RoomSummaryUpdater @Inject constructor( // check if sender can post child relation in parent? val senderId = parentInfo.stateEventSender val parentRoomId = parentInfo.roomId - val powerLevelsHelper = CurrentStateEventEntity + val powerLevelsContent = CurrentStateEventEntity .getOrNull(realm, parentRoomId, "", EventType.STATE_ROOM_POWER_LEVELS) ?.root ?.let { ContentMapper.map(it.content).toModel() } - ?.let { PowerLevelsHelper(it) } - isValidRelation = powerLevelsHelper?.isUserAllowedToSend(senderId, true, EventType.STATE_SPACE_CHILD) ?: false + val roomCreateContent = CurrentStateEventEntity + .getOrNull(realm, parentRoomId, "", EventType.STATE_ROOM_CREATE) + ?.root + ?.let { + val content = ContentMapper.map(it.content).toModel() + val sender = it.sender + if (content != null && sender != null) { + RoomCreateContentWithSender(sender, content) + } else { + null + } + } + val roomPowerLevels = RoomPowerLevels(powerLevelsContent, roomCreateContent) + isValidRelation = roomPowerLevels.isUserAllowedToSend(senderId, true, EventType.STATE_SPACE_CHILD) } if (isValidRelation) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt index 0bde3a11d2..fd99bb7158 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt @@ -25,7 +25,8 @@ import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.homeserver.RoomVersionStatus import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.model.create.getRoomCreateContentWithSender +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.version.RoomVersionService import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesDataSource import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource @@ -71,11 +72,17 @@ internal class DefaultRoomVersionService @AssistedInject constructor( } override fun userMayUpgradeRoom(userId: String): Boolean { - val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) + val powerLevelsContent = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) ?.content?.toModel() - ?.let { PowerLevelsHelper(it) } - return powerLevelsHelper?.isUserAllowedToSend(userId, true, EventType.STATE_ROOM_TOMBSTONE) ?: false + val roomCreateContent = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_CREATE, QueryStringValue.IsEmpty) + ?.getRoomCreateContentWithSender() + + val roomPowerLevels = RoomPowerLevels( + powerLevelsContent = powerLevelsContent, + roomCreateContent = roomCreateContent + ) + return roomPowerLevels.isUserAllowedToSend(userId, true, EventType.STATE_ROOM_TOMBSTONE) } companion object { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt index cd13b03017..80ecc4fa81 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt @@ -35,8 +35,9 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.powerlevels.Role +import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel import org.matrix.android.sdk.api.session.space.CreateSpaceParams import org.matrix.android.sdk.api.session.space.JoinSpaceResult import org.matrix.android.sdk.api.session.space.Space @@ -47,11 +48,13 @@ import org.matrix.android.sdk.api.session.space.model.SpaceChildContent import org.matrix.android.sdk.api.session.space.model.SpaceChildSummaryEvent import org.matrix.android.sdk.api.session.space.model.SpaceParentContent import org.matrix.android.sdk.api.session.space.peeking.SpacePeekResult +import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.room.RoomGetter import org.matrix.android.sdk.internal.session.room.SpaceGetter import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask import org.matrix.android.sdk.internal.session.room.membership.leaving.LeaveRoomTask +import org.matrix.android.sdk.internal.session.room.powerlevels.getRoomPowerLevels import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource import org.matrix.android.sdk.internal.session.space.peeking.PeekSpaceTask @@ -83,7 +86,7 @@ internal class DefaultSpaceService @Inject constructor( if (isPublic) { this.roomAliasName = roomAliasLocalPart this.powerLevelContentOverride = (powerLevelContentOverride ?: PowerLevelsContent()).copy( - invite = if (isPublic) Role.Default.value else Role.Moderator.value + invite = UserPowerLevel.User.value ) this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT this.historyVisibility = RoomHistoryVisibility.WORLD_READABLE @@ -253,15 +256,8 @@ internal class DefaultSpaceService @Inject constructor( if (roomSummaryDataSource.getRoomSummary(parentSpaceId)?.membership != Membership.JOIN) { throw UnsupportedOperationException("Cannot add canonical child if not member of parent") } - val powerLevelsEvent = stateEventDataSource.getStateEvent( - roomId = parentSpaceId, - eventType = EventType.STATE_ROOM_POWER_LEVELS, - stateKey = QueryStringValue.IsEmpty - ) - val powerLevelsContent = powerLevelsEvent?.content?.toModel() - ?: throw UnsupportedOperationException("Cannot add canonical child, missing power level") - val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent) - if (!powerLevelsHelper.isUserAllowedToSend(userId, true, EventType.STATE_SPACE_CHILD)) { + val roomPowerLevels = stateEventDataSource.getRoomPowerLevels(parentSpaceId) + if (!roomPowerLevels.isUserAllowedToSend(userId, true, EventType.STATE_SPACE_CHILD)) { throw UnsupportedOperationException("Cannot add canonical child, not enough power level") } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt index 7359fdbd91..8f197706ff 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt @@ -30,15 +30,13 @@ import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService -import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.widgets.WidgetManagementFailure import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManager +import org.matrix.android.sdk.internal.session.room.powerlevels.getRoomPowerLevels import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataDataSource import org.matrix.android.sdk.internal.session.widgets.helper.WidgetFactory @@ -200,12 +198,7 @@ internal class WidgetManager @Inject constructor( } fun hasPermissionsToHandleWidgets(roomId: String): Boolean { - val powerLevelsEvent = stateEventDataSource.getStateEvent( - roomId = roomId, - eventType = EventType.STATE_ROOM_POWER_LEVELS, - stateKey = QueryStringValue.IsEmpty - ) - val powerLevelsContent = powerLevelsEvent?.content?.toModel() ?: return false - return PowerLevelsHelper(powerLevelsContent).isUserAllowedToSend(userId, true, EventType.STATE_ROOM_WIDGET_LEGACY) + val roomPowerLevels = stateEventDataSource.getRoomPowerLevels(roomId) + return roomPowerLevels.isUserAllowedToSend(userId, true, EventType.STATE_ROOM_WIDGET_LEGACY) } } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessorTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessorTest.kt index 248c4b322d..bdfffa9d51 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessorTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessorTest.kt @@ -33,7 +33,7 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.getTimelineEvent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntityFields import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntity @@ -255,9 +255,9 @@ class DefaultPollAggregationProcessorTest { every { room.getTimelineEvent(eventId) } returns if (hasExistingTimelineEvent) A_TIMELINE_EVENT else null } - private fun mockRedactionPowerLevels(userId: String, isAbleToRedact: Boolean): PowerLevelsHelper { - val powerLevelsHelper = mockk() - every { powerLevelsHelper.isUserAbleToRedact(userId) } returns isAbleToRedact - return powerLevelsHelper + private fun mockRedactionPowerLevels(userId: String, isAbleToRedact: Boolean): RoomPowerLevels { + val roomPowerLevels = mockk() + every { roomPowerLevels.isUserAbleToRedact(userId) } returns isAbleToRedact + return roomPowerLevels } } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt index 1c2cf293b6..0669776394 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt @@ -376,8 +376,8 @@ internal class DefaultCreateLocalRoomStateEventsTaskTest { powerLevelsContent.kick shouldBeEqualTo Role.Moderator.value powerLevelsContent.invite shouldBeEqualTo Role.Moderator.value powerLevelsContent.redact shouldBeEqualTo Role.Moderator.value - powerLevelsContent.eventsDefault shouldBeEqualTo Role.Default.value - powerLevelsContent.usersDefault shouldBeEqualTo Role.Default.value + powerLevelsContent.eventsDefault shouldBeEqualTo Role.User.value + powerLevelsContent.usersDefault shouldBeEqualTo Role.User.value powerLevelsContent.stateDefault shouldBeEqualTo Role.Moderator.value // Guest access result.find { it.type == EventType.STATE_ROOM_GUEST_ACCESS } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index 618b31796f..72b72990e8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -110,7 +110,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachme import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import org.matrix.android.sdk.api.session.room.model.tombstone.RoomTombstoneContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.read.ReadService import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -304,11 +303,11 @@ class TimelineViewModel @AssistedInject constructor( private fun observePowerLevel() { if (room == null) return PowerLevelsFlowFactory(room).createFlow() - .onEach { - val canInvite = PowerLevelsHelper(it).isUserAbleToInvite(session.myUserId) + .onEach { powerLevels -> + val canInvite = powerLevels.isUserAbleToInvite(session.myUserId) val isAllowedToManageWidgets = session.widgetService().hasPermissionsToHandleWidgets(room.roomId) - val isAllowedToStartWebRTCCall = PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, false, EventType.CALL_INVITE) - val isAllowedToSetupEncryption = PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_ENCRYPTION) + val isAllowedToStartWebRTCCall = powerLevels.isUserAllowedToSend(session.myUserId, false, EventType.CALL_INVITE) + val isAllowedToSetupEncryption = powerLevels.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_ENCRYPTION) setState { copy( canInvite = canInvite, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt index 928adb63f4..00bc1890e7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt @@ -69,7 +69,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.relation.shouldRenderInThread -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.send.UserDraft import org.matrix.android.sdk.api.session.room.timeline.getRelationContent import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent @@ -183,7 +182,7 @@ class MessageComposerViewModel @AssistedInject constructor( PowerLevelsFlowFactory(room).createFlow(), room.flow().liveRoomSummary().unwrap() ) { pl, sum -> - val canSendMessage = PowerLevelsHelper(pl).isUserAllowedToSend(session.myUserId, false, EventType.MESSAGE) + val canSendMessage = pl.isUserAllowedToSend(session.myUserId, false, EventType.MESSAGE) if (canSendMessage) { val isE2E = sum.isEncrypted if (isE2E) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 1028a0548f..8a4e3bb797 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -49,7 +49,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited @@ -117,11 +117,10 @@ class MessageActionsViewModel @AssistedInject constructor( return } PowerLevelsFlowFactory(room).createFlow() - .onEach { - val powerLevelsHelper = PowerLevelsHelper(it) - val canReact = powerLevelsHelper.isUserAllowedToSend(session.myUserId, false, EventType.REACTION) - val canRedact = powerLevelsHelper.isUserAbleToRedact(session.myUserId) - val canSendMessage = powerLevelsHelper.isUserAllowedToSend(session.myUserId, false, EventType.MESSAGE) + .onEach { roomPowerLevels -> + val canReact = roomPowerLevels.isUserAllowedToSend(session.myUserId, false, EventType.REACTION) + val canRedact = roomPowerLevels.isUserAbleToRedact(session.myUserId) + val canSendMessage = roomPowerLevels.isUserAllowedToSend(session.myUserId, false, EventType.MESSAGE) val permissions = ActionPermissions(canSendMessage = canSendMessage, canRedact = canRedact, canReact = canReact) setState { copy(actionPermissions = permissions) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt index 45ba700d1f..c36cd14641 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt @@ -30,10 +30,11 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.getRoomPowerLevels import org.matrix.android.sdk.api.session.room.getStateEvent import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import javax.inject.Inject @@ -303,9 +304,7 @@ class MergedHeaderItemFactory @Inject constructor( collapsedEventIds.removeAll(mergedEventIds) } val mergeId = mergedEventIds.joinToString(separator = "_") { it.toString() } - val powerLevelsHelper = activeSessionHolder.getSafeActiveSession()?.getRoom(event.roomId) - ?.let { it.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty)?.content?.toModel() } - ?.let { PowerLevelsHelper(it) } + val roomPowerLevels = activeSessionHolder.getSafeActiveSession()?.getRoom(event.roomId)?.getRoomPowerLevels() val currentUserId = activeSessionHolder.getSafeActiveSession()?.myUserId ?: "" val attributes = MergedRoomCreationItem.Attributes( isCollapsed = isCollapsed, @@ -320,10 +319,10 @@ class MergedHeaderItemFactory @Inject constructor( callback = callback, currentUserId = currentUserId, roomSummary = partialState.roomSummary, - canInvite = powerLevelsHelper?.isUserAbleToInvite(currentUserId) ?: false, - canChangeAvatar = powerLevelsHelper?.isUserAllowedToSend(currentUserId, true, EventType.STATE_ROOM_AVATAR) ?: false, - canChangeTopic = powerLevelsHelper?.isUserAllowedToSend(currentUserId, true, EventType.STATE_ROOM_TOPIC) ?: false, - canChangeName = powerLevelsHelper?.isUserAllowedToSend(currentUserId, true, EventType.STATE_ROOM_NAME) ?: false + canInvite = roomPowerLevels?.isUserAbleToInvite(currentUserId) ?: false, + canChangeAvatar = roomPowerLevels?.isUserAllowedToSend(currentUserId, true, EventType.STATE_ROOM_AVATAR) ?: false, + canChangeTopic = roomPowerLevels?.isUserAllowedToSend(currentUserId, true, EventType.STATE_ROOM_TOPIC) ?: false, + canChangeName = roomPowerLevels?.isUserAllowedToSend(currentUserId, true, EventType.STATE_ROOM_NAME) ?: false ) MergedRoomCreationItem_() .id(mergeId) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index bd1c903f1f..5a8d3fc41a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -41,7 +41,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent import org.matrix.android.sdk.api.session.room.model.RoomTopicContent import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.widgets.model.WidgetContent import timber.log.Timber @@ -122,8 +122,8 @@ class NoticeEventFormatter @Inject constructor( userIds.addAll(previousPowerLevelsContent.users.orEmpty().keys) val diffs = ArrayList() userIds.forEach { userId -> - val from = PowerLevelsHelper(previousPowerLevelsContent).getUserRole(userId) - val to = PowerLevelsHelper(powerLevelsContent).getUserRole(userId) + val from = RoomPowerLevels(previousPowerLevelsContent,null).getUserRole(userId) + val to = RoomPowerLevels(powerLevelsContent, null).getUserRole(userId) if (from != to) { val fromStr = roleFormatter.format(from) val toStr = roleFormatter.format(to) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt index 82820e90cb..dfa1ec4549 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt @@ -34,7 +34,7 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getUserOrDefault -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.util.toMatrixItem import timber.log.Timber @@ -75,11 +75,10 @@ class LocationSharingViewModel @AssistedInject constructor( private fun observePowerLevelsForLiveLocationSharing() { PowerLevelsFlowFactory(room).createFlow() .distinctUntilChanged() - .setOnEach { - val powerLevelsHelper = PowerLevelsHelper(it) + .setOnEach { roomPowerLevels -> val canShareLiveLocation = EventType.STATE_ROOM_BEACON_INFO.values .all { beaconInfoType -> - powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, beaconInfoType) + roomPowerLevels.isUserAllowedToSend(session.myUserId, true, beaconInfoType) } copy(canShareLiveLocation = canShareLiveLocation) diff --git a/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt b/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt index f065334ae7..689add249c 100644 --- a/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt +++ b/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt @@ -9,23 +9,40 @@ package im.vector.app.features.powerlevel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOn import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent +import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent +import org.matrix.android.sdk.api.session.room.model.create.getRoomCreateContentWithSender +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.mapOptional -import org.matrix.android.sdk.flow.unwrap class PowerLevelsFlowFactory(private val room: Room) { - fun createFlow(): Flow { - return room.flow() + fun createFlow(): Flow { + val flowRoom = room.flow() + val powerLevelsFlow = flowRoom .liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) .mapOptional { it.content.toModel() } .flowOn(Dispatchers.Default) - .unwrap() + + val roomCreateFlow = flowRoom + .liveStateEvent(EventType.STATE_ROOM_CREATE, QueryStringValue.IsEmpty) + .mapOptional { event -> + event.getRoomCreateContentWithSender() + } + .flowOn(Dispatchers.Default) + + return combine(powerLevelsFlow, roomCreateFlow) { powerLevelsContent, roomCreateContent -> + RoomPowerLevels( + powerLevelsContent = powerLevelsContent.getOrNull(), + roomCreateContent = roomCreateContent.getOrNull() + ) + } } } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileAction.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileAction.kt index 8e764470c7..ade308339e 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileAction.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileAction.kt @@ -8,6 +8,7 @@ package im.vector.app.features.roommemberprofile import im.vector.app.core.platform.VectorViewModelAction +import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel sealed class RoomMemberProfileAction : VectorViewModelAction { object RetryFetchingInfo : RoomMemberProfileAction() @@ -18,7 +19,7 @@ sealed class RoomMemberProfileAction : VectorViewModelAction { object InviteUser : RoomMemberProfileAction() object VerifyUser : RoomMemberProfileAction() object ShareRoomMemberProfile : RoomMemberProfileAction() - data class SetPowerLevel(val previousValue: Int, val newValue: Int, val askForValidation: Boolean) : RoomMemberProfileAction() + data class SetPowerLevel(val previousValue: UserPowerLevel, val newValue: UserPowerLevel.Value, val askForValidation: Boolean) : RoomMemberProfileAction() data class SetUserColorOverride(val newColorSpec: String) : RoomMemberProfileAction() data class OpenOrCreateDm(val userId: String) : RoomMemberProfileAction() } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt index 95ee18682e..f4420d6a42 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt @@ -17,8 +17,8 @@ import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import im.vector.lib.strings.CommonStrings import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.powerlevels.Role +import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel import javax.inject.Inject class RoomMemberProfileController @Inject constructor( @@ -38,7 +38,7 @@ class RoomMemberProfileController @Inject constructor( fun onOverrideColorClicked() fun onJumpToReadReceiptClicked() fun onMentionClicked() - fun onEditPowerLevel(currentRole: Role) + fun onEditPowerLevel(userPowerLevel: UserPowerLevel) fun onKickClicked(isSpace: Boolean) fun onBanClicked(isSpace: Boolean, isUserBanned: Boolean) fun onCancelInviteClicked() @@ -243,11 +243,10 @@ class RoomMemberProfileController @Inject constructor( } private fun buildAdminSection(state: RoomMemberProfileViewState) { - val powerLevelsContent = state.powerLevelsContent ?: return val powerLevelsStr = state.userPowerLevelString() ?: return - val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent) - val userPowerLevel = powerLevelsHelper.getUserRole(state.userId) - val myPowerLevel = powerLevelsHelper.getUserRole(session.myUserId) + val roomPowerLevels = state.roomPowerLevels ?: return + val userPowerLevel = roomPowerLevels.getUserPowerLevel(state.userId) + val myPowerLevel = roomPowerLevels.getUserPowerLevel(session.myUserId) if ((!state.isMine && myPowerLevel <= userPowerLevel)) { return } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt index 29cb42a686..836e3857cc 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt @@ -51,7 +51,7 @@ import im.vector.app.features.roommemberprofile.powerlevel.EditPowerLevelDialogs import im.vector.lib.strings.CommonStrings import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.session.crypto.model.UserVerificationLevel -import org.matrix.android.sdk.api.session.room.powerlevels.Role +import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel import org.matrix.android.sdk.api.util.MatrixItem import javax.inject.Inject @@ -377,9 +377,9 @@ class RoomMemberProfileFragment : .show() } - override fun onEditPowerLevel(currentRole: Role) { - EditPowerLevelDialogs.showChoice(requireActivity(), CommonStrings.power_level_edit_title, currentRole) { newPowerLevel -> - viewModel.handle(RoomMemberProfileAction.SetPowerLevel(currentRole.value, newPowerLevel, true)) + override fun onEditPowerLevel(userPowerLevel: UserPowerLevel) { + EditPowerLevelDialogs.showChoice(requireActivity(), CommonStrings.power_level_edit_title, userPowerLevel) { newPowerLevel -> + viewModel.handle(RoomMemberProfileAction.SetPowerLevel(userPowerLevel, newPowerLevel, true)) } } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewEvents.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewEvents.kt index d806f521ad..11bb19c51b 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewEvents.kt @@ -8,6 +8,7 @@ package im.vector.app.features.roommemberprofile import im.vector.app.core.platform.VectorViewEvents +import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel /** * Transient events for RoomMemberProfile. @@ -22,8 +23,8 @@ sealed class RoomMemberProfileViewEvents : VectorViewEvents { object OnInviteActionSuccess : RoomMemberProfileViewEvents() object OnKickActionSuccess : RoomMemberProfileViewEvents() object OnBanActionSuccess : RoomMemberProfileViewEvents() - data class ShowPowerLevelValidation(val currentValue: Int, val newValue: Int) : RoomMemberProfileViewEvents() - data class ShowPowerLevelDemoteWarning(val currentValue: Int, val newValue: Int) : RoomMemberProfileViewEvents() + data class ShowPowerLevelValidation(val currentValue: UserPowerLevel, val newValue: UserPowerLevel.Value) : RoomMemberProfileViewEvents() + data class ShowPowerLevelDemoteWarning(val currentValue: UserPowerLevel, val newValue: UserPowerLevel.Value) : RoomMemberProfileViewEvents() data class StartVerification( val userId: String, diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt index 91024ee136..92ba7acfb5 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -42,9 +42,9 @@ import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomEncryptionAlgorithm import org.matrix.android.sdk.api.session.room.model.RoomType -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.util.toMatrixItem @@ -233,15 +233,15 @@ class RoomMemberProfileViewModel @AssistedInject constructor( if (room == null || action.previousValue == action.newValue) { return@withState } - val currentPowerLevelsContent = state.powerLevelsContent ?: return@withState - val myPowerLevel = PowerLevelsHelper(currentPowerLevelsContent).getUserPowerLevelValue(session.myUserId) + val roomPowerLevels = state.roomPowerLevels ?: return@withState + val myPowerLevel = roomPowerLevels.getUserPowerLevel(session.myUserId) if (action.askForValidation && action.newValue >= myPowerLevel) { _viewEvents.post(RoomMemberProfileViewEvents.ShowPowerLevelValidation(action.previousValue, action.newValue)) } else if (action.askForValidation && state.isMine) { _viewEvents.post(RoomMemberProfileViewEvents.ShowPowerLevelDemoteWarning(action.previousValue, action.newValue)) } else { - val newPowerLevelsContent = currentPowerLevelsContent - .setUserPowerLevel(state.userId, action.newValue) + val newPowerLevelsContent = (roomPowerLevels.powerLevelsContent ?: PowerLevelsContent()) + .setUserPowerLevel(state.userId, action.newValue.value) .toContent() viewModelScope.launch { _viewEvents.post(RoomMemberProfileViewEvents.Loading()) @@ -361,19 +361,17 @@ class RoomMemberProfileViewModel @AssistedInject constructor( private fun observeRoomSummaryAndPowerLevels(room: Room) { val roomSummaryLive = room.flow().liveRoomSummary().unwrap() - val powerLevelsContentLive = PowerLevelsFlowFactory(room).createFlow() - - powerLevelsContentLive - .onEach { - val powerLevelsHelper = PowerLevelsHelper(it) + val powerLevelsFlow = PowerLevelsFlowFactory(room).createFlow() + powerLevelsFlow + .onEach { roomPowerLevels -> val permissions = ActionPermissions( - canKick = powerLevelsHelper.isUserAbleToKick(session.myUserId), - canBan = powerLevelsHelper.isUserAbleToBan(session.myUserId), - canInvite = powerLevelsHelper.isUserAbleToInvite(session.myUserId), - canEditPowerLevel = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_POWER_LEVELS) + canKick = roomPowerLevels.isUserAbleToKick(session.myUserId), + canBan = roomPowerLevels.isUserAbleToBan(session.myUserId), + canInvite = roomPowerLevels.isUserAbleToInvite(session.myUserId), + canEditPowerLevel = roomPowerLevels.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_POWER_LEVELS) ) setState { - copy(powerLevelsContent = it, actionPermissions = permissions) + copy(roomPowerLevels = roomPowerLevels, actionPermissions = permissions) } }.launchIn(viewModelScope) @@ -388,14 +386,14 @@ class RoomMemberProfileViewModel @AssistedInject constructor( copy(isRoomEncrypted = false) } } - roomSummaryLive.combine(powerLevelsContentLive) { roomSummary, powerLevelsContent -> + roomSummaryLive.combine(powerLevelsFlow) { roomSummary, roomPowerLevels -> val roomName = roomSummary.toMatrixItem().getBestName() - val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent) - when (val userPowerLevel = powerLevelsHelper.getUserRole(initialState.userId)) { + when (roomPowerLevels.getUserRole(initialState.userId)) { + Role.SuperAdmin, + Role.Creator, Role.Admin -> stringProvider.getString(CommonStrings.room_member_power_level_admin_in, roomName) Role.Moderator -> stringProvider.getString(CommonStrings.room_member_power_level_moderator_in, roomName) - Role.Default -> stringProvider.getString(CommonStrings.room_member_power_level_default_in, roomName) - is Role.Custom -> stringProvider.getString(CommonStrings.room_member_power_level_custom_in, userPowerLevel.value, roomName) + Role.User -> stringProvider.getString(CommonStrings.room_member_power_level_default_in, roomName) } }.execute { copy(userPowerLevelString = it) diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewState.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewState.kt index 7d550c891d..a51eeef8e3 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewState.kt @@ -12,7 +12,7 @@ import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.util.MatrixItem data class RoomMemberProfileViewState( @@ -24,7 +24,7 @@ data class RoomMemberProfileViewState( val isIgnored: Async = Uninitialized, val isRoomEncrypted: Boolean = false, val isAlgorithmSupported: Boolean = true, - val powerLevelsContent: PowerLevelsContent? = null, + val roomPowerLevels: RoomPowerLevels? = null, val userPowerLevelString: Async = Uninitialized, val userMatrixItem: Async = Uninitialized, val userMXCrossSigningInfo: MXCrossSigningInfo? = null, diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/powerlevel/EditPowerLevelDialogs.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/powerlevel/EditPowerLevelDialogs.kt index ffe6314592..6d24f198f4 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/powerlevel/EditPowerLevelDialogs.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/powerlevel/EditPowerLevelDialogs.kt @@ -19,6 +19,7 @@ import im.vector.app.core.extensions.hideKeyboard import im.vector.app.databinding.DialogEditPowerLevelBinding import im.vector.lib.strings.CommonStrings import org.matrix.android.sdk.api.session.room.powerlevels.Role +import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel object EditPowerLevelDialogs { @@ -26,21 +27,21 @@ object EditPowerLevelDialogs { fun showChoice( activity: Activity, @StringRes titleRes: Int, - currentRole: Role, - listener: (Int) -> Unit + currentPowerLevel: UserPowerLevel, + listener: (UserPowerLevel.Value) -> Unit ) { val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_edit_power_level, null) val views = DialogEditPowerLevelBinding.bind(dialogLayout) views.powerLevelRadioGroup.setOnCheckedChangeListener { _, checkedId -> views.powerLevelCustomEditLayout.isVisible = checkedId == R.id.powerLevelCustomRadio } - views.powerLevelCustomEdit.setText("${currentRole.value}") - + val currentRole = Role.getSuggestedRole(currentPowerLevel) when (currentRole) { + Role.Creator -> views.powerLevelAdminRadio.isChecked = true + Role.SuperAdmin -> views.powerLevelAdminRadio.isChecked = true Role.Admin -> views.powerLevelAdminRadio.isChecked = true Role.Moderator -> views.powerLevelModeratorRadio.isChecked = true - Role.Default -> views.powerLevelDefaultRadio.isChecked = true - else -> views.powerLevelCustomRadio.isChecked = true + Role.User -> views.powerLevelDefaultRadio.isChecked = true } MaterialAlertDialogBuilder(activity) @@ -48,14 +49,14 @@ object EditPowerLevelDialogs { .setView(dialogLayout) .setPositiveButton(CommonStrings.edit) { _, _ -> val newValue = when (views.powerLevelRadioGroup.checkedRadioButtonId) { - R.id.powerLevelAdminRadio -> Role.Admin.value - R.id.powerLevelModeratorRadio -> Role.Moderator.value - R.id.powerLevelDefaultRadio -> Role.Default.value - else -> { - views.powerLevelCustomEdit.text?.toString()?.toInt() ?: currentRole.value - } + R.id.powerLevelAdminRadio -> UserPowerLevel.Admin + R.id.powerLevelModeratorRadio -> UserPowerLevel.Moderator + R.id.powerLevelDefaultRadio -> UserPowerLevel.User + else -> null + } + if(newValue != null) { + listener(newValue) } - listener(newValue) } .setNegativeButton(CommonStrings.action_cancel, null) .setOnKeyListener(DialogInterface.OnKeyListener diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt index 83b1c5a950..deb609509a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt @@ -39,7 +39,7 @@ import org.matrix.android.sdk.api.session.room.getStateEvent import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.state.isPublic import org.matrix.android.sdk.flow.FlowRoom import org.matrix.android.sdk.flow.flow @@ -115,9 +115,8 @@ class RoomProfileViewModel @AssistedInject constructor( private fun observePowerLevels() { val powerLevelsContentLive = PowerLevelsFlowFactory(room).createFlow() powerLevelsContentLive - .onEach { - val powerLevelsHelper = PowerLevelsHelper(it) - val canUpdateRoomState = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_ENCRYPTION) + .onEach { roomPowerLevels -> + val canUpdateRoomState = roomPowerLevels.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_ENCRYPTION) setState { copy(canUpdateRoomState = canUpdateRoomState) } @@ -158,10 +157,9 @@ class RoomProfileViewModel @AssistedInject constructor( private fun observePermissions() { PowerLevelsFlowFactory(room) .createFlow() - .setOnEach { - val powerLevelsHelper = PowerLevelsHelper(it) + .setOnEach { roomPowerLevels -> val permissions = RoomProfileViewState.ActionPermissions( - canEnableEncryption = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_ENCRYPTION) + canEnableEncryption = roomPowerLevels.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_ENCRYPTION) ) copy(actionPermissions = permissions) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index a80e7b2050..73f8e92434 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -29,7 +29,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.mapOptional import org.matrix.android.sdk.flow.unwrap @@ -127,10 +127,9 @@ class RoomAliasViewModel @AssistedInject constructor( private fun observePowerLevel() { PowerLevelsFlowFactory(room) .createFlow() - .onEach { - val powerLevelsHelper = PowerLevelsHelper(it) + .onEach { roomPowerLevels -> val permissions = RoomAliasViewState.ActionPermissions( - canChangeCanonicalAlias = powerLevelsHelper.isUserAllowedToSend( + canChangeCanonicalAlias = roomPowerLevels.isUserAllowedToSend( userId = session.myUserId, isState = true, eventType = EventType.STATE_ROOM_CANONICAL_ALIAS diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt index df697bd501..acfdbb8b1c 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt @@ -29,7 +29,7 @@ import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap @@ -62,12 +62,10 @@ class RoomBannedMemberListViewModel @AssistedInject constructor( ) } - val powerLevelsContentLive = PowerLevelsFlowFactory(room).createFlow() - - powerLevelsContentLive - .setOnEach { - val powerLevelsHelper = PowerLevelsHelper(it) - copy(canUserBan = powerLevelsHelper.isUserAbleToBan(session.myUserId)) + val powerLevelsFlow = PowerLevelsFlowFactory(room).createFlow() + powerLevelsFlow + .setOnEach { roomPowerLevels -> + copy(canUserBan = roomPowerLevels.isUserAbleToBan(session.myUserId)) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListComparator.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListComparator.kt new file mode 100644 index 0000000000..a4ce99f63d --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListComparator.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2020-2024 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.features.roomprofile.members + +import javax.inject.Inject + +class RoomMemberListComparator @Inject constructor() : Comparator { + + override fun compare(leftRoomMember: RoomMemberWithPowerLevel?, rightRoomMember: RoomMemberWithPowerLevel?): Int { + return when (leftRoomMember) { + null -> + when (rightRoomMember) { + null -> 0 + else -> 1 + } + else -> + when (rightRoomMember) { + null -> -1 + else -> + when { + leftRoomMember.powerLevel > rightRoomMember.powerLevel -> 1 + leftRoomMember.powerLevel < rightRoomMember.powerLevel -> -1 + leftRoomMember.summary.displayName.isNullOrBlank() -> + when { + rightRoomMember.summary.displayName.isNullOrBlank() -> { + // No display names, compare ids + leftRoomMember.summary.userId.compareTo(rightRoomMember.summary.userId) + } + else -> 1 + } + else -> + when { + rightRoomMember.summary.displayName.isNullOrBlank() -> -1 + else -> { + when (leftRoomMember.summary.displayName) { + rightRoomMember.summary.displayName -> + // Same display name, compare id + leftRoomMember.summary.userId.compareTo(rightRoomMember.summary.userId) + else -> + leftRoomMember.summary.displayName!!.compareTo(rightRoomMember.summary.displayName!!, true) + } + } + } + } + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt index 88f55aac70..24d2dddc8e 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt @@ -16,11 +16,13 @@ import im.vector.app.core.extensions.join import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.roomprofile.permissions.RoleFormatter import me.gujun.android.span.span import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent +import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject @@ -29,7 +31,8 @@ class RoomMemberListController @Inject constructor( private val avatarRenderer: AvatarRenderer, private val stringProvider: StringProvider, private val colorProvider: ColorProvider, - private val roomMemberSummaryFilter: RoomMemberSummaryFilter + private val roomMemberSummaryFilter: RoomMemberSummaryFilter, + private val roleFormatter: RoleFormatter, ) : TypedEpoxyController() { interface Callback { @@ -56,13 +59,13 @@ class RoomMemberListController @Inject constructor( .orEmpty() var threePidInvitesDone = filteredThreePidInvites.isEmpty() - for ((powerLevelCategory, roomMemberList) in roomMembersByPowerLevel) { - val filteredRoomMemberList = roomMemberList.filter { roomMemberSummaryFilter.test(it) } + for ((category, roomMemberList) in roomMembersByPowerLevel) { + val filteredRoomMemberList = roomMemberList.filter { roomMemberSummaryFilter.test(it.summary) } if (filteredRoomMemberList.isEmpty()) { continue } - if (powerLevelCategory == RoomMemberListCategories.USER && !threePidInvitesDone) { + if (category == RoomMemberListCategories.USER && !threePidInvitesDone) { // If there is no regular invite, display threepid invite before the regular user buildProfileSection( stringProvider.getString(RoomMemberListCategories.INVITE.titleRes) @@ -73,20 +76,20 @@ class RoomMemberListController @Inject constructor( } buildProfileSection( - stringProvider.getString(powerLevelCategory.titleRes) + stringProvider.getString(category.titleRes) ) filteredRoomMemberList.join( each = { _, roomMember -> - buildRoomMember(roomMember, powerLevelCategory, host, data) + buildRoomMember(roomMember, host, data) }, between = { _, roomMemberBefore -> dividerItem { - id("divider_${roomMemberBefore.userId}") + id("divider_${roomMemberBefore.summary.userId}") } } ) - if (powerLevelCategory == RoomMemberListCategories.INVITE && !threePidInvitesDone) { + if (category == RoomMemberListCategories.INVITE && !threePidInvitesDone) { // Display the threepid invite after the regular invite dividerItem { id("divider_threepidinvites") @@ -108,24 +111,24 @@ class RoomMemberListController @Inject constructor( } private fun buildRoomMember( - roomMember: RoomMemberSummary, - powerLevelCategory: RoomMemberListCategories, + roomMember: RoomMemberWithPowerLevel, host: RoomMemberListController, data: RoomMemberListViewState ) { - val powerLabel = stringProvider.getString(powerLevelCategory.titleRes) + val role = Role.getSuggestedRole(roomMember.powerLevel) + val powerLabel = roleFormatter.format(role) profileMatrixItemWithPowerLevelWithPresence { - id(roomMember.userId) - matrixItem(roomMember.toMatrixItem()) + id(roomMember.summary.userId) + matrixItem(roomMember.summary.toMatrixItem()) avatarRenderer(host.avatarRenderer) - userVerificationLevel(data.trustLevelMap.invoke()?.get(roomMember.userId)) + userVerificationLevel(data.trustLevelMap.invoke()?.get(roomMember.summary.userId)) clickListener { - host.callback?.onRoomMemberClicked(roomMember) + host.callback?.onRoomMemberClicked(roomMember.summary) } showPresence(true) - userPresence(roomMember.userPresence) - ignoredUser(roomMember.userId in data.ignoredUserIds) + userPresence(roomMember.summary.userPresence) + ignoredUser(roomMember.summary.userId in data.ignoredUserIds) powerLevelLabel( span { span(powerLabel) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt index 465da6a9c2..2974e10649 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt @@ -31,22 +31,19 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.UserVerificationLevel import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.powerlevels.Role +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.flow.mapOptional import org.matrix.android.sdk.flow.unwrap import timber.log.Timber class RoomMemberListViewModel @AssistedInject constructor( @Assisted initialState: RoomMemberListViewState, - private val roomMemberSummaryComparator: RoomMemberSummaryComparator, + private val roomMemberListComparator: RoomMemberListComparator, private val session: Session ) : VectorViewModel(initialState) { @@ -75,14 +72,12 @@ class RoomMemberListViewModel @AssistedInject constructor( memberships = Membership.activeMemberships() } + val powerLevelsFlow = PowerLevelsFlowFactory(room).createFlow() combine( roomFlow.liveRoomMembers(roomMemberQueryParams), - roomFlow - .liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) - .mapOptional { it.content.toModel() } - .unwrap() - ) { roomMembers, powerLevelsContent -> - buildRoomMemberSummaries(powerLevelsContent, roomMembers) + powerLevelsFlow, + ) { roomMembers, roomPowerLevels -> + buildRoomMemberSummaries(roomPowerLevels, roomMembers) } .execute { async -> copy(roomMemberSummaries = async) @@ -143,10 +138,10 @@ class RoomMemberListViewModel @AssistedInject constructor( private fun observePowerLevel() { PowerLevelsFlowFactory(room).createFlow() - .onEach { + .onEach { roomPowerLevels -> val permissions = ActionPermissions( - canInvite = PowerLevelsHelper(it).isUserAbleToInvite(session.myUserId), - canRevokeThreePidInvite = PowerLevelsHelper(it).isUserAllowedToSend( + canInvite = roomPowerLevels.isUserAbleToInvite(session.myUserId), + canRevokeThreePidInvite = roomPowerLevels.isUserAllowedToSend( userId = session.myUserId, isState = true, eventType = EventType.STATE_ROOM_THIRD_PARTY_INVITE @@ -184,31 +179,34 @@ class RoomMemberListViewModel @AssistedInject constructor( } } - private fun buildRoomMemberSummaries(powerLevelsContent: PowerLevelsContent, roomMembers: List): RoomMemberSummaries { - val admins = ArrayList() - val moderators = ArrayList() - val users = ArrayList(roomMembers.size) - val customs = ArrayList() - val invites = ArrayList() - val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent) + private fun buildRoomMemberSummaries(roomPowerLevels: RoomPowerLevels, roomMembers: List): RoomMembersByRole { + val admins = ArrayList() + val moderators = ArrayList() + val users = ArrayList(roomMembers.size) + val invites = ArrayList() roomMembers .forEach { roomMember -> - val userRole = powerLevelsHelper.getUserRole(roomMember.userId) + val powerLevel = roomPowerLevels.getUserPowerLevel(roomMember.userId) + val userRole = Role.getSuggestedRole(powerLevel) + val roomMemberWithPowerLevel = RoomMemberWithPowerLevel( + powerLevel = powerLevel, + summary = roomMember, + ) when { - roomMember.membership == Membership.INVITE -> invites.add(roomMember) - userRole == Role.Admin -> admins.add(roomMember) - userRole == Role.Moderator -> moderators.add(roomMember) - userRole == Role.Default -> users.add(roomMember) - else -> customs.add(roomMember) + roomMember.membership == Membership.INVITE -> invites.add(roomMemberWithPowerLevel) + userRole == Role.SuperAdmin || + userRole == Role.Creator || + userRole == Role.Admin -> admins.add(roomMemberWithPowerLevel) + userRole == Role.Moderator -> moderators.add(roomMemberWithPowerLevel) + userRole == Role.User -> users.add(roomMemberWithPowerLevel) } } return listOf( - RoomMemberListCategories.ADMIN to admins.sortedWith(roomMemberSummaryComparator), - RoomMemberListCategories.MODERATOR to moderators.sortedWith(roomMemberSummaryComparator), - RoomMemberListCategories.CUSTOM to customs.sortedWith(roomMemberSummaryComparator), - RoomMemberListCategories.INVITE to invites.sortedWith(roomMemberSummaryComparator), - RoomMemberListCategories.USER to users.sortedWith(roomMemberSummaryComparator) + RoomMemberListCategories.ADMIN to admins.sortedWith(roomMemberListComparator), + RoomMemberListCategories.MODERATOR to moderators.sortedWith(roomMemberListComparator), + RoomMemberListCategories.INVITE to invites.sortedWith(roomMemberListComparator), + RoomMemberListCategories.USER to users.sortedWith(roomMemberListComparator) ) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt index 90da131cb5..d68a9fceba 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt @@ -18,11 +18,12 @@ import org.matrix.android.sdk.api.session.crypto.model.UserVerificationLevel import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel data class RoomMemberListViewState( val roomId: String, val roomSummary: Async = Uninitialized, - val roomMemberSummaries: Async = Uninitialized, + val roomMemberSummaries: Async = Uninitialized, val areAllMembersLoaded: Boolean = false, val ignoredUserIds: List = emptyList(), val filter: String = "", @@ -41,12 +42,16 @@ data class ActionPermissions( val canRevokeThreePidInvite: Boolean = false ) -typealias RoomMemberSummaries = List>> +data class RoomMemberWithPowerLevel( + val powerLevel: UserPowerLevel, + val summary: RoomMemberSummary, +) + +typealias RoomMembersByRole = List>> enum class RoomMemberListCategories(@StringRes val titleRes: Int) { ADMIN(CommonStrings.room_member_power_level_admins), MODERATOR(CommonStrings.room_member_power_level_moderators), - CUSTOM(CommonStrings.room_member_power_level_custom), INVITE(CommonStrings.room_member_power_level_invites), USER(CommonStrings.room_member_power_level_users) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberSummaryComparator.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberSummaryComparator.kt deleted file mode 100644 index fbff99b287..0000000000 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberSummaryComparator.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2020-2024 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.features.roomprofile.members - -import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary -import javax.inject.Inject - -class RoomMemberSummaryComparator @Inject constructor() : Comparator { - - override fun compare(leftRoomMemberSummary: RoomMemberSummary?, rightRoomMemberSummary: RoomMemberSummary?): Int { - return when (leftRoomMemberSummary) { - null -> - when (rightRoomMemberSummary) { - null -> 0 - else -> 1 - } - else -> - when (rightRoomMemberSummary) { - null -> -1 - else -> - when { - leftRoomMemberSummary.displayName.isNullOrBlank() -> - when { - rightRoomMemberSummary.displayName.isNullOrBlank() -> { - // No display names, compare ids - leftRoomMemberSummary.userId.compareTo(rightRoomMemberSummary.userId) - } - else -> 1 - } - else -> - when { - rightRoomMemberSummary.displayName.isNullOrBlank() -> -1 - else -> { - when (leftRoomMemberSummary.displayName) { - rightRoomMemberSummary.displayName -> - // Same display name, compare id - leftRoomMemberSummary.userId.compareTo(rightRoomMemberSummary.userId) - else -> - leftRoomMemberSummary.displayName!!.compareTo(rightRoomMemberSummary.displayName!!, true) - } - } - } - } - } - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoleFormatter.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoleFormatter.kt index 6bc62cb53d..4febb822ad 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoleFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoleFormatter.kt @@ -19,8 +19,9 @@ class RoleFormatter @Inject constructor( return when (role) { Role.Admin -> stringProvider.getString(CommonStrings.power_level_admin) Role.Moderator -> stringProvider.getString(CommonStrings.power_level_moderator) - Role.Default -> stringProvider.getString(CommonStrings.power_level_default) - is Role.Custom -> stringProvider.getString(CommonStrings.power_level_custom, role.value) + Role.User -> stringProvider.getString(CommonStrings.power_level_default) + Role.Creator -> stringProvider.getString(CommonStrings.power_level_owner) + Role.SuperAdmin -> stringProvider.getString(CommonStrings.power_level_owner) } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsAction.kt index a8eb77bd03..cb4376c98f 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsAction.kt @@ -8,9 +8,10 @@ package im.vector.app.features.roomprofile.permissions import im.vector.app.core.platform.VectorViewModelAction +import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel sealed class RoomPermissionsAction : VectorViewModelAction { object ToggleShowAllPermissions : RoomPermissionsAction() - data class UpdatePermission(val editablePermission: EditablePermission, val powerLevel: Int) : RoomPermissionsAction() + data class UpdatePermission(val editablePermission: EditablePermission, val powerLevel: UserPowerLevel.Value) : RoomPermissionsAction() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsController.kt index 2e5d89e409..e5255eb435 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsController.kt @@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.session.room.model.redactOrDefault import org.matrix.android.sdk.api.session.room.model.stateDefaultOrDefault import org.matrix.android.sdk.api.session.room.model.usersDefaultOrDefault import org.matrix.android.sdk.api.session.room.powerlevels.Role +import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel import javax.inject.Inject class RoomPermissionsController @Inject constructor( @@ -34,7 +35,7 @@ class RoomPermissionsController @Inject constructor( ) : TypedEpoxyController() { interface Callback { - fun onEditPermission(editablePermission: EditablePermission, currentRole: Role) + fun onEditPermission(editablePermission: EditablePermission, currentPowerLevel: UserPowerLevel) fun toggleShowAllPermissions() } @@ -165,7 +166,8 @@ class RoomPermissionsController @Inject constructor( editable: Boolean, isSpace: Boolean ) { - val currentRole = getCurrentRole(editablePermission, content) + val currentPowerLevel = getPowerLevel(editablePermission, content) + val currentRole = Role.getSuggestedRole(currentPowerLevel) buildProfileAction( id = editablePermission.labelResId.toString(), title = stringProvider.getString( @@ -177,12 +179,12 @@ class RoomPermissionsController @Inject constructor( action = { callback ?.takeIf { editable } - ?.onEditPermission(editablePermission, currentRole) + ?.onEditPermission(editablePermission, currentPowerLevel) } ) } - private fun getCurrentRole(editablePermission: EditablePermission, content: PowerLevelsContent): Role { + private fun getPowerLevel(editablePermission: EditablePermission, content: PowerLevelsContent): UserPowerLevel.Value { val value = when (editablePermission) { is EditablePermission.EventTypeEditablePermission -> content.events?.get(editablePermission.eventType) ?: content.stateDefaultOrDefault() is EditablePermission.DefaultRole -> content.usersDefaultOrDefault() @@ -194,20 +196,6 @@ class RoomPermissionsController @Inject constructor( is EditablePermission.RemoveMessagesSentByOthers -> content.redactOrDefault() is EditablePermission.NotifyEveryone -> content.notificationLevel(PowerLevelsContent.NOTIFICATIONS_ROOM_KEY) } - - return Role.fromValue( - value, - when (editablePermission) { - is EditablePermission.EventTypeEditablePermission -> content.stateDefaultOrDefault() - is EditablePermission.DefaultRole -> Role.Default.value - is EditablePermission.SendMessages -> Role.Default.value - is EditablePermission.InviteUsers -> Role.Moderator.value - is EditablePermission.ChangeSettings -> Role.Moderator.value - is EditablePermission.KickUsers -> Role.Moderator.value - is EditablePermission.BanUsers -> Role.Moderator.value - is EditablePermission.RemoveMessagesSentByOthers -> Role.Moderator.value - is EditablePermission.NotifyEveryone -> Role.Moderator.value - } - ) + return UserPowerLevel.Value(value) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt index 5d92a96b5f..ea1afbcfc5 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt @@ -26,7 +26,7 @@ import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.roommemberprofile.powerlevel.EditPowerLevelDialogs import im.vector.app.features.roomprofile.RoomProfileArgs import im.vector.lib.strings.CommonStrings -import org.matrix.android.sdk.api.session.room.powerlevels.Role +import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject @@ -93,8 +93,8 @@ class RoomPermissionsFragment : } } - override fun onEditPermission(editablePermission: EditablePermission, currentRole: Role) { - EditPowerLevelDialogs.showChoice(requireActivity(), editablePermission.labelResId, currentRole) { newPowerLevel -> + override fun onEditPermission(editablePermission: EditablePermission, currentPowerLevel: UserPowerLevel) { + EditPowerLevelDialogs.showChoice(requireActivity(), editablePermission.labelResId, currentPowerLevel) { newPowerLevel -> viewModel.handle(RoomPermissionsAction.UpdatePermission(editablePermission, newPowerLevel)) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt index 0b942d78cb..3757a1ddd0 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt @@ -9,6 +9,7 @@ package im.vector.app.features.roomprofile.permissions import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -24,7 +25,6 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap @@ -61,19 +61,23 @@ class RoomPermissionsViewModel @AssistedInject constructor( private fun observePowerLevel() { PowerLevelsFlowFactory(room) .createFlow() - .onEach { powerLevelContent -> - val powerLevelsHelper = PowerLevelsHelper(powerLevelContent) + .onEach { roomPowerLevels -> val permissions = RoomPermissionsViewState.ActionPermissions( - canChangePowerLevels = powerLevelsHelper.isUserAllowedToSend( + canChangePowerLevels = roomPowerLevels.isUserAllowedToSend( userId = session.myUserId, isState = true, eventType = EventType.STATE_ROOM_POWER_LEVELS ) ) + val powerLevelsContent = roomPowerLevels.powerLevelsContent setState { copy( actionPermissions = permissions, - currentPowerLevelsContent = Success(powerLevelContent) + currentPowerLevelsContent = if (powerLevelsContent != null) { + Success(powerLevelsContent) + } else { + Uninitialized + } ) } }.launchIn(viewModelScope) @@ -94,26 +98,26 @@ class RoomPermissionsViewModel @AssistedInject constructor( private fun updatePermission(action: RoomPermissionsAction.UpdatePermission) { withState { state -> - val currentPowerLevel = state.currentPowerLevelsContent.invoke() ?: return@withState + val currentPowerLevelsContent = state.currentPowerLevelsContent.invoke() ?: return@withState postLoading(true) viewModelScope.launch { try { val newPowerLevelsContent = when (action.editablePermission) { - is EditablePermission.EventTypeEditablePermission -> currentPowerLevel.copy( - events = currentPowerLevel.events.orEmpty().toMutableMap().apply { - put(action.editablePermission.eventType, action.powerLevel) + is EditablePermission.EventTypeEditablePermission -> currentPowerLevelsContent.copy( + events = currentPowerLevelsContent.events.orEmpty().toMutableMap().apply { + put(action.editablePermission.eventType, action.powerLevel.value) } ) - is EditablePermission.DefaultRole -> currentPowerLevel.copy(usersDefault = action.powerLevel) - is EditablePermission.SendMessages -> currentPowerLevel.copy(eventsDefault = action.powerLevel) - is EditablePermission.InviteUsers -> currentPowerLevel.copy(invite = action.powerLevel) - is EditablePermission.ChangeSettings -> currentPowerLevel.copy(stateDefault = action.powerLevel) - is EditablePermission.KickUsers -> currentPowerLevel.copy(kick = action.powerLevel) - is EditablePermission.BanUsers -> currentPowerLevel.copy(ban = action.powerLevel) - is EditablePermission.RemoveMessagesSentByOthers -> currentPowerLevel.copy(redact = action.powerLevel) - is EditablePermission.NotifyEveryone -> currentPowerLevel.copy( - notifications = currentPowerLevel.notifications.orEmpty().toMutableMap().apply { - put(PowerLevelsContent.NOTIFICATIONS_ROOM_KEY, action.powerLevel) + is EditablePermission.DefaultRole -> currentPowerLevelsContent.copy(usersDefault = action.powerLevel.value) + is EditablePermission.SendMessages -> currentPowerLevelsContent.copy(eventsDefault = action.powerLevel.value) + is EditablePermission.InviteUsers -> currentPowerLevelsContent.copy(invite = action.powerLevel.value) + is EditablePermission.ChangeSettings -> currentPowerLevelsContent.copy(stateDefault = action.powerLevel.value) + is EditablePermission.KickUsers -> currentPowerLevelsContent.copy(kick = action.powerLevel.value) + is EditablePermission.BanUsers -> currentPowerLevelsContent.copy(ban = action.powerLevel.value) + is EditablePermission.RemoveMessagesSentByOthers -> currentPowerLevelsContent.copy(redact = action.powerLevel.value) + is EditablePermission.NotifyEveryone -> currentPowerLevelsContent.copy( + notifications = currentPowerLevelsContent.notifications.orEmpty().toMutableMap().apply { + put(PowerLevelsContent.NOTIFICATIONS_ROOM_KEY, action.powerLevel.value) } ) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt index af043b9ab2..188068afb4 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -32,7 +32,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.mapOptional import org.matrix.android.sdk.flow.unwrap @@ -115,28 +114,26 @@ class RoomSettingsViewModel @AssistedInject constructor( ) } - val powerLevelsContentLive = PowerLevelsFlowFactory(room).createFlow() - - powerLevelsContentLive - .onEach { - val powerLevelsHelper = PowerLevelsHelper(it) + val powerLevelsFlow = PowerLevelsFlowFactory(room).createFlow() + powerLevelsFlow + .onEach { roomPowerLevels -> val permissions = RoomSettingsViewState.ActionPermissions( - canChangeAvatar = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_AVATAR), - canChangeName = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_NAME), - canChangeTopic = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_TOPIC), - canChangeHistoryVisibility = powerLevelsHelper.isUserAllowedToSend( + canChangeAvatar = roomPowerLevels.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_AVATAR), + canChangeName = roomPowerLevels.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_NAME), + canChangeTopic = roomPowerLevels.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_TOPIC), + canChangeHistoryVisibility = roomPowerLevels.isUserAllowedToSend( session.myUserId, true, EventType.STATE_ROOM_HISTORY_VISIBILITY ), - canChangeJoinRule = powerLevelsHelper.isUserAllowedToSend( + canChangeJoinRule = roomPowerLevels.isUserAllowedToSend( session.myUserId, true, EventType.STATE_ROOM_JOIN_RULES ) && - powerLevelsHelper.isUserAllowedToSend( + roomPowerLevels.isUserAllowedToSend( session.myUserId, true, EventType.STATE_ROOM_GUEST_ACCESS ), - canAddChildren = powerLevelsHelper.isUserAllowedToSend( + canAddChildren = roomPowerLevels.isUserAllowedToSend( session.myUserId, true, EventType.STATE_SPACE_CHILD ) diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt index 404cfac9de..cebfb63f1f 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt @@ -32,7 +32,6 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.flow.flow @@ -74,20 +73,19 @@ class SpaceMenuViewModel @AssistedInject constructor( PowerLevelsFlowFactory(room) .createFlow() - .onEach { - val powerLevelsHelper = PowerLevelsHelper(it) + .onEach { roomPowerLevels -> - val canInvite = powerLevelsHelper.isUserAbleToInvite(session.myUserId) - val canAddChild = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_SPACE_CHILD) + val canInvite = roomPowerLevels.isUserAbleToInvite(session.myUserId) + val canAddChild = roomPowerLevels.isUserAllowedToSend(session.myUserId, true, EventType.STATE_SPACE_CHILD) - val canChangeAvatar = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_AVATAR) - val canChangeName = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_NAME) - val canChangeTopic = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_TOPIC) + val canChangeAvatar = roomPowerLevels.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_AVATAR) + val canChangeName = roomPowerLevels.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_NAME) + val canChangeTopic = roomPowerLevels.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_TOPIC) - val isAdmin = powerLevelsHelper.getUserRole(session.myUserId) is Role.Admin + val isAdmin = roomPowerLevels.getUserRole(session.myUserId) == Role.Admin val otherAdminCount = roomSummary?.otherMemberIds - ?.map { powerLevelsHelper.getUserRole(it) } - ?.count { it is Role.Admin } + ?.map { roomPowerLevels.getUserRole(it) } + ?.count { it == Role.Admin } ?: 0 val isLastAdmin = isAdmin && otherAdminCount == 0 diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt index bf91be4fa0..ca65b99fd4 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt @@ -27,6 +27,7 @@ import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset import org.matrix.android.sdk.api.session.room.model.create.RestrictedRoomPreset import org.matrix.android.sdk.api.session.room.powerlevels.Role +import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel import org.matrix.android.sdk.api.session.space.CreateSpaceParams import timber.log.Timber import javax.inject.Inject @@ -65,7 +66,7 @@ class CreateSpaceViewModelTask @Inject constructor( if (params.isPublic) { this.roomAliasName = params.spaceAlias this.powerLevelContentOverride = (powerLevelContentOverride ?: PowerLevelsContent()).copy( - invite = Role.Default.value + invite = UserPowerLevel.User.value ) this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT this.historyVisibility = RoomHistoryVisibility.WORLD_READABLE @@ -79,7 +80,7 @@ class CreateSpaceViewModelTask @Inject constructor( } ) this.powerLevelContentOverride = (powerLevelContentOverride ?: PowerLevelsContent()).copy( - invite = Role.Moderator.value + invite = UserPowerLevel.Moderator.value ) } }) diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt index ae112908fc..3627dd5469 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt @@ -36,7 +36,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.flow.flow import timber.log.Timber @@ -96,16 +95,14 @@ class SpaceDirectoryViewModel @AssistedInject constructor( private fun observePermissions() { val room = session.getRoom(initialState.spaceId) ?: return - val powerLevelsContentLive = PowerLevelsFlowFactory(room).createFlow() + val powerLevelsFlow = PowerLevelsFlowFactory(room).createFlow() - powerLevelsContentLive - .onEach { - val powerLevelsHelper = PowerLevelsHelper(it) + powerLevelsFlow + .onEach { roomPowerLevels -> setState { copy( - canAddRooms = powerLevelsHelper.isUserAllowedToSend( - session.myUserId, true, - EventType.STATE_SPACE_CHILD + canAddRooms = roomPowerLevels.isUserAllowedToSend( + userId = session.myUserId, isState = true, eventType = EventType.STATE_SPACE_CHILD ) ) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt index de68b697ef..b8a3bbd07d 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt @@ -31,10 +31,11 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.getRoomPowerLevels import org.matrix.android.sdk.api.session.room.getStateEvent import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.flow.flow @@ -50,14 +51,12 @@ class SpaceLeaveAdvancedViewModel @AssistedInject constructor( init { val space = session.getRoom(initialState.spaceId) val spaceSummary = space?.roomSummary() - - val powerLevelsEvent = space?.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) - powerLevelsEvent?.content?.toModel()?.let { powerLevelsContent -> - val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent) - val isAdmin = powerLevelsHelper.getUserRole(session.myUserId) is Role.Admin + val roomPowerLevels = space?.getRoomPowerLevels() + roomPowerLevels?.let { + val isAdmin = roomPowerLevels.getUserRole(session.myUserId) == Role.Admin val otherAdminCount = spaceSummary?.otherMemberIds - ?.map { powerLevelsHelper.getUserRole(it) } - ?.count { it is Role.Admin } + ?.map { roomPowerLevels.getUserRole(it) } + ?.count { it == Role.Admin } ?: 0 val isLastAdmin = isAdmin && otherAdminCount == 0 setState { diff --git a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleListController.kt b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleListController.kt index 1011952528..f8b5fada1a 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleListController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleListController.kt @@ -54,7 +54,7 @@ class SpacePeopleListController @Inject constructor( memberSummaries.forEach { memberEntry -> val filtered = memberEntry.second - .filter { roomMemberSummaryFilter.test(it) } + .filter { roomMemberSummaryFilter.test(it.summary) } if (filtered.isNotEmpty()) { dividerItem { id("divider_type_${memberEntry.first.titleRes}") @@ -65,10 +65,10 @@ class SpacePeopleListController @Inject constructor( .join( each = { _, roomMember -> profileMatrixItemWithPowerLevel { - id(roomMember.userId) - matrixItem(roomMember.toMatrixItem()) + id(roomMember.summary.userId) + matrixItem(roomMember.summary.toMatrixItem()) avatarRenderer(host.avatarRenderer) - userVerificationLevel(data.trustLevelMap.invoke()?.get(roomMember.userId)) + userVerificationLevel(data.trustLevelMap.invoke()?.get(roomMember.summary.userId)) .apply { val pl = host.toPowerLevelLabel(memberEntry.first) if (memberEntry.first == RoomMemberListCategories.INVITE) { @@ -106,13 +106,13 @@ class SpacePeopleListController @Inject constructor( } clickListener { - host.listener?.onSpaceMemberClicked(roomMember) + host.listener?.onSpaceMemberClicked(roomMember.summary) } } }, between = { _, roomMemberBefore -> dividerItem { - id("divider_${roomMemberBefore.userId}") + id("divider_${roomMemberBefore.summary.userId}") } } ) diff --git a/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt index 09b23bbed8..f5b6a0a06c 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt @@ -23,7 +23,7 @@ import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoomSummary -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels class ShareSpaceViewModel @AssistedInject constructor( @Assisted private val initialState: ShareSpaceViewState, @@ -52,11 +52,10 @@ class ShareSpaceViewModel @AssistedInject constructor( val room = session.getRoom(initialState.spaceId) ?: return PowerLevelsFlowFactory(room) .createFlow() - .onEach { powerLevelContent -> - val powerLevelsHelper = PowerLevelsHelper(powerLevelContent) + .onEach { roomPowerLevels -> setState { copy( - canInviteByMxId = powerLevelsHelper.isUserAbleToInvite(session.myUserId) + canInviteByMxId = roomPowerLevels.isUserAbleToInvite(session.myUserId) ) } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/usecase/StartVoiceBroadcastUseCase.kt index 233cb82d5d..973ea11f47 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/usecase/StartVoiceBroadcastUseCase.kt @@ -33,10 +33,11 @@ import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.getRoomPowerLevels import org.matrix.android.sdk.api.session.room.getStateEvent import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap import timber.log.Timber @@ -139,12 +140,8 @@ class StartVoiceBroadcastUseCase @Inject constructor( @VisibleForTesting fun assertHasEnoughPowerLevels(room: Room) { - val powerLevelsHelper = room.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) - ?.content - ?.toModel() - ?.let { PowerLevelsHelper(it) } - - if (powerLevelsHelper?.isUserAllowedToSend(session.myUserId, true, VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO) != true) { + val roomPowerLevels = room.getRoomPowerLevels() + if (!roomPowerLevels.isUserAllowedToSend(session.myUserId, true, VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO)) { Timber.d("## StartVoiceBroadcastUseCase: Cannot start voice broadcast: no permission") throw VoiceBroadcastFailure.RecordingError.NoPermission } diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt index 61f79662b0..326567fb07 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt @@ -25,10 +25,11 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.getRoomPowerLevels import org.matrix.android.sdk.api.session.room.getStateEvent import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.widgets.WidgetPostAPIMediator import org.matrix.android.sdk.api.util.JsonDict import timber.log.Timber @@ -146,13 +147,8 @@ class WidgetPostAPIHandler @AssistedInject constructor( Timber.d("## canSendEvent() : eventType $eventType isState $isState") - val powerLevelsEvent = room.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) - val powerLevelsContent = powerLevelsEvent?.content?.toModel() - val canSend = if (powerLevelsContent == null) { - false - } else { - PowerLevelsHelper(powerLevelsContent).isUserAllowedToSend(session.myUserId, isState, eventType) - } + val roomPowerLevels = room.getRoomPowerLevels() + val canSend = roomPowerLevels.isUserAllowedToSend(session.myUserId, isState, eventType) if (canSend) { Timber.d("## canSendEvent() returns true") widgetPostAPIMediator.sendBoolResponse(true, eventData) diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt index 6580b52fca..1778423db7 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt @@ -19,9 +19,11 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider +import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.widgets.permissions.WidgetPermissionsHelper import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session @@ -31,7 +33,7 @@ import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent -import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.widgets.WidgetManagementFailure import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.mapOptional @@ -102,11 +104,10 @@ class WidgetViewModel @AssistedInject constructor( if (room == null) { return } - room.flow().liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) - .mapOptional { it.content.toModel() } - .unwrap() - .map { - PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, true, null) + PowerLevelsFlowFactory(room) + .createFlow() + .map { roomPowerLevels -> + roomPowerLevels.isUserAllowedToSend(session.myUserId, true, null) } .setOnEach { copy(canManageWidgets = it) diff --git a/vector/src/test/java/im/vector/app/features/MemberListViewModelTest.kt b/vector/src/test/java/im/vector/app/features/MemberListViewModelTest.kt index 18ea98db3e..0f540bd2cd 100644 --- a/vector/src/test/java/im/vector/app/features/MemberListViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/MemberListViewModelTest.kt @@ -13,7 +13,7 @@ import com.airbnb.mvrx.test.MavericksTestRule import im.vector.app.features.roomprofile.RoomProfileArgs import im.vector.app.features.roomprofile.members.RoomMemberListViewModel import im.vector.app.features.roomprofile.members.RoomMemberListViewState -import im.vector.app.features.roomprofile.members.RoomMemberSummaryComparator +import im.vector.app.features.roomprofile.members.RoomMemberListComparator import im.vector.app.test.test import im.vector.app.test.testCoroutineDispatchers import io.mockk.coEvery @@ -266,7 +266,7 @@ class MemberListViewModelTest { private fun createViewModel(): RoomMemberListViewModel { return RoomMemberListViewModel( RoomMemberListViewState(args), - RoomMemberSummaryComparator(), + RoomMemberListComparator(), fakeSession, ) } From 2d21c15e3b3937fc2e01821fdc4d9975f90599ad Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 28 Jul 2025 16:49:57 +0200 Subject: [PATCH 34/46] change (power level) : continue handling v12 room --- .../sdk/api/session/room/powerlevels/Role.kt | 1 - .../room/powerlevels/RoomPowerLevels.kt | 2 +- .../timeline/format/NoticeEventFormatter.kt | 4 +- .../im/vector/app/features/powerlevel/Role.kt | 12 ++++ .../RoomMemberProfileController.kt | 4 +- .../RoomMemberProfileFragment.kt | 2 +- .../RoomMemberProfileViewModel.kt | 4 +- .../powerlevel/EditPowerLevelDialogs.kt | 32 ++++----- .../members/RoomMemberListComparator.kt | 4 +- .../permissions/RoomPermissionsController.kt | 2 +- .../permissions/RoomPermissionsFragment.kt | 2 +- .../app/features/spaces/SpaceMenuViewModel.kt | 4 +- .../leave/SpaceLeaveAdvanceViewState.kt | 2 +- .../leave/SpaceLeaveAdvancedFragment.kt | 2 +- .../leave/SpaceLeaveAdvancedViewModel.kt | 20 ++---- .../res/layout/dialog_edit_power_level.xml | 72 +++++++------------ 16 files changed, 77 insertions(+), 92 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/powerlevel/Role.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/Role.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/Role.kt index 57cd75e70d..dce48c8069 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/Role.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/Role.kt @@ -24,7 +24,6 @@ enum class Role { Moderator, User; - companion object { fun getSuggestedRole(userPowerLevel: UserPowerLevel): Role { return when { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/RoomPowerLevels.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/RoomPowerLevels.kt index d7fbeb8769..0d66136730 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/RoomPowerLevels.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/RoomPowerLevels.kt @@ -59,7 +59,7 @@ class RoomPowerLevels( * @param userId the user id * @return the power level */ - fun getUserRole(userId: String): Role { + fun getSuggestedRole(userId: String): Role { val value = getUserPowerLevel(userId) return Role.getSuggestedRole(value) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index 5a8d3fc41a..0e328bdbba 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -122,8 +122,8 @@ class NoticeEventFormatter @Inject constructor( userIds.addAll(previousPowerLevelsContent.users.orEmpty().keys) val diffs = ArrayList() userIds.forEach { userId -> - val from = RoomPowerLevels(previousPowerLevelsContent,null).getUserRole(userId) - val to = RoomPowerLevels(powerLevelsContent, null).getUserRole(userId) + val from = RoomPowerLevels(previousPowerLevelsContent,null).getSuggestedRole(userId) + val to = RoomPowerLevels(powerLevelsContent, null).getSuggestedRole(userId) if (from != to) { val fromStr = roleFormatter.format(from) val toStr = roleFormatter.format(to) diff --git a/vector/src/main/java/im/vector/app/features/powerlevel/Role.kt b/vector/src/main/java/im/vector/app/features/powerlevel/Role.kt new file mode 100644 index 0000000000..d61bbef768 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/powerlevel/Role.kt @@ -0,0 +1,12 @@ +/* + * Copyright 2025 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.features.powerlevel + +import org.matrix.android.sdk.api.session.room.powerlevels.Role + +fun Role.isOwner() = this == Role.Creator || this == Role.SuperAdmin diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt index f4420d6a42..308fdc5bce 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt @@ -17,7 +17,6 @@ import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import im.vector.lib.strings.CommonStrings import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel import javax.inject.Inject @@ -38,7 +37,7 @@ class RoomMemberProfileController @Inject constructor( fun onOverrideColorClicked() fun onJumpToReadReceiptClicked() fun onMentionClicked() - fun onEditPowerLevel(userPowerLevel: UserPowerLevel) + fun onEditPowerLevel(userPowerLevel: UserPowerLevel.Value) fun onKickClicked(isSpace: Boolean) fun onBanClicked(isSpace: Boolean, isUserBanned: Boolean) fun onCancelInviteClicked() @@ -250,6 +249,7 @@ class RoomMemberProfileController @Inject constructor( if ((!state.isMine && myPowerLevel <= userPowerLevel)) { return } + if(userPowerLevel !is UserPowerLevel.Value) return val membership = state.asyncMembership() ?: return val canKick = !state.isMine && state.actionPermissions.canKick val canBan = !state.isMine && state.actionPermissions.canBan diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt index 836e3857cc..7f0ec9b57a 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt @@ -377,7 +377,7 @@ class RoomMemberProfileFragment : .show() } - override fun onEditPowerLevel(userPowerLevel: UserPowerLevel) { + override fun onEditPowerLevel(userPowerLevel: UserPowerLevel.Value) { EditPowerLevelDialogs.showChoice(requireActivity(), CommonStrings.power_level_edit_title, userPowerLevel) { newPowerLevel -> viewModel.handle(RoomMemberProfileAction.SetPowerLevel(userPowerLevel, newPowerLevel, true)) } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt index 92ba7acfb5..14e286fd1d 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -388,9 +388,9 @@ class RoomMemberProfileViewModel @AssistedInject constructor( } roomSummaryLive.combine(powerLevelsFlow) { roomSummary, roomPowerLevels -> val roomName = roomSummary.toMatrixItem().getBestName() - when (roomPowerLevels.getUserRole(initialState.userId)) { + when (roomPowerLevels.getSuggestedRole(initialState.userId)) { Role.SuperAdmin, - Role.Creator, + Role.Creator -> stringProvider.getString(CommonStrings.room_member_power_level_owner_in, roomName) Role.Admin -> stringProvider.getString(CommonStrings.room_member_power_level_admin_in, roomName) Role.Moderator -> stringProvider.getString(CommonStrings.room_member_power_level_moderator_in, roomName) Role.User -> stringProvider.getString(CommonStrings.room_member_power_level_default_in, roomName) diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/powerlevel/EditPowerLevelDialogs.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/powerlevel/EditPowerLevelDialogs.kt index 6d24f198f4..c750b91f8f 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/powerlevel/EditPowerLevelDialogs.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/powerlevel/EditPowerLevelDialogs.kt @@ -17,6 +17,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.core.extensions.hideKeyboard import im.vector.app.databinding.DialogEditPowerLevelBinding +import im.vector.app.features.powerlevel.isOwner import im.vector.lib.strings.CommonStrings import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel @@ -27,46 +28,45 @@ object EditPowerLevelDialogs { fun showChoice( activity: Activity, @StringRes titleRes: Int, - currentPowerLevel: UserPowerLevel, + currentPowerLevel: UserPowerLevel.Value, listener: (UserPowerLevel.Value) -> Unit ) { val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_edit_power_level, null) val views = DialogEditPowerLevelBinding.bind(dialogLayout) - views.powerLevelRadioGroup.setOnCheckedChangeListener { _, checkedId -> - views.powerLevelCustomEditLayout.isVisible = checkedId == R.id.powerLevelCustomRadio - } val currentRole = Role.getSuggestedRole(currentPowerLevel) when (currentRole) { - Role.Creator -> views.powerLevelAdminRadio.isChecked = true - Role.SuperAdmin -> views.powerLevelAdminRadio.isChecked = true + Role.Creator, + Role.SuperAdmin -> views.powerLevelOwnerRadio.isChecked = true Role.Admin -> views.powerLevelAdminRadio.isChecked = true Role.Moderator -> views.powerLevelModeratorRadio.isChecked = true Role.User -> views.powerLevelDefaultRadio.isChecked = true } - + views.powerLevelOwnerRadio.isVisible = currentRole.isOwner() MaterialAlertDialogBuilder(activity) .setTitle(titleRes) .setView(dialogLayout) .setPositiveButton(CommonStrings.edit) { _, _ -> val newValue = when (views.powerLevelRadioGroup.checkedRadioButtonId) { + R.id.powerLevelOwnerRadio -> UserPowerLevel.SuperAdmin R.id.powerLevelAdminRadio -> UserPowerLevel.Admin R.id.powerLevelModeratorRadio -> UserPowerLevel.Moderator R.id.powerLevelDefaultRadio -> UserPowerLevel.User else -> null } - if(newValue != null) { + if (newValue != null) { listener(newValue) } } .setNegativeButton(CommonStrings.action_cancel, null) - .setOnKeyListener(DialogInterface.OnKeyListener - { dialog, keyCode, event -> - if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { - dialog.cancel() - return@OnKeyListener true - } - false - }) + .setOnKeyListener( + DialogInterface.OnKeyListener + { dialog, keyCode, event -> + if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { + dialog.cancel() + return@OnKeyListener true + } + false + }) .setOnDismissListener { dialogLayout.hideKeyboard() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListComparator.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListComparator.kt index a4ce99f63d..ffb4eaf522 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListComparator.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListComparator.kt @@ -23,8 +23,8 @@ class RoomMemberListComparator @Inject constructor() : Comparator -1 else -> when { - leftRoomMember.powerLevel > rightRoomMember.powerLevel -> 1 - leftRoomMember.powerLevel < rightRoomMember.powerLevel -> -1 + leftRoomMember.powerLevel > rightRoomMember.powerLevel -> -1 + leftRoomMember.powerLevel < rightRoomMember.powerLevel -> 1 leftRoomMember.summary.displayName.isNullOrBlank() -> when { rightRoomMember.summary.displayName.isNullOrBlank() -> { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsController.kt index e5255eb435..78a405db49 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsController.kt @@ -35,7 +35,7 @@ class RoomPermissionsController @Inject constructor( ) : TypedEpoxyController() { interface Callback { - fun onEditPermission(editablePermission: EditablePermission, currentPowerLevel: UserPowerLevel) + fun onEditPermission(editablePermission: EditablePermission, currentPowerLevel: UserPowerLevel.Value) fun toggleShowAllPermissions() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt index ea1afbcfc5..d2e94bf504 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt @@ -93,7 +93,7 @@ class RoomPermissionsFragment : } } - override fun onEditPermission(editablePermission: EditablePermission, currentPowerLevel: UserPowerLevel) { + override fun onEditPermission(editablePermission: EditablePermission, currentPowerLevel: UserPowerLevel.Value) { EditPowerLevelDialogs.showChoice(requireActivity(), editablePermission.labelResId, currentPowerLevel) { newPowerLevel -> viewModel.handle(RoomPermissionsAction.UpdatePermission(editablePermission, newPowerLevel)) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt index cebfb63f1f..c8ac7af8b8 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt @@ -82,9 +82,9 @@ class SpaceMenuViewModel @AssistedInject constructor( val canChangeName = roomPowerLevels.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_NAME) val canChangeTopic = roomPowerLevels.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_TOPIC) - val isAdmin = roomPowerLevels.getUserRole(session.myUserId) == Role.Admin + val isAdmin = roomPowerLevels.getSuggestedRole(session.myUserId) == Role.Admin val otherAdminCount = roomSummary?.otherMemberIds - ?.map { roomPowerLevels.getUserRole(it) } + ?.map { roomPowerLevels.getSuggestedRole(it) } ?.count { it == Role.Admin } ?: 0 val isLastAdmin = isAdmin && otherAdminCount == 0 diff --git a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvanceViewState.kt b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvanceViewState.kt index 48199cd2a2..0b1968b31e 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvanceViewState.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvanceViewState.kt @@ -21,7 +21,7 @@ data class SpaceLeaveAdvanceViewState( val currentFilter: String = "", val leaveState: Async = Uninitialized, val isFilteringEnabled: Boolean = false, - val isLastAdmin: Boolean = false + val isLastOwner: Boolean = false ) : MavericksState { constructor(args: SpaceBottomSheetSettingsArgs) : this( diff --git a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedFragment.kt index 1d9ff43a9d..1c1f33faa8 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedFragment.kt @@ -61,7 +61,7 @@ class SpaceLeaveAdvancedFragment : state.spaceSummary?.let { summary -> val warningMessage: CharSequence? = when { summary.otherMemberIds.isEmpty() -> getString(CommonStrings.space_leave_prompt_msg_only_you) - state.isLastAdmin -> getString(CommonStrings.space_leave_prompt_msg_as_admin) + state.isLastOwner -> getString(CommonStrings.space_leave_prompt_msg_as_admin) !summary.isPublic -> getString(CommonStrings.space_leave_prompt_msg_private) else -> null } diff --git a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt index b8a3bbd07d..3c06244888 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt @@ -20,23 +20,17 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.powerlevel.isOwner import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import okhttp3.internal.toImmutableList -import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.query.SpaceFilter import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.getRoomPowerLevels -import org.matrix.android.sdk.api.session.room.getStateEvent import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent -import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels -import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap @@ -53,14 +47,14 @@ class SpaceLeaveAdvancedViewModel @AssistedInject constructor( val spaceSummary = space?.roomSummary() val roomPowerLevels = space?.getRoomPowerLevels() roomPowerLevels?.let { - val isAdmin = roomPowerLevels.getUserRole(session.myUserId) == Role.Admin - val otherAdminCount = spaceSummary?.otherMemberIds - ?.map { roomPowerLevels.getUserRole(it) } - ?.count { it == Role.Admin } + val isOwner = roomPowerLevels.getSuggestedRole(session.myUserId).isOwner() + val otherOwnersCount = spaceSummary?.otherMemberIds + ?.map { roomPowerLevels.getSuggestedRole(it) } + ?.count { it.isOwner() } ?: 0 - val isLastAdmin = isAdmin && otherAdminCount == 0 + val isLastOwner = isOwner && otherOwnersCount == 0 setState { - copy(isLastAdmin = isLastAdmin) + copy(isLastOwner = isLastOwner) } } diff --git a/vector/src/main/res/layout/dialog_edit_power_level.xml b/vector/src/main/res/layout/dialog_edit_power_level.xml index bf91dc3367..00f6fbce56 100644 --- a/vector/src/main/res/layout/dialog_edit_power_level.xml +++ b/vector/src/main/res/layout/dialog_edit_power_level.xml @@ -1,5 +1,7 @@ - - + android:text="@string/power_level_owner" + android:textColor="?vctr_content_primary" /> - - - - - - - - - - - + android:text="@string/power_level_admin" + android:textColor="?vctr_content_primary" /> - + - + - + From 864346c3c0a5f4039c93483060efff0a46a56cf1 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 29 Jul 2025 16:34:23 +0200 Subject: [PATCH 35/46] change (leave room) : warn on last admin when leaving rooms --- .../src/main/res/values/strings.xml | 1 + .../sdk/api/session/room/RoomExtensions.kt | 3 + .../home/room/list/RoomListFragment.kt | 27 ++------ .../home/room/list/RoomListViewModel.kt | 8 ++- .../room/list/home/HomeRoomListFragment.kt | 28 ++------ .../room/list/home/HomeRoomListViewModel.kt | 6 +- .../im/vector/app/features/powerlevel/Role.kt | 31 +++++++++ .../app/features/room/LeaveRoomPrompt.kt | 67 +++++++++++++++++++ .../roomprofile/RoomProfileFragment.kt | 26 +++---- .../roomprofile/RoomProfileViewModel.kt | 9 +++ .../roomprofile/RoomProfileViewState.kt | 1 + .../leave/SpaceLeaveAdvanceViewState.kt | 2 +- .../leave/SpaceLeaveAdvancedFragment.kt | 28 ++++---- .../leave/SpaceLeaveAdvancedViewModel.kt | 22 ++---- 14 files changed, 163 insertions(+), 96 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/room/LeaveRoomPrompt.kt diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 02fef53306..f1922645ac 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -686,6 +686,7 @@ Leave room Are you sure you want to leave the room? This room is not public. You will not be able to rejoin without an invite. + You\'re the only admin of this room. Leaving it will mean no one has control over it. Direct Messages diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt index 29638857bc..f9c04ac34e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.api.session.room +import kotlinx.coroutines.flow.Flow import org.matrix.android.sdk.api.query.QueryStateEventValue import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Event @@ -25,6 +26,8 @@ import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.create.getRoomCreateContentWithSender import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.api.session.user.model.User +import org.matrix.android.sdk.internal.di.UserId /** * Get a TimelineEvent using the TimelineService of a Room. diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt index 1e04880382..51b413058e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt @@ -25,7 +25,6 @@ import com.airbnb.epoxy.OnModelBuildFinishedListener import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState -import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.epoxy.LayoutManagerStateRestorer @@ -45,6 +44,7 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA import im.vector.app.features.home.room.list.widget.NotifsFabMenuView import im.vector.app.features.matrixto.OriginOfMatrixTo import im.vector.app.features.notifications.NotificationDrawerManager +import im.vector.app.features.room.LeaveRoomPrompt import im.vector.lib.strings.CommonStrings import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn @@ -422,7 +422,7 @@ class RoomListFragment : } } - private fun handleQuickActions(quickAction: RoomListQuickActionsSharedAction) { + private suspend fun handleQuickActions(quickAction: RoomListQuickActionsSharedAction) { when (quickAction) { is RoomListQuickActionsSharedAction.NotificationsAllNoisy -> { roomListViewModel.handle(RoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.ALL_MESSAGES_NOISY)) @@ -451,26 +451,11 @@ class RoomListFragment : } } - private fun promptLeaveRoom(roomId: String) { - val isPublicRoom = roomListViewModel.isPublicRoom(roomId) - val message = buildString { - append(getString(CommonStrings.room_participants_leave_prompt_msg)) - if (!isPublicRoom) { - append("\n\n") - append(getString(CommonStrings.room_participants_leave_private_warning)) - } + private suspend fun promptLeaveRoom(roomId: String) { + val warning = roomListViewModel.getLeaveRoomWarning(roomId) + LeaveRoomPrompt.show(requireContext(), warning) { + roomListViewModel.handle(RoomListAction.LeaveRoom(roomId)) } - MaterialAlertDialogBuilder( - requireContext(), - if (isPublicRoom) 0 else im.vector.lib.ui.styles.R.style.ThemeOverlay_Vector_MaterialAlertDialog_Destructive - ) - .setTitle(CommonStrings.room_participants_leave_prompt_title) - .setMessage(message) - .setPositiveButton(CommonStrings.action_leave) { _, _ -> - roomListViewModel.handle(RoomListAction.LeaveRoom(roomId)) - } - .setNegativeButton(CommonStrings.action_cancel, null) - .show() } override fun invalidate() = withState(roomListViewModel) { state -> diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index 907c4cca72..d5821fab81 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -26,6 +26,8 @@ import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom import im.vector.app.features.analytics.plan.JoinedRoom import im.vector.app.features.displayname.getBestName import im.vector.app.features.invite.AutoAcceptInvites +import im.vector.app.features.room.LeaveRoomPrompt +import im.vector.app.features.room.getLeaveRoomWarning import im.vector.app.features.settings.VectorPreferences import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.distinctUntilChanged @@ -41,7 +43,6 @@ import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams -import org.matrix.android.sdk.api.session.room.state.isPublic import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.flow.flow import timber.log.Timber @@ -150,10 +151,11 @@ class RoomListViewModel @AssistedInject constructor( } } - fun isPublicRoom(roomId: String): Boolean { - return session.getRoom(roomId)?.stateService()?.isPublic().orFalse() + suspend fun getLeaveRoomWarning(roomId: String): LeaveRoomPrompt.Warning { + return session.getLeaveRoomWarning(roomId) } + // PRIVATE METHODS ***************************************************************************** private fun handleSelectRoom(action: RoomListAction.SelectRoom) = withState { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt index 02d702685a..a6421f19a0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt @@ -18,7 +18,6 @@ import androidx.recyclerview.widget.ConcatAdapter.Config.StableIdMode import androidx.recyclerview.widget.LinearLayoutManager import com.airbnb.epoxy.OnModelBuildFinishedListener import com.airbnb.mvrx.fragmentViewModel -import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.epoxy.LayoutManagerStateRestorer import im.vector.app.core.extensions.cleanup @@ -36,7 +35,7 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA import im.vector.app.features.home.room.list.home.header.HomeRoomFilter import im.vector.app.features.home.room.list.home.header.HomeRoomsHeadersController import im.vector.app.features.home.room.list.home.invites.InvitesActivity -import im.vector.lib.strings.CommonStrings +import im.vector.app.features.room.LeaveRoomPrompt import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -103,7 +102,7 @@ class HomeRoomListFragment : } } - private fun handleQuickActions(quickAction: RoomListQuickActionsSharedAction) { + private suspend fun handleQuickActions(quickAction: RoomListQuickActionsSharedAction) { when (quickAction) { is RoomListQuickActionsSharedAction.NotificationsAllNoisy -> { roomListViewModel.handle(HomeRoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.ALL_MESSAGES_NOISY)) @@ -185,26 +184,11 @@ class HomeRoomListFragment : concatAdapter.addAdapter(roomsAdapter) } - private fun promptLeaveRoom(roomId: String) { - val isPublicRoom = roomListViewModel.isPublicRoom(roomId) - val message = buildString { - append(getString(CommonStrings.room_participants_leave_prompt_msg)) - if (!isPublicRoom) { - append("\n\n") - append(getString(CommonStrings.room_participants_leave_private_warning)) - } + private suspend fun promptLeaveRoom(roomId: String) { + val warning = roomListViewModel.getLeaveRoomWarning(roomId) + LeaveRoomPrompt.show(requireContext(), warning) { + roomListViewModel.handle(HomeRoomListAction.LeaveRoom(roomId)) } - MaterialAlertDialogBuilder( - requireContext(), - if (isPublicRoom) 0 else im.vector.lib.ui.styles.R.style.ThemeOverlay_Vector_MaterialAlertDialog_Destructive - ) - .setTitle(CommonStrings.room_participants_leave_prompt_title) - .setMessage(message) - .setPositiveButton(CommonStrings.action_leave) { _, _ -> - roomListViewModel.handle(HomeRoomListAction.LeaveRoom(roomId)) - } - .setNegativeButton(CommonStrings.action_cancel, null) - .show() } private fun onInvitesCounterClicked() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt index d232dab3fb..2b367d75e9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt @@ -26,6 +26,8 @@ import im.vector.app.features.analytics.extensions.toTrackingValue import im.vector.app.features.analytics.plan.UserProperties import im.vector.app.features.displayname.getBestName import im.vector.app.features.home.room.list.home.header.HomeRoomFilter +import im.vector.app.features.room.LeaveRoomPrompt +import im.vector.app.features.room.getLeaveRoomWarning import im.vector.lib.strings.CommonStrings import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -331,8 +333,8 @@ class HomeRoomListViewModel @AssistedInject constructor( filteredPagedRoomSummariesLive.queryParams = getFilteredQueryParams(newFilter, filteredPagedRoomSummariesLive.queryParams) } - fun isPublicRoom(roomId: String): Boolean { - return session.getRoom(roomId)?.stateService()?.isPublic().orFalse() + suspend fun getLeaveRoomWarning(roomId: String): LeaveRoomPrompt.Warning { + return session.getLeaveRoomWarning(roomId) } private fun handleSelectRoom(action: HomeRoomListAction.SelectRoom) = withState { diff --git a/vector/src/main/java/im/vector/app/features/powerlevel/Role.kt b/vector/src/main/java/im/vector/app/features/powerlevel/Role.kt index d61bbef768..6ac6449820 100644 --- a/vector/src/main/java/im/vector/app/features/powerlevel/Role.kt +++ b/vector/src/main/java/im/vector/app/features/powerlevel/Role.kt @@ -7,6 +7,37 @@ package im.vector.app.features.powerlevel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.powerlevels.Role +import org.matrix.android.sdk.flow.flow fun Role.isOwner() = this == Role.Creator || this == Role.SuperAdmin + +fun Room.membersByRoleFlow(): Flow>> { + val roomMembersFlow = flow().liveRoomMembers(roomMemberQueryParams()) + val roomPowerLevelsFlow = PowerLevelsFlowFactory(this).createFlow() + return combine(roomMembersFlow, roomPowerLevelsFlow) { roomMembers, roomPowerLevels -> + roomMembers.groupBy { roomPowerLevels.getSuggestedRole(it.userId) } + }.distinctUntilChanged() +} + +fun Room.isLastAdminFlow(userId: String): Flow { + return membersByRoleFlow().map { membersByRole -> + val creatorMembers = membersByRole[Role.Creator].orEmpty() + val superAdminMembers = membersByRole[Role.SuperAdmin].orEmpty() + val adminMembers = membersByRole[Role.Admin].orEmpty() + val joinedAdmins = (adminMembers + creatorMembers + superAdminMembers).filter { it.membership == Membership.JOIN } + if (joinedAdmins.size == 1) { + joinedAdmins.first().userId == userId + } else { + false + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/room/LeaveRoomPrompt.kt b/vector/src/main/java/im/vector/app/features/room/LeaveRoomPrompt.kt new file mode 100644 index 0000000000..a43e7e35ed --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/room/LeaveRoomPrompt.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2025 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.features.room + +import android.content.Context +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import im.vector.app.features.powerlevel.isLastAdminFlow +import im.vector.app.features.room.LeaveRoomPrompt.Warning +import im.vector.lib.strings.CommonStrings +import im.vector.lib.ui.styles.R +import kotlinx.coroutines.flow.first +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.state.isPublic + +object LeaveRoomPrompt { + + enum class Warning { + LAST_ADMIN, + PRIVATE_ROOM, + NONE + }; + + fun show( + context: Context, + warning: Warning, + onLeaveClick: () -> Unit + ) { + val hasWarning = warning != Warning.NONE + val message = buildString { + append(context.getString(CommonStrings.room_participants_leave_prompt_msg)) + if (hasWarning) append("\n\n") + when (warning) { + Warning.LAST_ADMIN -> append(context.getString(CommonStrings.room_participants_leave_last_admin)) + Warning.PRIVATE_ROOM -> append(context.getString(CommonStrings.room_participants_leave_private_warning)) + Warning.NONE -> Unit + } + } + MaterialAlertDialogBuilder( + context, + if (hasWarning) R.style.ThemeOverlay_Vector_MaterialAlertDialog_Destructive else 0 + ) + .setTitle(CommonStrings.room_participants_leave_prompt_title) + .setMessage(message) + .setPositiveButton(CommonStrings.action_leave) { _, _ -> + onLeaveClick() + } + .setNegativeButton(CommonStrings.action_cancel, null) + .show() + } +} + +suspend fun Session.getLeaveRoomWarning(roomId: String): Warning { + val room = getRoom(roomId) ?: return Warning.NONE + val isLastAdmin = room.isLastAdminFlow(myUserId).first() + return when { + isLastAdmin -> Warning.LAST_ADMIN + !room.stateService().isPublic() -> Warning.PRIVATE_ROOM + else -> Warning.NONE + } +} + diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt index 1a56de9fab..54dc119696 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt @@ -45,6 +45,7 @@ import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel import im.vector.app.features.navigation.SettingsActivityPayload +import im.vector.app.features.room.LeaveRoomPrompt import im.vector.lib.strings.CommonStrings import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -320,25 +321,16 @@ class RoomProfileFragment : } override fun onLeaveRoomClicked() { - val isPublicRoom = roomProfileViewModel.isPublicRoom() - val message = buildString { - append(getString(CommonStrings.room_participants_leave_prompt_msg)) - if (!isPublicRoom) { - append("\n\n") - append(getString(CommonStrings.room_participants_leave_private_warning)) + withState(roomProfileViewModel){ state -> + val warning = when { + state.isLastAdmin -> LeaveRoomPrompt.Warning.LAST_ADMIN + state.roomSummary()?.isPublic == false -> LeaveRoomPrompt.Warning.PRIVATE_ROOM + else -> LeaveRoomPrompt.Warning.NONE + } + LeaveRoomPrompt.show(requireContext(), warning){ + roomProfileViewModel.handle(RoomProfileAction.LeaveRoom) } } - MaterialAlertDialogBuilder( - requireContext(), - if (isPublicRoom) 0 else im.vector.lib.ui.styles.R.style.ThemeOverlay_Vector_MaterialAlertDialog_Destructive - ) - .setTitle(CommonStrings.room_participants_leave_prompt_title) - .setMessage(message) - .setPositiveButton(CommonStrings.action_leave) { _, _ -> - roomProfileViewModel.handle(RoomProfileAction.LeaveRoom) - } - .setNegativeButton(CommonStrings.action_cancel, null) - .show() } override fun onRoomAliasesClicked() { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt index deb609509a..87c760ce45 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt @@ -20,6 +20,7 @@ import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.plan.Interaction import im.vector.app.features.home.ShortcutCreator import im.vector.app.features.powerlevel.PowerLevelsFlowFactory +import im.vector.app.features.powerlevel.isLastAdminFlow import im.vector.app.features.session.coroutineScope import im.vector.lib.strings.CommonStrings import kotlinx.coroutines.Dispatchers @@ -72,6 +73,14 @@ class RoomProfileViewModel @AssistedInject constructor( observePermissions() observePowerLevels() observeCryptoSettings(flowRoom) + observeIsLastAdmin() + } + + private fun observeIsLastAdmin() { + room.isLastAdminFlow(session.myUserId) + .onEach { isLastAdmin -> + setState { copy(isLastAdmin = isLastAdmin) } + }.launchIn(viewModelScope) } private fun observeCryptoSettings(flowRoom: FlowRoom) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewState.kt index d6784dc8a2..580b5a1283 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewState.kt @@ -30,6 +30,7 @@ data class RoomProfileViewState( val encryptToVerifiedDeviceOnly: Async = Uninitialized, val globalCryptoConfig: Async = Uninitialized, val unverifiedDevicesInTheRoom: Async = Uninitialized, + val isLastAdmin: Boolean = false ) : MavericksState { constructor(args: RoomProfileArgs) : this(roomId = args.roomId) diff --git a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvanceViewState.kt b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvanceViewState.kt index 0b1968b31e..48199cd2a2 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvanceViewState.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvanceViewState.kt @@ -21,7 +21,7 @@ data class SpaceLeaveAdvanceViewState( val currentFilter: String = "", val leaveState: Async = Uninitialized, val isFilteringEnabled: Boolean = false, - val isLastOwner: Boolean = false + val isLastAdmin: Boolean = false ) : MavericksState { constructor(args: SpaceBottomSheetSettingsArgs) : this( diff --git a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedFragment.kt index 1c1f33faa8..3712c5c0ad 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedFragment.kt @@ -50,27 +50,12 @@ class SpaceLeaveAdvancedFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - controller.listener = this withState(viewModel) { state -> setupToolbar(views.toolbar) .setSubtitle(state.spaceSummary?.name) .allowBack() - - state.spaceSummary?.let { summary -> - val warningMessage: CharSequence? = when { - summary.otherMemberIds.isEmpty() -> getString(CommonStrings.space_leave_prompt_msg_only_you) - state.isLastOwner -> getString(CommonStrings.space_leave_prompt_msg_as_admin) - !summary.isPublic -> getString(CommonStrings.space_leave_prompt_msg_private) - else -> null - } - - views.spaceLeavePromptDescription.isVisible = warningMessage != null - views.spaceLeavePromptDescription.text = warningMessage - } - - views.spaceLeavePromptTitle.text = getString(CommonStrings.space_leave_prompt_msg_with_name, state.spaceSummary?.name ?: "") } views.roomList.configureWith(controller) @@ -107,6 +92,19 @@ class SpaceLeaveAdvancedFragment : override fun invalidate() = withState(viewModel) { state -> super.invalidate() + state.spaceSummary?.let { summary -> + val warningMessage: CharSequence? = when { + summary.otherMemberIds.isEmpty() -> getString(CommonStrings.space_leave_prompt_msg_only_you) + state.isLastAdmin -> getString(CommonStrings.space_leave_prompt_msg_as_admin) + !summary.isPublic -> getString(CommonStrings.space_leave_prompt_msg_private) + else -> null + } + views.spaceLeavePromptDescription.isVisible = warningMessage != null + views.spaceLeavePromptDescription.text = warningMessage + } + + views.spaceLeavePromptTitle.text = getString(CommonStrings.space_leave_prompt_msg_with_name, state.spaceSummary?.name ?: "") + if (state.isFilteringEnabled) { views.appBarLayout.setExpanded(false) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt index 3c06244888..7f0a2d4008 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt @@ -20,7 +20,7 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel -import im.vector.app.features.powerlevel.isOwner +import im.vector.app.features.powerlevel.isLastAdminFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -29,7 +29,6 @@ import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.query.SpaceFilter import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.getRoom -import org.matrix.android.sdk.api.session.room.getRoomPowerLevels import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.flow.flow @@ -44,20 +43,13 @@ class SpaceLeaveAdvancedViewModel @AssistedInject constructor( init { val space = session.getRoom(initialState.spaceId) - val spaceSummary = space?.roomSummary() - val roomPowerLevels = space?.getRoomPowerLevels() - roomPowerLevels?.let { - val isOwner = roomPowerLevels.getSuggestedRole(session.myUserId).isOwner() - val otherOwnersCount = spaceSummary?.otherMemberIds - ?.map { roomPowerLevels.getSuggestedRole(it) } - ?.count { it.isOwner() } - ?: 0 - val isLastOwner = isOwner && otherOwnersCount == 0 - setState { - copy(isLastOwner = isLastOwner) - } - } + space?.isLastAdminFlow(session.myUserId) + ?.onEach { isLastAdmin -> + setState { copy(isLastAdmin = isLastAdmin) } + }?.launchIn(viewModelScope) + + val spaceSummary = space?.roomSummary() setState { copy(spaceSummary = spaceSummary) } session.getRoom(initialState.spaceId) ?.flow() From 15cf848cf18247ace81bb8577ce488d03ad3f501 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 30 Jul 2025 12:12:51 +0200 Subject: [PATCH 36/46] test : try to fix some tests --- .../session/room/model/PowerLevelsContent.kt | 2 +- ...faultCreateLocalRoomStateEventsTaskTest.kt | 15 ++++++------- .../app/core/utils/PermissionChecker.kt | 21 ++++++++++++++++--- .../features/location/LocationTrackerTest.kt | 4 +++- .../live/map/LiveLocationMapViewModelTest.kt | 3 +++ .../app/test/fakes/FakePermissionChecker.kt | 16 ++++++++++++++ 6 files changed, 49 insertions(+), 12 deletions(-) create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakePermissionChecker.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt index 894640a488..cc9a5b5459 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt @@ -35,7 +35,7 @@ data class PowerLevelsContent( */ @Json(name = "kick") val kick: Int? = null, /** - * The level required to invite a user. Defaults to 50 if unspecified. + * The level required to invite a user. Defaults to 0 if unspecified. */ @Json(name = "invite") val invite: Int? = null, /** diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt index 0669776394..664e7d4b64 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt @@ -52,6 +52,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomTopicContent import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent import org.matrix.android.sdk.api.session.room.powerlevels.Role +import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel import org.matrix.android.sdk.api.session.user.UserService import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.internal.session.profile.ThirdPartyIdentifier.Companion.MEDIUM_EMAIL @@ -372,13 +373,13 @@ internal class DefaultCreateLocalRoomStateEventsTaskTest { // Power levels val powerLevelsContent = result.find { it.type == EventType.STATE_ROOM_POWER_LEVELS }?.content.toModel() powerLevelsContent.shouldNotBeNull() - powerLevelsContent.ban shouldBeEqualTo Role.Moderator.value - powerLevelsContent.kick shouldBeEqualTo Role.Moderator.value - powerLevelsContent.invite shouldBeEqualTo Role.Moderator.value - powerLevelsContent.redact shouldBeEqualTo Role.Moderator.value - powerLevelsContent.eventsDefault shouldBeEqualTo Role.User.value - powerLevelsContent.usersDefault shouldBeEqualTo Role.User.value - powerLevelsContent.stateDefault shouldBeEqualTo Role.Moderator.value + powerLevelsContent.ban shouldBeEqualTo UserPowerLevel.Moderator.value + powerLevelsContent.kick shouldBeEqualTo UserPowerLevel.Moderator.value + powerLevelsContent.invite shouldBeEqualTo UserPowerLevel.User.value + powerLevelsContent.redact shouldBeEqualTo UserPowerLevel.Moderator.value + powerLevelsContent.eventsDefault shouldBeEqualTo UserPowerLevel.User.value + powerLevelsContent.usersDefault shouldBeEqualTo UserPowerLevel.User.value + powerLevelsContent.stateDefault shouldBeEqualTo UserPowerLevel.Moderator.value // Guest access result.find { it.type == EventType.STATE_ROOM_GUEST_ACCESS } ?.content.toModel()?.guestAccess shouldBeEqualTo GuestAccess.Forbidden diff --git a/vector/src/main/java/im/vector/app/core/utils/PermissionChecker.kt b/vector/src/main/java/im/vector/app/core/utils/PermissionChecker.kt index 3840e028c0..1323edf188 100644 --- a/vector/src/main/java/im/vector/app/core/utils/PermissionChecker.kt +++ b/vector/src/main/java/im/vector/app/core/utils/PermissionChecker.kt @@ -10,12 +10,27 @@ package im.vector.app.core.utils import android.content.Context import android.content.pm.PackageManager import androidx.core.app.ActivityCompat +import dagger.Binds +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent import javax.inject.Inject -class PermissionChecker @Inject constructor( +interface PermissionChecker { + + @InstallIn(SingletonComponent::class) + @dagger.Module + interface Module { + @Binds + fun bindPermissionChecker(permissionChecker: AndroidPermissionChecker): PermissionChecker + } + + fun checkPermission(vararg permissions: String): Boolean +} + +class AndroidPermissionChecker @Inject constructor( private val applicationContext: Context, -) { - fun checkPermission(vararg permissions: String): Boolean { +) : PermissionChecker { + override fun checkPermission(vararg permissions: String): Boolean { return permissions.any { permission -> ActivityCompat.checkSelfPermission(applicationContext, permission) != PackageManager.PERMISSION_GRANTED } diff --git a/vector/src/test/java/im/vector/app/features/location/LocationTrackerTest.kt b/vector/src/test/java/im/vector/app/features/location/LocationTrackerTest.kt index 75660bb16f..40afb31b8f 100644 --- a/vector/src/test/java/im/vector/app/features/location/LocationTrackerTest.kt +++ b/vector/src/test/java/im/vector/app/features/location/LocationTrackerTest.kt @@ -14,6 +14,7 @@ import im.vector.app.features.session.coroutineScope import im.vector.app.test.fakes.FakeActiveSessionHolder import im.vector.app.test.fakes.FakeContext import im.vector.app.test.fakes.FakeLocationManager +import im.vector.app.test.fakes.FakePermissionChecker import im.vector.app.test.fixtures.aBuildMeta import im.vector.app.test.test import io.mockk.every @@ -29,6 +30,7 @@ import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBeEqualTo import org.junit.After import org.junit.Before +import org.junit.Ignore import org.junit.Test private const val A_LATITUDE = 1.2 @@ -48,7 +50,7 @@ class LocationTrackerTest { @Before fun setUp() { mockkStatic("im.vector.app.features.session.SessionCoroutineScopesKt") - locationTracker = LocationTracker(fakeContext.instance, fakeActiveSessionHolder.instance, aBuildMeta()) + locationTracker = LocationTracker(fakeContext.instance, fakeActiveSessionHolder.instance, aBuildMeta(), FakePermissionChecker()) fakeLocationManager.givenRemoveUpdates(locationTracker) } diff --git a/vector/src/test/java/im/vector/app/features/location/live/map/LiveLocationMapViewModelTest.kt b/vector/src/test/java/im/vector/app/features/location/live/map/LiveLocationMapViewModelTest.kt index 108bdbed34..cc76a806b9 100644 --- a/vector/src/test/java/im/vector/app/features/location/live/map/LiveLocationMapViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/location/live/map/LiveLocationMapViewModelTest.kt @@ -12,6 +12,7 @@ import im.vector.app.features.location.LocationData import im.vector.app.features.location.live.StopLiveLocationShareUseCase import im.vector.app.test.fakes.FakeLocationSharingServiceConnection import im.vector.app.test.fakes.FakeLocationTracker +import im.vector.app.test.fakes.FakePermissionChecker import im.vector.app.test.fakes.FakeSession import im.vector.app.test.test import io.mockk.every @@ -38,6 +39,7 @@ class LiveLocationMapViewModelTest { private val fakeLocationSharingServiceConnection = FakeLocationSharingServiceConnection() private val fakeStopLiveLocationShareUseCase = mockk() private val fakeLocationTracker = FakeLocationTracker() + private val fakePermissionChecker = FakePermissionChecker() private fun createViewModel(): LiveLocationMapViewModel { return LiveLocationMapViewModel( @@ -47,6 +49,7 @@ class LiveLocationMapViewModelTest { locationSharingServiceConnection = fakeLocationSharingServiceConnection.instance, stopLiveLocationShareUseCase = fakeStopLiveLocationShareUseCase, locationTracker = fakeLocationTracker.instance, + permissionChecker = fakePermissionChecker ) } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakePermissionChecker.kt b/vector/src/test/java/im/vector/app/test/fakes/FakePermissionChecker.kt new file mode 100644 index 0000000000..5665a706a7 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakePermissionChecker.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2022-2024 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.test.fakes + +import im.vector.app.core.utils.PermissionChecker + +class FakePermissionChecker(val permissionResult: Boolean = true) : PermissionChecker { + override fun checkPermission(vararg permissions: String): Boolean { + return permissionResult + } +} From cc99104060951ef441955f5c59d704bf7f1d6c3d Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 30 Jul 2025 15:03:04 +0200 Subject: [PATCH 37/46] misc : code clean up --- .../matrix/android/sdk/session/space/SpaceHierarchyTest.kt | 2 +- .../matrix/android/sdk/api/session/room/RoomExtensions.kt | 3 --- .../sdk/api/session/room/model/PowerLevelsContent.kt | 1 - .../internal/session/pushers/DefaultConditionResolver.kt | 7 ------- .../session/room/EventRelationsAggregationProcessor.kt | 1 - .../internal/session/room/state/StateEventDataSource.kt | 1 - .../sdk/internal/session/space/DefaultSpaceService.kt | 2 -- .../create/DefaultCreateLocalRoomStateEventsTaskTest.kt | 1 - .../room/detail/timeline/action/MessageActionsViewModel.kt | 1 - .../detail/timeline/factory/MergedHeaderItemFactory.kt | 4 ---- .../room/detail/timeline/format/NoticeEventFormatter.kt | 2 +- .../app/features/home/room/list/RoomListViewModel.kt | 1 - .../features/home/room/list/home/HomeRoomListViewModel.kt | 1 - .../app/features/location/LocationSharingViewModel.kt | 1 - .../app/features/powerlevel/PowerLevelsFlowFactory.kt | 1 - .../java/im/vector/app/features/room/LeaveRoomPrompt.kt | 3 +-- .../roommemberprofile/RoomMemberProfileController.kt | 2 +- .../vector/app/features/roomprofile/RoomProfileFragment.kt | 4 ++-- .../app/features/roomprofile/RoomProfileViewModel.kt | 1 - .../app/features/roomprofile/alias/RoomAliasViewModel.kt | 1 - .../roomprofile/banned/RoomBannedMemberListViewModel.kt | 1 - .../app/features/spaces/create/CreateSpaceViewModelTask.kt | 1 - .../app/features/spaces/share/ShareSpaceViewModel.kt | 1 - .../recording/usecase/StartVoiceBroadcastUseCase.kt | 6 ------ .../im/vector/app/features/widgets/WidgetPostAPIHandler.kt | 3 --- .../java/im/vector/app/features/widgets/WidgetViewModel.kt | 7 ------- .../java/im/vector/app/features/MemberListViewModelTest.kt | 2 +- .../im/vector/app/features/location/LocationTrackerTest.kt | 1 - 28 files changed, 7 insertions(+), 55 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt index 6c9d0ad983..df17fbf4a8 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt @@ -40,8 +40,8 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.RestrictedRoomPreset -import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.powerlevels.Role +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest import org.matrix.android.sdk.common.SessionTestParams diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt index f9c04ac34e..29638857bc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.api.session.room -import kotlinx.coroutines.flow.Flow import org.matrix.android.sdk.api.query.QueryStateEventValue import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Event @@ -26,8 +25,6 @@ import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.create.getRoomCreateContentWithSender import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent -import org.matrix.android.sdk.api.session.user.model.User -import org.matrix.android.sdk.internal.di.UserId /** * Get a TimelineEvent using the TimelineService of a Room. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt index cc9a5b5459..890b7d64b3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt @@ -115,7 +115,6 @@ private fun defaultNotificationLevel(key: String): Int { } } - // Fallback to default value, defined in the Matrix specification fun PowerLevelsContent?.banOrDefault() = this?.ban ?: UserPowerLevel.Moderator.value fun PowerLevelsContent?.kickOrDefault() = this?.kick ?: UserPowerLevel.Moderator.value diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt index c1fb93b3ff..3bc12eecb1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt @@ -15,20 +15,13 @@ */ package org.matrix.android.sdk.internal.session.pushers -import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.pushrules.ConditionResolver import org.matrix.android.sdk.api.session.pushrules.ContainsDisplayNameCondition import org.matrix.android.sdk.api.session.pushrules.EventMatchCondition import org.matrix.android.sdk.api.session.pushrules.RoomMemberCountCondition import org.matrix.android.sdk.api.session.pushrules.SenderNotificationPermissionCondition import org.matrix.android.sdk.api.session.room.getRoomPowerLevels -import org.matrix.android.sdk.api.session.room.getStateEvent -import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent -import org.matrix.android.sdk.api.session.room.model.create.getRoomCreateContentWithSender -import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.room.RoomGetter import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt index 3b963c13cb..821aeaa494 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt @@ -34,7 +34,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent -import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.crypto.verification.toState import org.matrix.android.sdk.internal.database.helper.findRootThreadEvent diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt index 433a4399a3..723d604ad4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt @@ -96,4 +96,3 @@ internal class StateEventDataSource @Inject constructor( } } } - diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt index 80ecc4fa81..9b6e87a736 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt @@ -35,8 +35,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset -import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels -import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel import org.matrix.android.sdk.api.session.space.CreateSpaceParams import org.matrix.android.sdk.api.session.space.JoinSpaceResult diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt index 664e7d4b64..b05a890c1b 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt @@ -51,7 +51,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent import org.matrix.android.sdk.api.session.room.model.RoomTopicContent import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent -import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel import org.matrix.android.sdk.api.session.user.UserService import org.matrix.android.sdk.api.session.user.model.User diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 8a4e3bb797..36f63f0b20 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -49,7 +49,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent -import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt index c36cd14641..012421bf8d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt @@ -24,17 +24,13 @@ import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovement import im.vector.lib.strings.CommonPlurals import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.getRoomPowerLevels -import org.matrix.android.sdk.api.session.room.getStateEvent -import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent -import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index 0e328bdbba..aea064736a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -122,7 +122,7 @@ class NoticeEventFormatter @Inject constructor( userIds.addAll(previousPowerLevelsContent.users.orEmpty().keys) val diffs = ArrayList() userIds.forEach { userId -> - val from = RoomPowerLevels(previousPowerLevelsContent,null).getSuggestedRole(userId) + val from = RoomPowerLevels(previousPowerLevelsContent, null).getSuggestedRole(userId) val to = RoomPowerLevels(powerLevelsContent, null).getSuggestedRole(userId) if (from != to) { val fromStr = roleFormatter.format(from) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index d5821fab81..a4dae15519 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -155,7 +155,6 @@ class RoomListViewModel @AssistedInject constructor( return session.getLeaveRoomWarning(roomId) } - // PRIVATE METHODS ***************************************************************************** private fun handleSelectRoom(action: RoomListAction.SelectRoom) = withState { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt index 2b367d75e9..b84de29630 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt @@ -55,7 +55,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams -import org.matrix.android.sdk.api.session.room.state.isPublic import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toOption diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt index dfa1ec4549..c000c2423a 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt @@ -34,7 +34,6 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getUserOrDefault -import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.util.toMatrixItem import timber.log.Timber diff --git a/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt b/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt index 689add249c..fbbfac98dc 100644 --- a/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt +++ b/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt @@ -16,7 +16,6 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent -import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent import org.matrix.android.sdk.api.session.room.model.create.getRoomCreateContentWithSender import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.flow.flow diff --git a/vector/src/main/java/im/vector/app/features/room/LeaveRoomPrompt.kt b/vector/src/main/java/im/vector/app/features/room/LeaveRoomPrompt.kt index a43e7e35ed..ac06dd471c 100644 --- a/vector/src/main/java/im/vector/app/features/room/LeaveRoomPrompt.kt +++ b/vector/src/main/java/im/vector/app/features/room/LeaveRoomPrompt.kt @@ -24,7 +24,7 @@ object LeaveRoomPrompt { LAST_ADMIN, PRIVATE_ROOM, NONE - }; + } fun show( context: Context, @@ -64,4 +64,3 @@ suspend fun Session.getLeaveRoomWarning(roomId: String): Warning { else -> Warning.NONE } } - diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt index 308fdc5bce..bef3c5bda5 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt @@ -249,7 +249,7 @@ class RoomMemberProfileController @Inject constructor( if ((!state.isMine && myPowerLevel <= userPowerLevel)) { return } - if(userPowerLevel !is UserPowerLevel.Value) return + if (userPowerLevel !is UserPowerLevel.Value) return val membership = state.asyncMembership() ?: return val canKick = !state.isMine && state.actionPermissions.canKick val canBan = !state.isMine && state.actionPermissions.canBan diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt index 54dc119696..6c1f3b14fd 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt @@ -321,13 +321,13 @@ class RoomProfileFragment : } override fun onLeaveRoomClicked() { - withState(roomProfileViewModel){ state -> + withState(roomProfileViewModel) { state -> val warning = when { state.isLastAdmin -> LeaveRoomPrompt.Warning.LAST_ADMIN state.roomSummary()?.isPublic == false -> LeaveRoomPrompt.Warning.PRIVATE_ROOM else -> LeaveRoomPrompt.Warning.NONE } - LeaveRoomPrompt.show(requireContext(), warning){ + LeaveRoomPrompt.show(requireContext(), warning) { roomProfileViewModel.handle(RoomProfileAction.LeaveRoom) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt index 87c760ce45..e2e70ee8b4 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt @@ -40,7 +40,6 @@ import org.matrix.android.sdk.api.session.room.getStateEvent import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent -import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.state.isPublic import org.matrix.android.sdk.flow.FlowRoom import org.matrix.android.sdk.flow.flow diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index 73f8e92434..a2bd121cf9 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -29,7 +29,6 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent -import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.mapOptional import org.matrix.android.sdk.flow.unwrap diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt index acfdbb8b1c..6c0bbcb0e5 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt @@ -29,7 +29,6 @@ import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary -import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt index ca65b99fd4..f12ab7b938 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt @@ -26,7 +26,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset import org.matrix.android.sdk.api.session.room.model.create.RestrictedRoomPreset -import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel import org.matrix.android.sdk.api.session.space.CreateSpaceParams import timber.log.Timber diff --git a/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt index f5b6a0a06c..b4d58f61a0 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt @@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoomSummary -import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels class ShareSpaceViewModel @AssistedInject constructor( @Assisted private val initialState: ShareSpaceViewState, diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/usecase/StartVoiceBroadcastUseCase.kt index 973ea11f47..3add1751b1 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/usecase/StartVoiceBroadcastUseCase.kt @@ -25,19 +25,13 @@ import im.vector.lib.multipicker.utils.toMultiPickerAudioType import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import org.jetbrains.annotations.VisibleForTesting -import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.toContent -import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.getRoomPowerLevels -import org.matrix.android.sdk.api.session.room.getStateEvent -import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent -import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap import timber.log.Timber diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt index 326567fb07..40c64c7f19 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt @@ -23,13 +23,10 @@ import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent -import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.getRoomPowerLevels import org.matrix.android.sdk.api.session.room.getStateEvent import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent -import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.widgets.WidgetPostAPIMediator import org.matrix.android.sdk.api.util.JsonDict import timber.log.Timber diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt index 1778423db7..f1df2cff2a 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt @@ -23,21 +23,14 @@ import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.widgets.permissions.WidgetPermissionsHelper import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.Content -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService -import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent -import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.widgets.WidgetManagementFailure import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.flow.mapOptional -import org.matrix.android.sdk.flow.unwrap import timber.log.Timber import javax.net.ssl.HttpsURLConnection diff --git a/vector/src/test/java/im/vector/app/features/MemberListViewModelTest.kt b/vector/src/test/java/im/vector/app/features/MemberListViewModelTest.kt index 0f540bd2cd..c833c46366 100644 --- a/vector/src/test/java/im/vector/app/features/MemberListViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/MemberListViewModelTest.kt @@ -11,9 +11,9 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.lifecycle.MutableLiveData import com.airbnb.mvrx.test.MavericksTestRule import im.vector.app.features.roomprofile.RoomProfileArgs +import im.vector.app.features.roomprofile.members.RoomMemberListComparator import im.vector.app.features.roomprofile.members.RoomMemberListViewModel import im.vector.app.features.roomprofile.members.RoomMemberListViewState -import im.vector.app.features.roomprofile.members.RoomMemberListComparator import im.vector.app.test.test import im.vector.app.test.testCoroutineDispatchers import io.mockk.coEvery diff --git a/vector/src/test/java/im/vector/app/features/location/LocationTrackerTest.kt b/vector/src/test/java/im/vector/app/features/location/LocationTrackerTest.kt index 40afb31b8f..f2d7f8a1d4 100644 --- a/vector/src/test/java/im/vector/app/features/location/LocationTrackerTest.kt +++ b/vector/src/test/java/im/vector/app/features/location/LocationTrackerTest.kt @@ -30,7 +30,6 @@ import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBeEqualTo import org.junit.After import org.junit.Before -import org.junit.Ignore import org.junit.Test private const val A_LATITUDE = 1.2 From fe0b54e8989201c8336ad6318a2368523c7f4ca7 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 30 Jul 2025 15:34:49 +0200 Subject: [PATCH 38/46] misc : fix copyright --- .../session/room/powerlevels/UserPowerLevel.kt | 16 +++++++++++++--- .../session/room/powerlevels/RoomPowerLevels.kt | 16 +++++++++++++--- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/UserPowerLevel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/UserPowerLevel.kt index 8932f16685..7d213cb269 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/UserPowerLevel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/UserPowerLevel.kt @@ -1,8 +1,18 @@ /* - * Copyright 2025 New Vector Ltd. + * Copyright 2025 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. */ package org.matrix.android.sdk.api.session.room.powerlevels diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/powerlevels/RoomPowerLevels.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/powerlevels/RoomPowerLevels.kt index d3556324e7..bbdbc4c123 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/powerlevels/RoomPowerLevels.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/powerlevels/RoomPowerLevels.kt @@ -1,8 +1,18 @@ /* - * Copyright 2025 New Vector Ltd. + * Copyright 2025 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. */ package org.matrix.android.sdk.internal.session.room.powerlevels From 43b20b2042d5fc944b60f9b2c250ccdcbfd82658 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Fri, 1 Aug 2025 11:08:26 +0100 Subject: [PATCH 39/46] Strip trailing whitespace from log lines Log lines coming from the Rust SDK have a trailing newline, meaning that when we emit them, we get a blank line in the logs. To prevent this, strip off trailing whitespace. --- .../java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt index a3c68c2230..bbc4c3bb39 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt @@ -95,7 +95,7 @@ import org.matrix.rustcomponents.sdk.crypto.ProgressListener as RustProgressList class CryptoLogger : Logger { override fun log(logLine: String) { - Timber.d(logLine) + Timber.d(logLine.trimEnd()) } } From a239f5fb08ef9fb7c9619e39a7298379e5531513 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 4 Aug 2025 18:19:25 +0200 Subject: [PATCH 40/46] misc : clean up after PR review --- .../org/matrix/android/sdk/flow/FlowRoom.kt | 5 ++ .../sdk/api/session/room/RoomExtensions.kt | 12 +---- .../api/session/room/state/StateService.kt | 3 ++ .../pushers/DefaultConditionResolver.kt | 2 - .../room/powerlevels/RoomPowerLevels.kt | 42 +++++++++++++++-- .../session/room/state/DefaultStateService.kt | 11 +++++ .../room/version/DefaultRoomVersionService.kt | 15 +----- .../home/room/detail/TimelineViewModel.kt | 3 +- .../composer/MessageComposerViewModel.kt | 3 +- .../action/MessageActionsViewModel.kt | 3 +- .../location/LocationSharingViewModel.kt | 4 +- .../powerlevel/PowerLevelsFlowFactory.kt | 47 ------------------- .../im/vector/app/features/powerlevel/Role.kt | 2 +- .../RoomMemberProfileViewModel.kt | 3 +- .../roomprofile/RoomProfileViewModel.kt | 6 +-- .../roomprofile/alias/RoomAliasViewModel.kt | 4 +- .../banned/RoomBannedMemberListViewModel.kt | 3 +- .../members/RoomMemberListViewModel.kt | 5 +- .../permissions/RoomPermissionsViewModel.kt | 4 +- .../settings/RoomSettingsViewModel.kt | 3 +- .../app/features/spaces/SpaceMenuViewModel.kt | 4 +- .../spaces/explore/SpaceDirectoryViewModel.kt | 3 +- .../spaces/share/ShareSpaceViewModel.kt | 5 +- .../app/features/widgets/WidgetViewModel.kt | 4 +- 24 files changed, 81 insertions(+), 115 deletions(-) delete mode 100644 vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt diff --git a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt index 94f09e0bf5..88dd677cb8 100644 --- a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt +++ b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt @@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.session.room.model.ReadReceipt import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.send.UserDraft import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.Optional @@ -95,6 +96,10 @@ class FlowRoom(private val room: Room) { } } + fun liveRoomPowerLevels(): Flow { + return room.stateService().getRoomPowerLevelsLive().asFlow() + } + fun liveReadMarker(): Flow> { return room.readService().getReadMarkerLive().asFlow() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt index 29638857bc..6da0f8df52 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt @@ -17,12 +17,7 @@ package org.matrix.android.sdk.api.session.room import org.matrix.android.sdk.api.query.QueryStateEventValue -import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent -import org.matrix.android.sdk.api.session.room.model.create.getRoomCreateContentWithSender import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -45,10 +40,5 @@ fun Room.getStateEvent(eventType: String, stateKey: QueryStateEventValue): Event * Get the current RoomPowerLevels of the room. */ fun Room.getRoomPowerLevels(): RoomPowerLevels { - val powerLevelsContent = getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty)?.content?.toModel() - val roomCreateContent = getStateEvent(EventType.STATE_ROOM_CREATE, QueryStringValue.IsEmpty)?.getRoomCreateContentWithSender() - return RoomPowerLevels( - powerLevelsContent, - roomCreateContent - ) + return stateService().getRoomPowerLevels() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt index 6ca63c2c49..6e01d7edfa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt @@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.Optional @@ -106,4 +107,6 @@ interface StateService { suspend fun setJoinRulePublic() suspend fun setJoinRuleInviteOnly() suspend fun setJoinRuleRestricted(allowList: List) + fun getRoomPowerLevels(): RoomPowerLevels + fun getRoomPowerLevelsLive(): LiveData } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt index 3bc12eecb1..3a7a43c8ca 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt @@ -24,13 +24,11 @@ import org.matrix.android.sdk.api.session.pushrules.SenderNotificationPermission import org.matrix.android.sdk.api.session.room.getRoomPowerLevels import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.room.RoomGetter -import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import javax.inject.Inject internal class DefaultConditionResolver @Inject constructor( private val roomGetter: RoomGetter, @UserId private val userId: String, - private val stateEventDataSource: StateEventDataSource, ) : ConditionResolver { override fun resolveEventMatchCondition( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/powerlevels/RoomPowerLevels.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/powerlevels/RoomPowerLevels.kt index bbdbc4c123..9243672f1a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/powerlevels/RoomPowerLevels.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/powerlevels/RoomPowerLevels.kt @@ -17,17 +17,53 @@ package org.matrix.android.sdk.internal.session.room.powerlevels +import androidx.lifecycle.LiveData +import androidx.lifecycle.MediatorLiveData import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.create.getRoomCreateContentWithSender import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels +import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource internal fun StateEventDataSource.getRoomPowerLevels(roomId: String): RoomPowerLevels { - val powerLevelsContent = getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) - ?.content?.toModel() - val roomCreateContent = getStateEvent(roomId, EventType.STATE_ROOM_CREATE, QueryStringValue.IsEmpty)?.getRoomCreateContentWithSender() + val powerLevelsEvent = getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) + val roomCreateEvent = getStateEvent(roomId, EventType.STATE_ROOM_CREATE, QueryStringValue.IsEmpty) + return createRoomPowerLevels(powerLevelsEvent = powerLevelsEvent, roomCreateEvent = roomCreateEvent) +} + +internal fun StateEventDataSource.getRoomPowerLevelsLive(roomId: String): LiveData { + val powerLevelsEventLive = getStateEventLive(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) + val roomCreateEventLive = getStateEventLive(roomId, EventType.STATE_ROOM_CREATE, QueryStringValue.IsEmpty) + val resultLiveData = MediatorLiveData() + + fun emitIfReady(powerLevelEvent: Optional?, roomCreateEvent: Optional?) { + if (powerLevelEvent != null && roomCreateEvent != null) { + val roomPowerLevels = createRoomPowerLevels(powerLevelEvent.get(), roomCreateEvent.get()) + resultLiveData.postValue(roomPowerLevels) + } + } + resultLiveData.apply { + var powerLevelEvent: Optional? = null + var roomCreateEvent: Optional? = null + + addSource(powerLevelsEventLive) { + powerLevelEvent = it + emitIfReady(powerLevelEvent, roomCreateEvent) + } + addSource(roomCreateEventLive) { + roomCreateEvent = it + emitIfReady(powerLevelEvent, roomCreateEvent) + } + } + return resultLiveData +} + +private fun createRoomPowerLevels(powerLevelsEvent: Event?, roomCreateEvent: Event?): RoomPowerLevels { + val powerLevelsContent = powerLevelsEvent?.content?.toModel() + val roomCreateContent = roomCreateEvent?.getRoomCreateContentWithSender() return RoomPowerLevels(powerLevelsContent, roomCreateContent) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt index ad47b82428..b939490ae3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt @@ -31,11 +31,14 @@ import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent +import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.state.StateService import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.MimeTypes import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.session.content.FileUploader +import org.matrix.android.sdk.internal.session.room.powerlevels.getRoomPowerLevels +import org.matrix.android.sdk.internal.session.room.powerlevels.getRoomPowerLevelsLive internal class DefaultStateService @AssistedInject constructor( @Assisted private val roomId: String, @@ -65,6 +68,14 @@ internal class DefaultStateService @AssistedInject constructor( return stateEventDataSource.getStateEventsLive(roomId, eventTypes, stateKey) } + override fun getRoomPowerLevels(): RoomPowerLevels { + return stateEventDataSource.getRoomPowerLevels(roomId) + } + + override fun getRoomPowerLevelsLive(): LiveData { + return stateEventDataSource.getRoomPowerLevelsLive(roomId) + } + override suspend fun sendStateEvent( eventType: String, stateKey: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt index fd99bb7158..0fec0e4c17 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt @@ -23,12 +23,10 @@ import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.homeserver.RoomVersionStatus -import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent -import org.matrix.android.sdk.api.session.room.model.create.getRoomCreateContentWithSender -import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels import org.matrix.android.sdk.api.session.room.version.RoomVersionService import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesDataSource +import org.matrix.android.sdk.internal.session.room.powerlevels.getRoomPowerLevels import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource internal class DefaultRoomVersionService @AssistedInject constructor( @@ -72,16 +70,7 @@ internal class DefaultRoomVersionService @AssistedInject constructor( } override fun userMayUpgradeRoom(userId: String): Boolean { - val powerLevelsContent = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) - ?.content?.toModel() - - val roomCreateContent = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_CREATE, QueryStringValue.IsEmpty) - ?.getRoomCreateContentWithSender() - - val roomPowerLevels = RoomPowerLevels( - powerLevelsContent = powerLevelsContent, - roomCreateContent = roomCreateContent - ) + val roomPowerLevels = stateEventDataSource.getRoomPowerLevels(roomId) return roomPowerLevels.isUserAllowedToSend(userId, true, EventType.STATE_ROOM_TOMBSTONE) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index 72b72990e8..f92a062546 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -54,7 +54,6 @@ import im.vector.app.features.home.room.typing.TypingHelper import im.vector.app.features.location.live.StopLiveLocationShareUseCase import im.vector.app.features.location.live.tracking.LocationSharingServiceConnection import im.vector.app.features.notifications.NotificationDrawerManager -import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.raw.wellknown.CryptoConfig import im.vector.app.features.raw.wellknown.getOutboundSessionKeySharingStrategyOrDefault import im.vector.app.features.raw.wellknown.withElementWellKnown @@ -302,7 +301,7 @@ class TimelineViewModel @AssistedInject constructor( private fun observePowerLevel() { if (room == null) return - PowerLevelsFlowFactory(room).createFlow() + room.flow().liveRoomPowerLevels() .onEach { powerLevels -> val canInvite = powerLevels.isUserAbleToInvite(session.myUserId) val isAllowedToManageWidgets = session.widgetService().hasPermissionsToHandleWidgets(room.roomId) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt index 00bc1890e7..4e645f93ae 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt @@ -30,7 +30,6 @@ import im.vector.app.features.home.room.detail.ChatEffect import im.vector.app.features.home.room.detail.composer.rainbow.RainbowGenerator import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView import im.vector.app.features.home.room.detail.toMessageType -import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.voice.VoiceFailure @@ -179,7 +178,7 @@ class MessageComposerViewModel @AssistedInject constructor( private fun observePowerLevelAndEncryption(room: Room) { combine( - PowerLevelsFlowFactory(room).createFlow(), + room.flow().liveRoomPowerLevels(), room.flow().liveRoomSummary().unwrap() ) { pl, sum -> val canSendMessage = pl.isUserAllowedToSend(session.myUserId, false, EventType.MESSAGE) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 36f63f0b20..2280a26842 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -23,7 +23,6 @@ import im.vector.app.features.home.room.detail.timeline.format.NoticeEventFormat import im.vector.app.features.html.EventHtmlRenderer import im.vector.app.features.html.PillsPostProcessor import im.vector.app.features.html.VectorHtmlCompressor -import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.reactions.data.EmojiDataSource import im.vector.app.features.settings.VectorPreferences import im.vector.lib.strings.CommonStrings @@ -115,7 +114,7 @@ class MessageActionsViewModel @AssistedInject constructor( if (room == null) { return } - PowerLevelsFlowFactory(room).createFlow() + room.flow().liveRoomPowerLevels() .onEach { roomPowerLevels -> val canReact = roomPowerLevels.isUserAllowedToSend(session.myUserId, false, EventType.REACTION) val canRedact = roomPowerLevels.isUserAbleToRedact(session.myUserId) diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt index c000c2423a..9c8dd1dc8e 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt @@ -19,7 +19,6 @@ import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.utils.PermissionChecker import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider import im.vector.app.features.location.domain.usecase.CompareLocationsUseCase -import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.settings.VectorPreferences import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.distinctUntilChanged @@ -35,6 +34,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getUserOrDefault import org.matrix.android.sdk.api.util.toMatrixItem +import org.matrix.android.sdk.flow.flow import timber.log.Timber /** @@ -72,7 +72,7 @@ class LocationSharingViewModel @AssistedInject constructor( } private fun observePowerLevelsForLiveLocationSharing() { - PowerLevelsFlowFactory(room).createFlow() + room.flow().liveRoomPowerLevels() .distinctUntilChanged() .setOnEach { roomPowerLevels -> val canShareLiveLocation = EventType.STATE_ROOM_BEACON_INFO.values diff --git a/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt b/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt deleted file mode 100644 index fbbfac98dc..0000000000 --- a/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2020-2024 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.features.powerlevel - -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.flowOn -import org.matrix.android.sdk.api.query.QueryStringValue -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.room.Room -import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent -import org.matrix.android.sdk.api.session.room.model.create.getRoomCreateContentWithSender -import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels -import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.flow.mapOptional - -class PowerLevelsFlowFactory(private val room: Room) { - - fun createFlow(): Flow { - val flowRoom = room.flow() - val powerLevelsFlow = flowRoom - .liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) - .mapOptional { it.content.toModel() } - .flowOn(Dispatchers.Default) - - val roomCreateFlow = flowRoom - .liveStateEvent(EventType.STATE_ROOM_CREATE, QueryStringValue.IsEmpty) - .mapOptional { event -> - event.getRoomCreateContentWithSender() - } - .flowOn(Dispatchers.Default) - - return combine(powerLevelsFlow, roomCreateFlow) { powerLevelsContent, roomCreateContent -> - RoomPowerLevels( - powerLevelsContent = powerLevelsContent.getOrNull(), - roomCreateContent = roomCreateContent.getOrNull() - ) - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/powerlevel/Role.kt b/vector/src/main/java/im/vector/app/features/powerlevel/Role.kt index 6ac6449820..5330c3c4be 100644 --- a/vector/src/main/java/im/vector/app/features/powerlevel/Role.kt +++ b/vector/src/main/java/im/vector/app/features/powerlevel/Role.kt @@ -22,7 +22,7 @@ fun Role.isOwner() = this == Role.Creator || this == Role.SuperAdmin fun Room.membersByRoleFlow(): Flow>> { val roomMembersFlow = flow().liveRoomMembers(roomMemberQueryParams()) - val roomPowerLevelsFlow = PowerLevelsFlowFactory(this).createFlow() + val roomPowerLevelsFlow = flow().liveRoomPowerLevels() return combine(roomMembersFlow, roomPowerLevelsFlow) { roomMembers, roomPowerLevels -> roomMembers.groupBy { roomPowerLevels.getSuggestedRole(it.userId) } }.distinctUntilChanged() diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt index 14e286fd1d..47d4cf423e 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -23,7 +23,6 @@ import im.vector.app.core.resources.StringProvider import im.vector.app.features.createdirect.DirectRoomHelper import im.vector.app.features.displayname.getBestName import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider -import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.lib.strings.CommonStrings import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.combine @@ -361,7 +360,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor( private fun observeRoomSummaryAndPowerLevels(room: Room) { val roomSummaryLive = room.flow().liveRoomSummary().unwrap() - val powerLevelsFlow = PowerLevelsFlowFactory(room).createFlow() + val powerLevelsFlow = room.flow().liveRoomPowerLevels() powerLevelsFlow .onEach { roomPowerLevels -> val permissions = ActionPermissions( diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt index e2e70ee8b4..eae4d47de6 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt @@ -19,7 +19,6 @@ import im.vector.app.core.resources.StringProvider import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.plan.Interaction import im.vector.app.features.home.ShortcutCreator -import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.powerlevel.isLastAdminFlow import im.vector.app.features.session.coroutineScope import im.vector.lib.strings.CommonStrings @@ -121,7 +120,7 @@ class RoomProfileViewModel @AssistedInject constructor( } private fun observePowerLevels() { - val powerLevelsContentLive = PowerLevelsFlowFactory(room).createFlow() + val powerLevelsContentLive = room.flow().liveRoomPowerLevels() powerLevelsContentLive .onEach { roomPowerLevels -> val canUpdateRoomState = roomPowerLevels.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_ENCRYPTION) @@ -163,8 +162,7 @@ class RoomProfileViewModel @AssistedInject constructor( } private fun observePermissions() { - PowerLevelsFlowFactory(room) - .createFlow() + room.flow().liveRoomPowerLevels() .setOnEach { roomPowerLevels -> val permissions = RoomProfileViewState.ActionPermissions( canEnableEncryption = roomPowerLevels.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_ENCRYPTION) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index a2bd121cf9..2c1a112620 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -18,7 +18,6 @@ import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel -import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -124,8 +123,7 @@ class RoomAliasViewModel @AssistedInject constructor( } private fun observePowerLevel() { - PowerLevelsFlowFactory(room) - .createFlow() + room.flow().liveRoomPowerLevels() .onEach { roomPowerLevels -> val permissions = RoomAliasViewState.ActionPermissions( canChangeCanonicalAlias = roomPowerLevels.isUserAllowedToSend( diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt index 6c0bbcb0e5..4d373dfaa8 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt @@ -15,7 +15,6 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider -import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.lib.strings.CommonStrings import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -61,7 +60,7 @@ class RoomBannedMemberListViewModel @AssistedInject constructor( ) } - val powerLevelsFlow = PowerLevelsFlowFactory(room).createFlow() + val powerLevelsFlow = room.flow().liveRoomPowerLevels() powerLevelsFlow .setOnEach { roomPowerLevels -> copy(canUserBan = roomPowerLevels.isUserAbleToBan(session.myUserId)) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt index 2974e10649..65e02f63d4 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt @@ -16,7 +16,6 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel -import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged @@ -72,7 +71,7 @@ class RoomMemberListViewModel @AssistedInject constructor( memberships = Membership.activeMemberships() } - val powerLevelsFlow = PowerLevelsFlowFactory(room).createFlow() + val powerLevelsFlow = room.flow().liveRoomPowerLevels() combine( roomFlow.liveRoomMembers(roomMemberQueryParams), powerLevelsFlow, @@ -137,7 +136,7 @@ class RoomMemberListViewModel @AssistedInject constructor( } private fun observePowerLevel() { - PowerLevelsFlowFactory(room).createFlow() + room.flow().liveRoomPowerLevels() .onEach { roomPowerLevels -> val permissions = ActionPermissions( canInvite = roomPowerLevels.isUserAbleToInvite(session.myUserId), diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt index 3757a1ddd0..7f88ae88c7 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt @@ -16,7 +16,6 @@ import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel -import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -59,8 +58,7 @@ class RoomPermissionsViewModel @AssistedInject constructor( } private fun observePowerLevel() { - PowerLevelsFlowFactory(room) - .createFlow() + room.flow().liveRoomPowerLevels() .onEach { roomPowerLevels -> val permissions = RoomPermissionsViewState.ActionPermissions( canChangePowerLevels = roomPowerLevels.isUserAllowedToSend( diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt index 188068afb4..c603927d77 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -15,7 +15,6 @@ import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel -import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.settings.VectorPreferences import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.mapNotNull @@ -114,7 +113,7 @@ class RoomSettingsViewModel @AssistedInject constructor( ) } - val powerLevelsFlow = PowerLevelsFlowFactory(room).createFlow() + val powerLevelsFlow = room.flow().liveRoomPowerLevels() powerLevelsFlow .onEach { roomPowerLevels -> val permissions = RoomSettingsViewState.ActionPermissions( diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt index c8ac7af8b8..6744548872 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt @@ -20,7 +20,6 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel -import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.session.coroutineScope import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -71,8 +70,7 @@ class SpaceMenuViewModel @AssistedInject constructor( } }.launchIn(viewModelScope) - PowerLevelsFlowFactory(room) - .createFlow() + room.flow().liveRoomPowerLevels() .onEach { roomPowerLevels -> val canInvite = roomPowerLevels.isUserAbleToInvite(session.myUserId) diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt index 3627dd5469..ed73ace342 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt @@ -21,7 +21,6 @@ import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom import im.vector.app.features.analytics.plan.JoinedRoom -import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map @@ -95,7 +94,7 @@ class SpaceDirectoryViewModel @AssistedInject constructor( private fun observePermissions() { val room = session.getRoom(initialState.spaceId) ?: return - val powerLevelsFlow = PowerLevelsFlowFactory(room).createFlow() + val powerLevelsFlow = room.flow().liveRoomPowerLevels() powerLevelsFlow .onEach { roomPowerLevels -> diff --git a/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt index b4d58f61a0..88c3348ed7 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt @@ -16,13 +16,13 @@ import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel -import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoomSummary +import org.matrix.android.sdk.flow.flow class ShareSpaceViewModel @AssistedInject constructor( @Assisted private val initialState: ShareSpaceViewState, @@ -49,8 +49,7 @@ class ShareSpaceViewModel @AssistedInject constructor( private fun observePowerLevel() { val room = session.getRoom(initialState.spaceId) ?: return - PowerLevelsFlowFactory(room) - .createFlow() + room.flow().liveRoomPowerLevels() .onEach { roomPowerLevels -> setState { copy( diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt index f1df2cff2a..1c4c19b4f5 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt @@ -19,7 +19,6 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider -import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.widgets.permissions.WidgetPermissionsHelper import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map @@ -97,8 +96,7 @@ class WidgetViewModel @AssistedInject constructor( if (room == null) { return } - PowerLevelsFlowFactory(room) - .createFlow() + room.flow().liveRoomPowerLevels() .map { roomPowerLevels -> roomPowerLevels.isUserAllowedToSend(session.myUserId, true, null) } From 8d412cd6ec618219bdca07f165456800f9b7a193 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 5 Aug 2025 17:26:28 +0200 Subject: [PATCH 41/46] Fix RoomPowerLevels with nullable event --- .../session/room/powerlevels/RoomPowerLevels.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/powerlevels/RoomPowerLevels.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/powerlevels/RoomPowerLevels.kt index 9243672f1a..a72a0951f2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/powerlevels/RoomPowerLevels.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/powerlevels/RoomPowerLevels.kt @@ -32,7 +32,10 @@ import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource internal fun StateEventDataSource.getRoomPowerLevels(roomId: String): RoomPowerLevels { val powerLevelsEvent = getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) val roomCreateEvent = getStateEvent(roomId, EventType.STATE_ROOM_CREATE, QueryStringValue.IsEmpty) - return createRoomPowerLevels(powerLevelsEvent = powerLevelsEvent, roomCreateEvent = roomCreateEvent) + return createRoomPowerLevels( + powerLevelsEvent = powerLevelsEvent, + roomCreateEvent = roomCreateEvent + ) } internal fun StateEventDataSource.getRoomPowerLevelsLive(roomId: String): LiveData { @@ -42,7 +45,10 @@ internal fun StateEventDataSource.getRoomPowerLevelsLive(roomId: String): LiveDa fun emitIfReady(powerLevelEvent: Optional?, roomCreateEvent: Optional?) { if (powerLevelEvent != null && roomCreateEvent != null) { - val roomPowerLevels = createRoomPowerLevels(powerLevelEvent.get(), roomCreateEvent.get()) + val roomPowerLevels = createRoomPowerLevels( + powerLevelsEvent = powerLevelEvent.getOrNull(), + roomCreateEvent = roomCreateEvent.getOrNull() + ) resultLiveData.postValue(roomPowerLevels) } } From 3b0b2321515188ae1ff1a65c3684fd3de93f9c1e Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 6 Aug 2025 12:10:41 +0200 Subject: [PATCH 42/46] fix (window insets) : first upgrade some dependencies --- dependencies.gradle | 4 ++-- dependencies_groups.gradle | 1 + vector-app/src/main/java/im/vector/app/VectorApplication.kt | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dependencies.gradle b/dependencies.gradle index 0fd445b543..d3e839a118 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -27,7 +27,7 @@ def bigImageViewer = "1.8.1" def jjwt = "0.11.5" def vanniktechEmoji = "0.16.0" def sentry = "6.18.1" -def fragment = "1.8.1" +def fragment = "1.8.6" // Testing def mockk = "1.13.11" def espresso = "3.6.1" @@ -50,7 +50,7 @@ ext.libs = [ 'activity' : "androidx.activity:activity-ktx:1.9.0", 'appCompat' : "androidx.appcompat:appcompat:1.7.0", 'biometric' : "androidx.biometric:biometric:1.1.0", - 'core' : "androidx.core:core-ktx:1.10.1", + 'core' : "androidx.core:core-ktx:1.16.0", 'recyclerview' : "androidx.recyclerview:recyclerview:1.3.0", 'exifinterface' : "androidx.exifinterface:exifinterface:1.3.6", 'fragmentKtx' : "androidx.fragment:fragment-ktx:$fragment", diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle index 3024963bf8..282fa353e4 100644 --- a/dependencies_groups.gradle +++ b/dependencies_groups.gradle @@ -213,6 +213,7 @@ ext.groups = [ 'org.jitsi', 'org.json', 'org.jsoup', + 'org.jspecify', 'org.junit', 'org.junit.jupiter', 'org.junit.platform', diff --git a/vector-app/src/main/java/im/vector/app/VectorApplication.kt b/vector-app/src/main/java/im/vector/app/VectorApplication.kt index 50bced59ed..84b87b1404 100644 --- a/vector-app/src/main/java/im/vector/app/VectorApplication.kt +++ b/vector-app/src/main/java/im/vector/app/VectorApplication.kt @@ -161,6 +161,7 @@ class VectorApplication : "Noto Color Emoji Compat", R.array.com_google_android_gms_fonts_certs ) + @Suppress("DEPRECATION") FontsContractCompat.requestFont(this, fontRequest, emojiCompatFontProvider, getFontThreadHandler()) vectorLocale.init() ThemeUtils.init(this) From f3b631be6b3a4657d432168a73391ad475703ecf Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 6 Aug 2025 12:11:19 +0200 Subject: [PATCH 43/46] fix (window insets) : makes sure insets are consumed from BaseActivity --- .../java/im/vector/app/core/platform/VectorBaseActivity.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt index a6b9d77930..bf621b60a3 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt @@ -27,6 +27,7 @@ import androidx.core.app.MultiWindowModeChangedInfo import androidx.core.util.Consumer import androidx.core.view.MenuProvider import androidx.core.view.ViewCompat +import androidx.core.view.ViewGroupCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.isVisible import androidx.core.view.updatePadding @@ -210,6 +211,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver ThemeUtils.setActivityTheme(this, getOtherThemes()) viewModelFactory = activityEntryPoint.viewModelFactory() enableEdgeToEdge() + ViewGroupCompat.installCompatInsetsDispatch(window.decorView) super.onCreate(savedInstanceState) addOnMultiWindowModeChangedListener(onMultiWindowModeChangedListener) setupMenu() @@ -416,6 +418,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver // Just log that a change occurred. Timber.w("MDM data has been updated") } + ViewCompat.setOnApplyWindowInsetsListener(rootView) { v, insets -> val systemBars = insets.getInsets( WindowInsetsCompat.Type.systemBars() or @@ -428,7 +431,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver systemBars.right, systemBars.bottom, ) - insets + WindowInsetsCompat.CONSUMED } } From 340affe85a3114e452033abf3a4e25fd8b9caa5b Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 6 Aug 2025 12:18:13 +0200 Subject: [PATCH 44/46] fix (window insets) : makes sure insets are consumed from drawer fragments --- .../app/features/home/HomeDrawerFragment.kt | 16 ++++++++++++++++ .../home/room/breadcrumbs/BreadcrumbsFragment.kt | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt index 4a3d5d2583..0ead96292a 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt @@ -13,7 +13,9 @@ import android.view.View import android.view.ViewGroup import androidx.core.app.ActivityOptionsCompat import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat import androidx.core.view.isVisible +import androidx.core.view.updatePadding import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.observeK @@ -109,6 +111,20 @@ class HomeDrawerFragment : } } + ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets -> + val systemBars = insets.getInsets( + WindowInsetsCompat.Type.systemBars() or + WindowInsetsCompat.Type.displayCutout() + ) + v.updatePadding( + systemBars.left, + systemBars.top, + systemBars.right, + systemBars.bottom, + ) + WindowInsetsCompat.CONSUMED + } + // Debug menu views.homeDrawerHeaderDebugView.debouncedClicks { sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer) diff --git a/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsFragment.kt index ccad10150a..6f32572420 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsFragment.kt @@ -11,6 +11,9 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updatePadding import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import dagger.hilt.android.AndroidEntryPoint @@ -38,6 +41,19 @@ class BreadcrumbsFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + ViewCompat.setOnApplyWindowInsetsListener(views.breadcrumbsRecyclerView) { v, insets -> + val systemBars = insets.getInsets( + WindowInsetsCompat.Type.systemBars() or + WindowInsetsCompat.Type.displayCutout() + ) + v.updatePadding( + systemBars.left, + systemBars.top, + systemBars.right, + systemBars.bottom, + ) + WindowInsetsCompat.CONSUMED + } setupRecyclerView() sharedActionViewModel = activityViewModelProvider.get(RoomDetailSharedActionViewModel::class.java) } From d9b83dd7524cce173259d26d880a2cd0e59daf76 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 6 Aug 2025 15:17:22 +0200 Subject: [PATCH 45/46] Changelog for version 1.6.44 --- CHANGES.md | 11 +++++++++++ changelog.d/9058.misc | 1 - 2 files changed, 11 insertions(+), 1 deletion(-) delete mode 100644 changelog.d/9058.misc diff --git a/CHANGES.md b/CHANGES.md index 9547bc7652..d3cf064732 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,14 @@ +Changes in Element v1.6.44 (2025-08-06) +======================================= + +Other changes +------------- + - Hide the "Manually Verify by Text" option behind devtool flag. ([#9058](https://github.com/element-hq/element-android/issues/9058)) + - Change targetSdk to 35. ([#9051](https://github.com/element-hq/element-android/issues/9051)) + - Support room v12. ([#9065](https://github.com/element-hq/element-android/issues/9065)) + - Fix window insets. ([#9067](https://github.com/element-hq/element-android/issues/9067)) + + Changes in Element v1.6.42 (2025-06-10) ======================================= diff --git a/changelog.d/9058.misc b/changelog.d/9058.misc deleted file mode 100644 index 47ae9f75fe..0000000000 --- a/changelog.d/9058.misc +++ /dev/null @@ -1 +0,0 @@ - Hide the "Manually Verify by Text" option behind devtool flag. From 3ad7f565e1a643713bd4c6b9c3f1f60c7844b573 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 6 Aug 2025 15:18:26 +0200 Subject: [PATCH 46/46] Adding fastlane file for version 1.6.44 --- fastlane/metadata/android/en-US/changelogs/40106440.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/en-US/changelogs/40106440.txt diff --git a/fastlane/metadata/android/en-US/changelogs/40106440.txt b/fastlane/metadata/android/en-US/changelogs/40106440.txt new file mode 100644 index 0000000000..b09bc07aa2 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40106440.txt @@ -0,0 +1,2 @@ +Main changes in this version: support room v12. +Full changelog: https://github.com/element-hq/element-android/releases \ No newline at end of file