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..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:
@@ -15,7 +11,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:
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/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.
[
](https://play.google.com/store/apps/details?id=im.vector.app)
[
](https://f-droid.org/app/im.vector.app)
diff --git a/dependencies.gradle b/dependencies.gradle
index 97c4d85a5a..1f922a4dcc 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -1,13 +1,13 @@
ext.versions = [
'minSdk' : 21,
- 'compileSdk' : 34,
- 'targetSdk' : 34,
+ 'compileSdk' : 35,
+ 'targetSdk' : 35,
'sourceCompat' : JavaVersion.VERSION_21,
'targetCompat' : JavaVersion.VERSION_21,
'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"
@@ -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"
@@ -51,7 +51,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 b53b1fc5bb..69caa9d91c 100644
--- a/dependencies_groups.gradle
+++ b/dependencies_groups.gradle
@@ -214,6 +214,7 @@ ext.groups = [
'org.jitsi',
'org.json',
'org.jsoup',
+ 'org.jspecify',
'org.junit',
'org.junit.jupiter',
'org.junit.platform',
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
diff --git a/gradle.properties b/gradle.properties
index 5c85c38561..a6964bdf57 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -46,4 +46,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
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index e6441136f3..1b33c55baa 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
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..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
+#
##############################################################################
#
@@ -55,7 +57,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 +85,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 -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
@@ -114,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.
@@ -133,10 +133,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 +147,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 +155,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,16 +200,20 @@ 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, 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" \
-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 6689b85bee..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 ##########################################################################
@@ -43,11 +45,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,22 +59,22 @@ 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
: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
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/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index 803510ed76..f1922645ac 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
@@ -685,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
@@ -2383,6 +2385,7 @@
Invites
Users
+ Owner in %1$s
Admin in %1$s
Moderator in %1$s
Default in %1$s
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 e65f4ede3c..d92525d725 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
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/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/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..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.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.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
import org.matrix.android.sdk.common.SessionTestParams
@@ -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/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)
}
/**
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..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
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.room
import org.matrix.android.sdk.api.query.QueryStateEventValue
import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
/**
@@ -34,3 +35,10 @@ 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 {
+ return stateService().getRoomPowerLevels()
+}
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..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
@@ -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.
@@ -34,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,
/**
@@ -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,20 @@ 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..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
@@ -17,26 +17,21 @@
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)
-
- override fun compareTo(other: Role): Int {
- return value.compareTo(other.value)
- }
+enum class Role {
+ Creator,
+ SuperAdmin,
+ Admin,
+ Moderator,
+ User;
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 58%
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..0d66136730 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)
}
/**
@@ -49,10 +59,9 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
* @param userId the user id
* @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())
+ fun getSuggestedRole(userId: String): Role {
+ 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..134e1505af
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/UserPowerLevel.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ *
+ */
+
+package org.matrix.android.sdk.api.session.room.powerlevels
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+sealed interface UserPowerLevel : Comparable, Parcelable {
+ 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/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/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())
}
}
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.
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..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
@@ -15,24 +15,20 @@
*/
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.getStateEvent
-import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
+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 javax.inject.Inject
internal class DefaultConditionResolver @Inject constructor(
private val roomGetter: RoomGetter,
- @UserId private val userId: String
+ @UserId private val userId: String,
) : ConditionResolver {
override fun resolveEventMatchCondition(
@@ -55,13 +51,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 f626049e01..8abb71d1ab 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,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.PowerLevelsHelper
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 +59,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 +214,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 +378,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..a72a0951f2
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/powerlevels/RoomPowerLevels.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ *
+ */
+
+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 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(
+ powerLevelsEvent = powerLevelEvent.getOrNull(),
+ roomCreateEvent = roomCreateEvent.getOrNull()
+ )
+ 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/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
index 57056332a1..eb711fb58b 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
@@ -36,7 +36,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
@@ -391,13 +392,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..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,11 +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.powerlevels.PowerLevelsHelper
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(
@@ -71,11 +70,8 @@ internal class DefaultRoomVersionService @AssistedInject constructor(
}
override fun userMayUpgradeRoom(userId: String): Boolean {
- val powerLevelsHelper = 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 roomPowerLevels = stateEventDataSource.getRoomPowerLevels(roomId)
+ 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..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,7 @@ 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.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 +46,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 +84,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 +254,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..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,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.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 +372,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.Default.value
- powerLevelsContent.usersDefault shouldBeEqualTo Role.Default.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-app/build.gradle b/vector-app/build.gradle
index b3528c2d0e..eb53e406a2 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
ext.scVersion = 87
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-app/src/main/java/im/vector/app/VectorApplication.kt b/vector-app/src/main/java/im/vector/app/VectorApplication.kt
index b47ac03bd2..2fab7c825a 100644
--- a/vector-app/src/main/java/im/vector/app/VectorApplication.kt
+++ b/vector-app/src/main/java/im/vector/app/VectorApplication.kt
@@ -169,6 +169,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)
diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
index a20ad6b5b2..28b6757c32 100644
--- a/vector/src/main/AndroidManifest.xml
+++ b/vector/src/main/AndroidManifest.xml
@@ -169,8 +169,7 @@
+ android:parentActivityName=".features.home.HomeActivity">
@@ -374,8 +373,8 @@
+ android:exported="false"
+ android:foregroundServiceType="phoneCall">
@@ -392,7 +391,8 @@
+ android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+ tools:targetApi="M">
@@ -418,8 +418,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" />
diff --git a/vector/src/main/java/im/vector/app/SpaceStateHandlerImpl.kt b/vector/src/main/java/im/vector/app/SpaceStateHandlerImpl.kt
index 1ac595b5e9..d00d39b298 100644
--- a/vector/src/main/java/im/vector/app/SpaceStateHandlerImpl.kt
+++ b/vector/src/main/java/im/vector/app/SpaceStateHandlerImpl.kt
@@ -172,7 +172,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/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 58337af15b..125af577b2 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,25 +10,27 @@ 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.activity.enableEdgeToEdge
import androidx.annotation.CallSuper
import androidx.annotation.MainThread
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.ViewCompat
+import androidx.core.view.ViewGroupCompat
+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
@@ -210,6 +212,8 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver
val activityEntryPoint = EntryPointAccessors.fromActivity(this, ActivityEntryPoint::class.java)
ThemeUtils.setActivityTheme(this, getOtherThemes())
viewModelFactory = activityEntryPoint.viewModelFactory()
+ enableEdgeToEdge()
+ ViewGroupCompat.installCompatInsetsDispatch(window.decorView)
super.onCreate(savedInstanceState)
addOnMultiWindowModeChangedListener(onMultiWindowModeChangedListener)
setupMenu()
@@ -249,7 +253,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
}
}
@@ -336,7 +342,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() {
@@ -413,6 +420,21 @@ 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,
+ )
+ WindowInsetsCompat.CONSUMED
+ }
}
private val postResumeScheduledActions = mutableListOf<() -> Unit>()
@@ -446,14 +468,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,30 +477,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
- 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
- window.statusBarColor = ContextCompat.getColor(this, im.vector.lib.ui.styles.R.color.half_transparent_status_bar)
- // New API instead of FLAG_TRANSLUCENT_NAVIGATION
- 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 -> {
@@ -588,8 +578,6 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver
abstract fun getBinding(): VB
- open fun displayInFullscreen() = false
-
open fun doBeforeSetContentView() = Unit
open fun initUiAndData() = Unit
@@ -628,6 +616,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/core/services/BluetoothHeadsetReceiver.kt b/vector/src/main/java/im/vector/app/core/services/BluetoothHeadsetReceiver.kt
index dd04e5664e..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
@@ -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/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/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
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..1323edf188
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/utils/PermissionChecker.kt
@@ -0,0 +1,38 @@
+/*
+ * 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 dagger.Binds
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Inject
+
+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,
+) : PermissionChecker {
+ override 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/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/attachments/preview/AttachmentsPreviewFragment.kt b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewFragment.kt
index 8de5262da0..2052433d77 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
@@ -164,6 +164,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..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
@@ -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)
@@ -185,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/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))
}
}
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/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/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/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/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
index 6fb5821a21..cac45f1be9 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
@@ -198,6 +198,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/HomeDrawerFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt
index a2f0aa36b7..66f499747d 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
@@ -110,6 +112,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)
}
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..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
@@ -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
@@ -82,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()
@@ -93,7 +95,7 @@ class RoomDetailActivity :
super.onCreate(savedInstanceState)
// 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/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
index e10dab291f..d7dfb78300 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
@@ -18,7 +18,7 @@ import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
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.model.localecho.RoomLocalEcho
-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.sender.SenderInfo
import org.matrix.android.sdk.api.session.sync.SyncRequestState
import org.matrix.android.sdk.api.session.sync.SyncState
@@ -48,7 +48,7 @@ data class RoomDetailViewState(
val myRoomMember: Async = Uninitialized,
val asyncInviter: Async = Uninitialized,
val asyncRoomSummary: Async = Uninitialized,
- val powerLevelsHelper: PowerLevelsHelper? = null,
+ val powerLevels: RoomPowerLevels? = null,
val openAtFirstUnread: Boolean? = null,
val openAnonymously: Boolean = false,
val activeRoomWidgets: Async> = Uninitialized,
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 dcffdd423a..a11801f31d 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
@@ -34,13 +34,10 @@ 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.isGone
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
-import androidx.core.view.postDelayed
-import androidx.core.view.updatePadding
import androidx.fragment.app.setFragmentResultListener
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.withResumed
@@ -449,13 +446,6 @@ class TimelineFragment :
is RoomDetailViewEvents.RevokeFilePermission -> revokeFilePermission(it)
}
}
-
- ViewCompat.setOnApplyWindowInsetsListener(views.coordinatorLayout) { _, insets ->
- val imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime() or WindowInsetsCompat.Type.systemBars())
- views.appBarLayout.updatePadding(top = imeInsets.top)
- views.voiceMessageRecorderContainer.updatePadding(bottom = imeInsets.bottom)
- insets
- }
}
private fun setupBackPressHandling() {
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 40918f9838..bbb7f33f1d 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
@@ -66,7 +66,6 @@ import im.vector.app.features.location.live.StopLiveLocationShareUseCase
import im.vector.app.features.location.live.tracking.LocationSharingServiceConnection
import im.vector.app.features.media.ImageContentRenderer
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
@@ -123,7 +122,7 @@ 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.powerlevels.RoomPowerLevels
import org.matrix.android.sdk.api.session.room.read.ReadService
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
import org.matrix.android.sdk.api.session.room.timeline.Timeline
@@ -369,19 +368,18 @@ class TimelineViewModel @AssistedInject constructor(
private fun observePowerLevel() {
if (room == null) return
- PowerLevelsFlowFactory(room).createFlow()
- .onEach {
- val powerLevelsHelper = PowerLevelsHelper(it)
- val canInvite = powerLevelsHelper.isUserAbleToInvite(session.myUserId)
+ room.flow().liveRoomPowerLevels()
+ .onEach { powerLevels ->
+ val canInvite = powerLevels.isUserAbleToInvite(session.myUserId)
val isAllowedToManageWidgets = session.widgetService().hasPermissionsToHandleWidgets(room.roomId)
- val isAllowedToStartWebRTCCall = powerLevelsHelper.isUserAllowedToSend(session.myUserId, false, EventType.CALL_INVITE)
- val isAllowedToSetupEncryption = powerLevelsHelper.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,
isAllowedToManageWidgets = isAllowedToManageWidgets,
isAllowedToStartWebRTCCall = isAllowedToStartWebRTCCall,
- powerLevelsHelper = powerLevelsHelper,
+ powerLevels = powerLevels,
isAllowedToSetupEncryption = isAllowedToSetupEncryption
)
}
@@ -1653,7 +1651,7 @@ class TimelineViewModel @AssistedInject constructor(
return tryOrNull { operation() }
}
- override fun getPowerLevelsHelper(): PowerLevelsHelper? = withState(this) { state ->
- state.powerLevelsHelper
+ override fun getPowerLevels(): RoomPowerLevels? = withState(this) { state ->
+ state.powerLevels
}
}
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 40363eaa6c..d2ad2b216b 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
@@ -69,7 +68,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
@@ -186,10 +184,10 @@ class MessageComposerViewModel @AssistedInject constructor(
private fun observePowerLevelAndEncryption(room: Room) {
combine(
- PowerLevelsFlowFactory(room).createFlow(),
+ room.flow().liveRoomPowerLevels(),
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/composer/PlainTextComposerLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt
index ea4e0ea70f..1c45f6b6c0 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt
@@ -186,7 +186,7 @@ class PlainTextComposerLayout @JvmOverloads constructor(
MatrixItemColorProvider.UserInRoomInformation(
it.isDm(),
it.isPublic(),
- it.powerLevelsHelper?.getUserPowerLevelValue(matrixItem.id)
+ it.powerLevels?.getUserPowerLevel(matrixItem.id)
)
}
}
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/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
index 84a1dedf2f..9bcadf1eee 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
@@ -16,7 +16,6 @@ import androidx.recyclerview.widget.RecyclerView
import com.airbnb.epoxy.EpoxyController
import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.VisibilityState
-import de.spiritcroc.matrixsdk.util.DbgUtil
import im.vector.app.core.date.DateFormatKind
import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.core.epoxy.LoadingItem_
@@ -70,7 +69,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
import org.matrix.android.sdk.api.session.room.model.message.MessageImageInfoContent
import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
-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.read.ReadService
import org.matrix.android.sdk.api.session.room.timeline.Timeline
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
@@ -105,7 +104,7 @@ class TimelineEventController @Inject constructor(
val highlightedEventId: String? = null,
val jitsiState: JitsiState = JitsiState(),
val roomSummary: RoomSummary? = null,
- val powerLevelsHelper: PowerLevelsHelper? = null,
+ val powerLevels: RoomPowerLevels? = null,
val rootThreadEventId: String? = null,
) {
@@ -114,7 +113,7 @@ class TimelineEventController @Inject constructor(
highlightedEventId = state.highlightedEventId,
jitsiState = state.jitsiState,
roomSummary = state.asyncRoomSummary(),
- powerLevelsHelper = state.powerLevelsHelper,
+ powerLevels = state.powerLevels,
rootThreadEventId = state.rootThreadEventId,
)
@@ -318,7 +317,7 @@ class TimelineEventController @Inject constructor(
return
}
// Full list rebuild if power levels changed and username colors depend on power levels
- if (partialState.powerLevelsHelper != newPartialState.powerLevelsHelper) {
+ if (partialState.powerLevels != newPartialState.powerLevels) {
val coloringMode = vectorPreferences.userColorMode(newPartialState.roomSummary?.isDirect ?: false, newPartialState.roomSummary?.isPublic ?: false)
if (coloringMode == MatrixItemColorProvider.USER_COLORING_FROM_PL) {
partialState = newPartialState
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..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
@@ -49,7 +48,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.PowerLevelsHelper
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
@@ -116,12 +114,11 @@ class MessageActionsViewModel @AssistedInject constructor(
if (room == null) {
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)
+ room.flow().liveRoomPowerLevels()
+ .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 afa57658d3..7c8f0c8a33 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
@@ -25,16 +25,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.getStateEvent
-import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
+import org.matrix.android.sdk.api.session.room.getRoomPowerLevels
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.timeline.TimelineEvent
import javax.inject.Inject
@@ -306,9 +303,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,
@@ -324,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 ba41854b4e..7a57cdbfb7 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
@@ -42,7 +42,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
@@ -123,8 +123,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).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/home/room/detail/timeline/helper/MatrixItemColorProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MatrixItemColorProvider.kt
index 1a49421a57..70e39daaab 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MatrixItemColorProvider.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MatrixItemColorProvider.kt
@@ -15,6 +15,7 @@ import androidx.annotation.VisibleForTesting
import im.vector.app.core.resources.ColorProvider
import im.vector.app.features.settings.VectorPreferences
import im.vector.lib.ui.styles.R
+import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel
import org.matrix.android.sdk.api.util.MatrixItem
import timber.log.Timber
import javax.inject.Inject
@@ -40,11 +41,11 @@ class MatrixItemColorProvider @Inject constructor(
colorProvider.getColorFromAttribute(
when {
userInRoomInformation?.userPowerLevel == null -> com.google.android.material.R.attr.colorAccent // This is also used for avatars in the room overview
- userInRoomInformation.userPowerLevel >= 100 -> R.attr.user_color_pl_100
- userInRoomInformation.userPowerLevel >= 95 -> R.attr.user_color_pl_95
- userInRoomInformation.userPowerLevel >= 51 -> R.attr.user_color_pl_51
- userInRoomInformation.userPowerLevel >= 50 -> R.attr.user_color_pl_50
- userInRoomInformation.userPowerLevel >= 1 -> R.attr.user_color_pl_1
+ userInRoomInformation.userPowerLevel >= UserPowerLevel.Value(100) -> R.attr.user_color_pl_100
+ userInRoomInformation.userPowerLevel >= UserPowerLevel.Value(95) -> R.attr.user_color_pl_95
+ userInRoomInformation.userPowerLevel >= UserPowerLevel.Value(51) -> R.attr.user_color_pl_51
+ userInRoomInformation.userPowerLevel >= UserPowerLevel.Value(50) -> R.attr.user_color_pl_50
+ userInRoomInformation.userPowerLevel >= UserPowerLevel.Value(1) -> R.attr.user_color_pl_1
else -> R.attr.user_color_pl_0
}
)
@@ -171,5 +172,5 @@ class MatrixItemColorProvider @Inject constructor(
const val USER_COLORING_DEFAULT = USER_COLORING_UNIFORM
}
- data class UserInRoomInformation(val isDm: Boolean? = null, val isPublicRoom: Boolean? = null, val userPowerLevel: Int? = null)
+ data class UserInRoomInformation(val isDm: Boolean? = null, val isPublicRoom: Boolean? = null, val userPowerLevel: UserPowerLevel? = null)
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
index 40d67a0870..87d8343849 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
@@ -127,7 +127,7 @@ class MessageInformationDataFactory @Inject constructor(
}
// Sender power level
- val senderPowerLevel = params.partialState.powerLevelsHelper?.getUserPowerLevelValue(event.senderInfo.userId)
+ val senderPowerLevel = params.partialState.powerLevels?.getUserPowerLevel(event.senderInfo.userId)
val messageLayout = messageLayoutFactory.create(params)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
index 86c4f14f85..d89905bb19 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
@@ -11,6 +11,7 @@ import android.os.Parcelable
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.crypto.verification.VerificationState
+import org.matrix.android.sdk.api.session.room.powerlevels.UserPowerLevel
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.api.util.MatrixItem
@@ -34,7 +35,7 @@ data class MessageInformationData(
val isDirect: Boolean,
val isPublic: Boolean,
val isReply: Boolean,
- val senderPowerLevel: Int?,
+ val senderPowerLevel: UserPowerLevel?,
val dmChatPartnerId: String?,
val e2eDecoration: E2EDecoration = E2EDecoration.NONE,
val sendStateDecoration: SendStateDecoration = SendStateDecoration.NONE,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reply/ReplyPreviewRetriever.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reply/ReplyPreviewRetriever.kt
index 8def332c3e..9e382c18f3 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reply/ReplyPreviewRetriever.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reply/ReplyPreviewRetriever.kt
@@ -39,7 +39,7 @@ import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
import org.matrix.android.sdk.api.session.events.model.getRelationContent
import org.matrix.android.sdk.api.session.getRoom
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.api.session.room.sender.SenderInfo
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.getLatestEventId
@@ -253,7 +253,7 @@ class ReplyPreviewRetriever(
fun onStateUpdated(state: PreviewReplyUiState)
}
interface PowerLevelProvider {
- fun getPowerLevelsHelper(): PowerLevelsHelper?
+ fun getPowerLevels(): RoomPowerLevels?
}
fun getMemberNameColor(event: TimelineEvent, roomInformationData: MessageInformationData): Int {
@@ -263,7 +263,7 @@ class ReplyPreviewRetriever(
MatrixItemColorProvider.UserInRoomInformation(
roomInformationData.isDirect,
roomInformationData.isPublic,
- powerLevelProvider.getPowerLevelsHelper()?.getUserPowerLevelValue(event.senderInfo.userId)
+ powerLevelProvider.getPowerLevels()?.getUserPowerLevel(event.senderInfo.userId)
)
)
}
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/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt
index bdb6a4a5ee..f2268f8184 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
@@ -28,7 +28,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 de.spiritcroc.matrixsdk.util.DbgUtil
import de.spiritcroc.matrixsdk.util.Dimber
@@ -51,6 +50,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.app.features.settings.VectorPreferences
import im.vector.lib.strings.CommonStrings
import kotlinx.coroutines.flow.collect
@@ -533,7 +533,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))
@@ -574,26 +574,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 82e584257b..28742b2a48 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
@@ -28,6 +28,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
@@ -44,7 +46,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
@@ -166,8 +167,8 @@ 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 *****************************************************************************
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 20c6150da2..1b6764d842 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))
@@ -198,26 +197,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 f102f7a4ce..c2197cb80b 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
@@ -53,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
@@ -332,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 handleSetMarkedUnread(action: HomeRoomListAction.SetMarkedUnread) {
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/lifecycle/VectorActivityLifecycleCallbacks.kt b/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt
index 7a699e1b38..86c39b41e4 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
@@ -147,9 +155,7 @@ 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 }.also {
- if (it) {
- Timber.tag("SC_NP_DBG").e("Found potential malicious activity: ${activity.className} (${activity.packageName}) vs ${activitiesInfo.joinToString { it.name }}")
+ private fun isPotentialMaliciousActivity(activity: ComponentName): Boolean = activitiesInfo.orEmpty().none { it.name == activity.className }.also {
+ Timber.tag("SC_NP_DBG").e("Found potential malicious activity: ${activity.className} (${activity.packageName}) vs ${activitiesInfo?.joinToString { it.name }}")
}
- }
}
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 61b2e368cd..eb91345f71 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(initialState), LocationTracker.Callback {
private val room = session.getRoom(initialState.roomId)!!
@@ -70,13 +72,12 @@ class LocationSharingViewModel @AssistedInject constructor(
}
private fun observePowerLevelsForLiveLocationSharing() {
- PowerLevelsFlowFactory(room).createFlow()
+ room.flow().liveRoomPowerLevels()
.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)
@@ -88,7 +89,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/LiveLocationMapViewActivity.kt b/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewActivity.kt
index 21e8f2ba02..2564d5a395 100644
--- a/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationMapViewActivity.kt
@@ -10,6 +10,7 @@ package im.vector.app.features.location.live.map
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
@@ -29,6 +30,9 @@ class LiveLocationMapViewActivity : VectorBaseActivity(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 315a555703..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
@@ -7,14 +7,18 @@
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
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
@@ -52,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
@@ -74,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
@@ -95,7 +108,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 +163,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/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()
}
}
diff --git a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
index cbf13acc98..2419ba7bfc 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
@@ -76,6 +76,9 @@ open class LoginActivity : 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/login/PromptSimplifiedModeActivity.kt b/vector/src/main/java/im/vector/app/features/login/PromptSimplifiedModeActivity.kt
index be1e4f45a5..54cf40afcd 100644
--- a/vector/src/main/java/im/vector/app/features/login/PromptSimplifiedModeActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/login/PromptSimplifiedModeActivity.kt
@@ -19,6 +19,7 @@ package im.vector.app.features.login
import android.annotation.SuppressLint
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
@@ -35,6 +36,9 @@ open class PromptSimplifiedModeActivity : 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/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()
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 259a3a6dee..9db264446b 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
@@ -152,55 +155,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? {
@@ -998,7 +1005,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) {
@@ -1026,30 +1037,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_status_bar_sc)
- .setLargeIcon(getBitmap(context, R.drawable.element_logo_sc))
- .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_status_bar_sc)
+ .setLargeIcon(getBitmap(context, R.drawable.element_logo_sc))
+ .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? {
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/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
}
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 f065334ae7..0000000000
--- a/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt
+++ /dev/null
@@ -1,31 +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.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.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()
- .liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty)
- .mapOptional { it.content.toModel() }
- .flowOn(Dispatchers.Default)
- .unwrap()
- }
-}
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..5330c3c4be
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/powerlevel/Role.kt
@@ -0,0 +1,43 @@
+/*
+ * 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 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