mirror of
https://github.com/vector-im/riotX-android
synced 2025-10-05 15:52:47 +02:00
Merge branch 'release/1.6.44' into main
This commit is contained in:
2
.github/workflows/post-pr.yml
vendored
2
.github/workflows/post-pr.yml
vendored
@@ -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
|
||||
|
8
.github/workflows/tests.yml
vendored
8
.github/workflows/tests.yml
vendored
@@ -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:
|
||||
|
11
CHANGES.md
11
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)
|
||||
=======================================
|
||||
|
||||
|
@@ -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.
|
||||
|
||||
[<img src="resources/img/google-play-badge.png" alt="Get it on Google Play" height="60">](https://play.google.com/store/apps/details?id=im.vector.app)
|
||||
[<img src="resources/img/f-droid-badge.png" alt="Get it on F-Droid" height="60">](https://f-droid.org/app/im.vector.app)
|
||||
|
@@ -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"
|
||||
@@ -50,7 +50,7 @@ ext.libs = [
|
||||
'activity' : "androidx.activity:activity-ktx:1.9.0",
|
||||
'appCompat' : "androidx.appcompat:appcompat:1.7.0",
|
||||
'biometric' : "androidx.biometric:biometric:1.1.0",
|
||||
'core' : "androidx.core:core-ktx:1.10.1",
|
||||
'core' : "androidx.core:core-ktx:1.16.0",
|
||||
'recyclerview' : "androidx.recyclerview:recyclerview:1.3.0",
|
||||
'exifinterface' : "androidx.exifinterface:exifinterface:1.3.6",
|
||||
'fragmentKtx' : "androidx.fragment:fragment-ktx:$fragment",
|
||||
|
@@ -213,6 +213,7 @@ ext.groups = [
|
||||
'org.jitsi',
|
||||
'org.json',
|
||||
'org.jsoup',
|
||||
'org.jspecify',
|
||||
'org.junit',
|
||||
'org.junit.jupiter',
|
||||
'org.junit.platform',
|
||||
|
2
fastlane/metadata/android/en-US/changelogs/40106440.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40106440.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Main changes in this version: support room v12.
|
||||
Full changelog: https://github.com/element-hq/element-android/releases
|
@@ -42,4 +42,4 @@ signing.element.nightly.keyPassword=Secret
|
||||
|
||||
# Customise the Lint version to use a more recent version than the one bundled with AGP
|
||||
# https://googlesamples.github.io/android-custom-lint-rules/usage/newer-lint.md.html
|
||||
android.experimental.lint.version=8.6.0-alpha08
|
||||
android.experimental.lint.version=8.12.0-alpha08
|
||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -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
|
||||
|
37
gradlew
vendored
37
gradlew
vendored
@@ -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.
|
||||
|
26
gradlew.bat
vendored
26
gradlew.bat
vendored
@@ -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
|
||||
|
@@ -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")
|
||||
|
@@ -120,6 +120,7 @@
|
||||
<string name="notice_widget_modified">%1$s modified %2$s widget</string>
|
||||
<string name="notice_widget_modified_by_you">You modified %1$s widget</string>
|
||||
|
||||
<string name="power_level_owner">Owner</string>
|
||||
<string name="power_level_admin">Admin</string>
|
||||
<string name="power_level_moderator">Moderator</string>
|
||||
<string name="power_level_default">Default</string>
|
||||
@@ -685,6 +686,7 @@
|
||||
<string name="room_participants_leave_prompt_title">Leave room</string>
|
||||
<string name="room_participants_leave_prompt_msg">Are you sure you want to leave the room?</string>
|
||||
<string name="room_participants_leave_private_warning">This room is not public. You will not be able to rejoin without an invite.</string>
|
||||
<string name="room_participants_leave_last_admin">You\'re the only admin of this room. Leaving it will mean no one has control over it.</string>
|
||||
|
||||
<string name="room_participants_header_direct_chats">Direct Messages</string>
|
||||
|
||||
@@ -2383,6 +2385,7 @@
|
||||
<string name="room_member_power_level_invites">Invites</string>
|
||||
<string name="room_member_power_level_users">Users</string>
|
||||
|
||||
<string name="room_member_power_level_owner_in">Owner in %1$s</string>
|
||||
<string name="room_member_power_level_admin_in">Admin in %1$s</string>
|
||||
<string name="room_member_power_level_moderator_in">Moderator in %1$s</string>
|
||||
<string name="room_member_power_level_default_in">Default in %1$s</string>
|
||||
|
@@ -64,6 +64,9 @@
|
||||
|
||||
<!-- Material color -->
|
||||
<item name="colorPrimary">@color/element_accent_light</item>
|
||||
<!-- Fix background color of status bar in home and room detail activity -->
|
||||
<!--item name="colorPrimaryDark">@color/element_background_light</item-->
|
||||
<item name="colorPrimaryDark">?vctr_toolbar_background</item>
|
||||
<item name="colorPrimaryVariant">@color/element_accent_light</item>
|
||||
<item name="colorOnPrimary">@android:color/white</item>
|
||||
<item name="colorSecondary">@color/element_accent_light</item>
|
||||
|
@@ -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<RoomPowerLevels> {
|
||||
return room.stateService().getRoomPowerLevelsLive().asFlow()
|
||||
}
|
||||
|
||||
fun liveReadMarker(): Flow<Optional<String>> {
|
||||
return room.readService().getReadMarkerLive().asFlow()
|
||||
}
|
||||
|
@@ -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()}\""
|
||||
|
@@ -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<PowerLevelsContent>()
|
||||
?.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 ?: ""))
|
||||
|
@@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
|
@@ -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()
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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<String>? = 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<RoomCreateContent>() ?: 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)
|
||||
}
|
||||
|
@@ -17,26 +17,21 @@
|
||||
|
||||
package org.matrix.android.sdk.api.session.room.powerlevels
|
||||
|
||||
sealed class Role(open val value: Int) : Comparable<Role> {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
sealed interface UserPowerLevel : Comparable<UserPowerLevel> {
|
||||
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)
|
||||
}
|
||||
}
|
@@ -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<String>)
|
||||
fun getRoomPowerLevels(): RoomPowerLevels
|
||||
fun getRoomPowerLevelsLive(): LiveData<RoomPowerLevels>
|
||||
}
|
||||
|
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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.
|
||||
|
@@ -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<PowerLevelsContent>()
|
||||
?.let { PowerLevelsHelper(it) }
|
||||
|
||||
return powerLevelsHelper?.isUserAbleToInvite(userId) ?: false
|
||||
val roomPowerLevels = stateEventDataSource.getRoomPowerLevels(roomId)
|
||||
return roomPowerLevels.isUserAbleToInvite(userId)
|
||||
}
|
||||
}
|
||||
|
@@ -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>()
|
||||
?: PowerLevelsContent()
|
||||
|
||||
return condition.isSatisfied(event, powerLevelsContent)
|
||||
val roomPowerLevels = room.getRoomPowerLevels()
|
||||
return condition.isSatisfied(event, roomPowerLevels)
|
||||
}
|
||||
|
||||
override fun resolveContainsDisplayNameCondition(
|
||||
|
@@ -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<PowerLevelsContent>()
|
||||
?.let { PowerLevelsHelper(it) }
|
||||
}
|
||||
|
||||
private fun handleInitialAggregatedRelations(
|
||||
realm: Realm,
|
||||
event: Event,
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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<RoomPowerLevels> {
|
||||
val powerLevelsEventLive = getStateEventLive(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty)
|
||||
val roomCreateEventLive = getStateEventLive(roomId, EventType.STATE_ROOM_CREATE, QueryStringValue.IsEmpty)
|
||||
val resultLiveData = MediatorLiveData<RoomPowerLevels>()
|
||||
|
||||
fun emitIfReady(powerLevelEvent: Optional<Event>?, roomCreateEvent: Optional<Event>?) {
|
||||
if (powerLevelEvent != null && roomCreateEvent != null) {
|
||||
val roomPowerLevels = createRoomPowerLevels(
|
||||
powerLevelsEvent = powerLevelEvent.getOrNull(),
|
||||
roomCreateEvent = roomCreateEvent.getOrNull()
|
||||
)
|
||||
resultLiveData.postValue(roomPowerLevels)
|
||||
}
|
||||
}
|
||||
resultLiveData.apply {
|
||||
var powerLevelEvent: Optional<Event>? = null
|
||||
var roomCreateEvent: Optional<Event>? = 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<PowerLevelsContent>()
|
||||
val roomCreateContent = roomCreateEvent?.getRoomCreateContentWithSender()
|
||||
return RoomPowerLevels(powerLevelsContent, roomCreateContent)
|
||||
}
|
@@ -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<RoomPowerLevels> {
|
||||
return stateEventDataSource.getRoomPowerLevelsLive(roomId)
|
||||
}
|
||||
|
||||
override suspend fun sendStateEvent(
|
||||
eventType: String,
|
||||
stateKey: String,
|
||||
|
@@ -34,7 +34,8 @@ import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||
import org.matrix.android.sdk.api.session.room.model.VersioningState
|
||||
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
|
||||
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
||||
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContentWithSender
|
||||
import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels
|
||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||
import org.matrix.android.sdk.api.session.sync.model.RoomSyncSummary
|
||||
import org.matrix.android.sdk.api.session.sync.model.RoomSyncUnreadNotifications
|
||||
@@ -313,13 +314,25 @@ internal class RoomSummaryUpdater @Inject constructor(
|
||||
// check if sender can post child relation in parent?
|
||||
val senderId = parentInfo.stateEventSender
|
||||
val parentRoomId = parentInfo.roomId
|
||||
val powerLevelsHelper = CurrentStateEventEntity
|
||||
val powerLevelsContent = CurrentStateEventEntity
|
||||
.getOrNull(realm, parentRoomId, "", EventType.STATE_ROOM_POWER_LEVELS)
|
||||
?.root
|
||||
?.let { ContentMapper.map(it.content).toModel<PowerLevelsContent>() }
|
||||
?.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<RoomCreateContent>()
|
||||
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) {
|
||||
|
@@ -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<PowerLevelsContent>()
|
||||
?.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 {
|
||||
|
@@ -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<PowerLevelsContent>()
|
||||
?: 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")
|
||||
}
|
||||
}
|
||||
|
@@ -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<PowerLevelsContent>() ?: 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)
|
||||
}
|
||||
}
|
||||
|
@@ -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<PowerLevelsHelper>()
|
||||
every { powerLevelsHelper.isUserAbleToRedact(userId) } returns isAbleToRedact
|
||||
return powerLevelsHelper
|
||||
private fun mockRedactionPowerLevels(userId: String, isAbleToRedact: Boolean): RoomPowerLevels {
|
||||
val roomPowerLevels = mockk<RoomPowerLevels>()
|
||||
every { roomPowerLevels.isUserAbleToRedact(userId) } returns isAbleToRedact
|
||||
return roomPowerLevels
|
||||
}
|
||||
}
|
||||
|
@@ -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>()
|
||||
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<RoomGuestAccessContent>()?.guestAccess shouldBeEqualTo GuestAccess.Forbidden
|
||||
|
@@ -37,7 +37,7 @@ ext.versionMinor = 6
|
||||
// Note: even values are reserved for regular release, odd values for hotfix release.
|
||||
// When creating a hotfix, you should decrease the value, since the current value
|
||||
// is the value for the next regular release.
|
||||
ext.versionPatch = 42
|
||||
ext.versionPatch = 44
|
||||
|
||||
static def getGitTimestamp() {
|
||||
def cmd = 'git show -s --format=%ct'
|
||||
|
@@ -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<ActivityDebugMenuBinding>() {
|
||||
|
||||
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
|
||||
|
@@ -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<ActivityDebugPermissionBindin
|
||||
|
||||
override fun getCoordinatorLayout() = views.coordinatorLayout
|
||||
|
||||
override val rootView: View
|
||||
get() = views.coordinatorLayout
|
||||
|
||||
// For debug
|
||||
private val allPermissions = listOf(
|
||||
Manifest.permission.CAMERA,
|
||||
|
@@ -7,6 +7,7 @@
|
||||
|
||||
package im.vector.app.features.debug.analytics
|
||||
|
||||
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 DebugAnalyticsActivity : VectorBaseActivity<ActivitySimpleBinding>() {
|
||||
|
||||
override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater)
|
||||
|
||||
override fun getCoordinatorLayout() = views.coordinatorLayout
|
||||
override val rootView: View
|
||||
get() = views.coordinatorLayout
|
||||
|
||||
override fun initUiAndData() {
|
||||
if (isFirstCreation()) {
|
||||
addFragment(
|
||||
|
@@ -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<FragmentGenericRecycler
|
||||
|
||||
override fun getBinding() = FragmentGenericRecyclerBinding.inflate(layoutInflater)
|
||||
|
||||
override val rootView: View
|
||||
get() = views.mainRoot
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
controller.listener = object : FeaturesController.Listener {
|
||||
|
@@ -8,6 +8,7 @@
|
||||
package im.vector.app.features.debug.jitsi
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.View
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
import im.vector.application.databinding.ActivityDebugJitsiBinding
|
||||
@@ -19,6 +20,8 @@ class DebugJitsiActivity : VectorBaseActivity<ActivityDebugJitsiBinding>() {
|
||||
override fun getBinding() = ActivityDebugJitsiBinding.inflate(layoutInflater)
|
||||
|
||||
override fun getCoordinatorLayout() = views.coordinatorLayout
|
||||
override val rootView: View
|
||||
get() = views.coordinatorLayout
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun initUiAndData() {
|
||||
|
@@ -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<ActivitySimpleBinding>() {
|
||||
|
||||
override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater)
|
||||
|
||||
override fun getCoordinatorLayout() = views.coordinatorLayout
|
||||
override val rootView: View
|
||||
get() = views.coordinatorLayout
|
||||
|
||||
override fun initUiAndData() {
|
||||
if (isFirstCreation()) {
|
||||
addFragment(
|
||||
|
@@ -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<ActivitySimpleBinding>()
|
||||
|
||||
override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater)
|
||||
|
||||
override fun getCoordinatorLayout() = views.coordinatorLayout
|
||||
override val rootView: View
|
||||
get() = views.coordinatorLayout
|
||||
|
||||
override fun initUiAndData() {
|
||||
if (isFirstCreation()) {
|
||||
addFragment(
|
||||
|
@@ -161,6 +161,7 @@ class VectorApplication :
|
||||
"Noto Color Emoji Compat",
|
||||
R.array.com_google_android_gms_fonts_certs
|
||||
)
|
||||
@Suppress("DEPRECATION")
|
||||
FontsContractCompat.requestFont(this, fontRequest, emojiCompatFontProvider, getFontThreadHandler())
|
||||
vectorLocale.init()
|
||||
ThemeUtils.init(this)
|
||||
|
@@ -167,8 +167,7 @@
|
||||
|
||||
<activity
|
||||
android:name=".features.home.room.detail.RoomDetailActivity"
|
||||
android:parentActivityName=".features.home.HomeActivity"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
android:parentActivityName=".features.home.HomeActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".features.home.HomeActivity" />
|
||||
@@ -369,8 +368,8 @@
|
||||
|
||||
<service
|
||||
android:name=".core.services.CallAndroidService"
|
||||
android:foregroundServiceType="phoneCall"
|
||||
android:exported="false">
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="phoneCall">
|
||||
<!-- in order to get headset button events -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||
@@ -387,7 +386,8 @@
|
||||
<service
|
||||
android:name=".features.call.telecom.VectorConnectionAndroidService"
|
||||
android:exported="false"
|
||||
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
|
||||
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
|
||||
tools:targetApi="M">
|
||||
<intent-filter>
|
||||
<action android:name="android.telecom.ConnectionService" />
|
||||
</intent-filter>
|
||||
@@ -413,8 +413,7 @@
|
||||
android:name=".features.call.audio.MicrophoneAccessService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="microphone"
|
||||
android:permission="android.permission.FOREGROUND_SERVICE_MICROPHONE">
|
||||
</service>
|
||||
android:permission="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
|
||||
|
||||
<!-- Receivers -->
|
||||
|
||||
|
@@ -137,7 +137,7 @@ class SpaceStateHandlerImpl @Inject constructor(
|
||||
|
||||
override fun popSpaceBackstack(): String? {
|
||||
vectorPreferences.getSpaceBackstack().toMutableList().apply {
|
||||
val poppedSpaceId = removeLast()
|
||||
val poppedSpaceId = removeAt(lastIndex)
|
||||
vectorPreferences.setSpaceBackstack(this)
|
||||
return poppedSpaceId
|
||||
}
|
||||
|
@@ -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<ActivityBinding>() {
|
||||
|
||||
final override fun getCoordinatorLayout() = views.coordinatorLayout
|
||||
|
||||
override val rootView: View
|
||||
get() = views.coordinatorLayout
|
||||
|
||||
override fun initUiAndData() {
|
||||
setupToolbar(views.toolbar)
|
||||
.allowBack(true)
|
||||
|
@@ -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
|
||||
@@ -208,6 +210,8 @@ abstract class VectorBaseActivity<VB : ViewBinding> : 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()
|
||||
@@ -247,7 +251,9 @@ abstract class VectorBaseActivity<VB : ViewBinding> : 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
|
||||
}
|
||||
}
|
||||
@@ -334,7 +340,8 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
||||
private fun handleCertificateError(certificateError: GlobalError.CertificateError) {
|
||||
singletonEntryPoint()
|
||||
.unrecognizedCertificateDialog()
|
||||
.show(this,
|
||||
.show(
|
||||
this,
|
||||
certificateError.fingerprint,
|
||||
object : UnrecognizedCertificateDialog.Callback {
|
||||
override fun onAccept() {
|
||||
@@ -411,6 +418,21 @@ abstract class VectorBaseActivity<VB : ViewBinding> : 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>()
|
||||
@@ -444,14 +466,6 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
||||
mdmService.unregisterListener(this)
|
||||
}
|
||||
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
super.onWindowFocusChanged(hasFocus)
|
||||
|
||||
if (hasFocus && displayInFullscreen()) {
|
||||
setFullScreen()
|
||||
}
|
||||
}
|
||||
|
||||
private val onMultiWindowModeChangedListener = Consumer<MultiWindowModeChangedInfo> {
|
||||
Timber.w("onMultiWindowModeChanged. isInMultiWindowMode: ${it.isInMultiWindowMode}")
|
||||
bugReporter.inMultiWindowMode = it.isInMultiWindowMode
|
||||
@@ -461,30 +475,6 @@ abstract class VectorBaseActivity<VB : ViewBinding> : 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 -> {
|
||||
@@ -586,8 +576,6 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
||||
|
||||
abstract fun getBinding(): VB
|
||||
|
||||
open fun displayInFullscreen() = false
|
||||
|
||||
open fun doBeforeSetContentView() = Unit
|
||||
|
||||
open fun initUiAndData() = Unit
|
||||
@@ -626,6 +614,8 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
||||
|
||||
open fun getCoordinatorLayout(): CoordinatorLayout? = null
|
||||
|
||||
abstract val rootView: View
|
||||
|
||||
/* ==========================================================================================
|
||||
* User Consent
|
||||
* ========================================================================================== */
|
||||
|
@@ -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>(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,
|
||||
|
@@ -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<CallArgs>(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
|
||||
}
|
||||
|
@@ -658,7 +658,8 @@ class ExpandingBottomSheetBehavior<V : View> : CoordinatorLayout.Behavior<V> {
|
||||
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
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
@@ -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<ActivityMainBinding>(), UnlockedActivity
|
||||
val className = componentName.className
|
||||
return packageName == buildMeta.applicationId && className in allowList
|
||||
}
|
||||
|
||||
override val rootView: View
|
||||
get() = views.mainRoot
|
||||
}
|
||||
|
@@ -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<ActivitySimpleBinding>() {
|
||||
|
||||
override fun getCoordinatorLayout() = views.coordinatorLayout
|
||||
|
||||
override val rootView: View
|
||||
get() = views.coordinatorLayout
|
||||
|
||||
override fun initUiAndData() {
|
||||
orientationLocker.lockPhonesToPortrait(this)
|
||||
if (isFirstCreation()) {
|
||||
|
@@ -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<ActivitySimpleBinding>() {
|
||||
|
||||
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
|
||||
|
@@ -163,6 +163,7 @@ class AttachmentsPreviewFragment :
|
||||
|
||||
private fun applyInsets() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
@Suppress("DEPRECATION")
|
||||
activity?.window?.setDecorFitsSystemWindows(false)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
|
@@ -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()
|
||||
|
@@ -43,6 +43,11 @@ internal class API21AudioDeviceDetector(
|
||||
return HashSet<CallAudioManager.Device>().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))
|
||||
}
|
||||
}
|
||||
|
@@ -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<ActivityJitsiBinding>(), 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()
|
||||
|
@@ -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<View>(R.id.zero).setOnClickListener { keyPressed(KeyEvent.KEYCODE_0, "0") }
|
||||
dialpadView.findViewById<View>(R.id.one).setOnClickListener { keyPressed(KeyEvent.KEYCODE_1, "1") }
|
||||
|
@@ -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<ActivityCallTransferBinding>() {
|
||||
|
||||
override fun getCoordinatorLayout() = views.vectorCoordinatorLayout
|
||||
|
||||
override val rootView: View
|
||||
get() = views.vectorCoordinatorLayout
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
waitingView = views.waitingView.waitingView
|
||||
|
@@ -122,6 +122,7 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetBoot
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
val rootView = super.onCreateView(inflater, container, savedInstanceState)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
@Suppress("DEPRECATION")
|
||||
dialog?.window?.setDecorFitsSystemWindows(false)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
|
@@ -192,6 +192,9 @@ class HomeActivity :
|
||||
|
||||
override fun getCoordinatorLayout() = views.coordinatorLayout
|
||||
|
||||
override val rootView: View
|
||||
get() = views.coordinatorLayout
|
||||
|
||||
override fun getBinding() = ActivityHomeBinding.inflate(layoutInflater)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
@@ -13,7 +13,9 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.app.ActivityOptionsCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.observeK
|
||||
@@ -109,6 +111,20 @@ class HomeDrawerFragment :
|
||||
}
|
||||
}
|
||||
|
||||
ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
|
||||
val systemBars = insets.getInsets(
|
||||
WindowInsetsCompat.Type.systemBars() or
|
||||
WindowInsetsCompat.Type.displayCutout()
|
||||
)
|
||||
v.updatePadding(
|
||||
systemBars.left,
|
||||
systemBars.top,
|
||||
systemBars.right,
|
||||
systemBars.bottom,
|
||||
)
|
||||
WindowInsetsCompat.CONSUMED
|
||||
}
|
||||
|
||||
// Debug menu
|
||||
views.homeDrawerHeaderDebugView.debouncedClicks {
|
||||
sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer)
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -32,11 +32,9 @@ import androidx.core.net.toUri
|
||||
import androidx.core.text.toSpannable
|
||||
import androidx.core.util.Pair
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.forEach
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.setFragmentResultListener
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.withResumed
|
||||
@@ -409,13 +407,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() {
|
||||
|
@@ -54,7 +54,6 @@ import im.vector.app.features.home.room.typing.TypingHelper
|
||||
import im.vector.app.features.location.live.StopLiveLocationShareUseCase
|
||||
import im.vector.app.features.location.live.tracking.LocationSharingServiceConnection
|
||||
import im.vector.app.features.notifications.NotificationDrawerManager
|
||||
import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
|
||||
import im.vector.app.features.raw.wellknown.CryptoConfig
|
||||
import im.vector.app.features.raw.wellknown.getOutboundSessionKeySharingStrategyOrDefault
|
||||
import im.vector.app.features.raw.wellknown.withElementWellKnown
|
||||
@@ -110,7 +109,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachme
|
||||
import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
|
||||
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
||||
import org.matrix.android.sdk.api.session.room.model.tombstone.RoomTombstoneContent
|
||||
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
||||
import org.matrix.android.sdk.api.session.room.read.ReadService
|
||||
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
@@ -303,12 +301,12 @@ class TimelineViewModel @AssistedInject constructor(
|
||||
|
||||
private fun observePowerLevel() {
|
||||
if (room == null) return
|
||||
PowerLevelsFlowFactory(room).createFlow()
|
||||
.onEach {
|
||||
val canInvite = PowerLevelsHelper(it).isUserAbleToInvite(session.myUserId)
|
||||
room.flow().liveRoomPowerLevels()
|
||||
.onEach { powerLevels ->
|
||||
val canInvite = powerLevels.isUserAbleToInvite(session.myUserId)
|
||||
val isAllowedToManageWidgets = session.widgetService().hasPermissionsToHandleWidgets(room.roomId)
|
||||
val isAllowedToStartWebRTCCall = PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, false, EventType.CALL_INVITE)
|
||||
val isAllowedToSetupEncryption = PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_ENCRYPTION)
|
||||
val isAllowedToStartWebRTCCall = powerLevels.isUserAllowedToSend(session.myUserId, false, EventType.CALL_INVITE)
|
||||
val isAllowedToSetupEncryption = powerLevels.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_ENCRYPTION)
|
||||
setState {
|
||||
copy(
|
||||
canInvite = canInvite,
|
||||
|
@@ -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
|
||||
@@ -180,10 +178,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) {
|
||||
|
@@ -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<ActivitySearchBinding>() {
|
||||
|
||||
override fun getCoordinatorLayout() = views.coordinatorLayout
|
||||
|
||||
override val rootView: View
|
||||
get() = views.coordinatorLayout
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setupToolbar(views.searchToolbar)
|
||||
|
@@ -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)
|
||||
|
@@ -24,16 +24,13 @@ import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovement
|
||||
import im.vector.lib.strings.CommonPlurals
|
||||
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.api.session.getRoom
|
||||
import org.matrix.android.sdk.api.session.room.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
|
||||
|
||||
@@ -303,9 +300,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<PowerLevelsContent>() }
|
||||
?.let { PowerLevelsHelper(it) }
|
||||
val roomPowerLevels = activeSessionHolder.getSafeActiveSession()?.getRoom(event.roomId)?.getRoomPowerLevels()
|
||||
val currentUserId = activeSessionHolder.getSafeActiveSession()?.myUserId ?: ""
|
||||
val attributes = MergedRoomCreationItem.Attributes(
|
||||
isCollapsed = isCollapsed,
|
||||
@@ -320,10 +315,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)
|
||||
|
@@ -41,7 +41,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
|
||||
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
|
||||
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
||||
import org.matrix.android.sdk.api.session.room.powerlevels.RoomPowerLevels
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import org.matrix.android.sdk.api.session.widgets.model.WidgetContent
|
||||
import timber.log.Timber
|
||||
@@ -122,8 +122,8 @@ class NoticeEventFormatter @Inject constructor(
|
||||
userIds.addAll(previousPowerLevelsContent.users.orEmpty().keys)
|
||||
val diffs = ArrayList<String>()
|
||||
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)
|
||||
|
@@ -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<ActivityFilteredRoomsBinding>()
|
||||
|
||||
override fun getCoordinatorLayout() = views.coordinatorLayout
|
||||
|
||||
override val rootView: View
|
||||
get() = views.coordinatorLayout
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
analyticsScreenName = MobileScreen.ScreenName.RoomFilter
|
||||
|
@@ -25,7 +25,6 @@ import com.airbnb.epoxy.OnModelBuildFinishedListener
|
||||
import com.airbnb.mvrx.args
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.LayoutManagerStateRestorer
|
||||
@@ -45,6 +44,7 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA
|
||||
import im.vector.app.features.home.room.list.widget.NotifsFabMenuView
|
||||
import im.vector.app.features.matrixto.OriginOfMatrixTo
|
||||
import im.vector.app.features.notifications.NotificationDrawerManager
|
||||
import im.vector.app.features.room.LeaveRoomPrompt
|
||||
import im.vector.lib.strings.CommonStrings
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
@@ -422,7 +422,7 @@ class RoomListFragment :
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleQuickActions(quickAction: RoomListQuickActionsSharedAction) {
|
||||
private suspend fun handleQuickActions(quickAction: RoomListQuickActionsSharedAction) {
|
||||
when (quickAction) {
|
||||
is RoomListQuickActionsSharedAction.NotificationsAllNoisy -> {
|
||||
roomListViewModel.handle(RoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.ALL_MESSAGES_NOISY))
|
||||
@@ -451,26 +451,11 @@ class RoomListFragment :
|
||||
}
|
||||
}
|
||||
|
||||
private fun promptLeaveRoom(roomId: String) {
|
||||
val isPublicRoom = roomListViewModel.isPublicRoom(roomId)
|
||||
val message = buildString {
|
||||
append(getString(CommonStrings.room_participants_leave_prompt_msg))
|
||||
if (!isPublicRoom) {
|
||||
append("\n\n")
|
||||
append(getString(CommonStrings.room_participants_leave_private_warning))
|
||||
}
|
||||
private suspend fun promptLeaveRoom(roomId: String) {
|
||||
val warning = roomListViewModel.getLeaveRoomWarning(roomId)
|
||||
LeaveRoomPrompt.show(requireContext(), warning) {
|
||||
roomListViewModel.handle(RoomListAction.LeaveRoom(roomId))
|
||||
}
|
||||
MaterialAlertDialogBuilder(
|
||||
requireContext(),
|
||||
if (isPublicRoom) 0 else im.vector.lib.ui.styles.R.style.ThemeOverlay_Vector_MaterialAlertDialog_Destructive
|
||||
)
|
||||
.setTitle(CommonStrings.room_participants_leave_prompt_title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(CommonStrings.action_leave) { _, _ ->
|
||||
roomListViewModel.handle(RoomListAction.LeaveRoom(roomId))
|
||||
}
|
||||
.setNegativeButton(CommonStrings.action_cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(roomListViewModel) { state ->
|
||||
|
@@ -26,6 +26,8 @@ import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom
|
||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||
import im.vector.app.features.displayname.getBestName
|
||||
import im.vector.app.features.invite.AutoAcceptInvites
|
||||
import im.vector.app.features.room.LeaveRoomPrompt
|
||||
import im.vector.app.features.room.getLeaveRoomWarning
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
@@ -41,7 +43,6 @@ import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||
import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho
|
||||
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
|
||||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.room.state.isPublic
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import org.matrix.android.sdk.flow.flow
|
||||
import timber.log.Timber
|
||||
@@ -150,8 +151,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 *****************************************************************************
|
||||
|
@@ -18,7 +18,6 @@ import androidx.recyclerview.widget.ConcatAdapter.Config.StableIdMode
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.airbnb.epoxy.OnModelBuildFinishedListener
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.core.epoxy.LayoutManagerStateRestorer
|
||||
import im.vector.app.core.extensions.cleanup
|
||||
@@ -36,7 +35,7 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA
|
||||
import im.vector.app.features.home.room.list.home.header.HomeRoomFilter
|
||||
import im.vector.app.features.home.room.list.home.header.HomeRoomsHeadersController
|
||||
import im.vector.app.features.home.room.list.home.invites.InvitesActivity
|
||||
import im.vector.lib.strings.CommonStrings
|
||||
import im.vector.app.features.room.LeaveRoomPrompt
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
@@ -103,7 +102,7 @@ class HomeRoomListFragment :
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleQuickActions(quickAction: RoomListQuickActionsSharedAction) {
|
||||
private suspend fun handleQuickActions(quickAction: RoomListQuickActionsSharedAction) {
|
||||
when (quickAction) {
|
||||
is RoomListQuickActionsSharedAction.NotificationsAllNoisy -> {
|
||||
roomListViewModel.handle(HomeRoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.ALL_MESSAGES_NOISY))
|
||||
@@ -185,26 +184,11 @@ class HomeRoomListFragment :
|
||||
concatAdapter.addAdapter(roomsAdapter)
|
||||
}
|
||||
|
||||
private fun promptLeaveRoom(roomId: String) {
|
||||
val isPublicRoom = roomListViewModel.isPublicRoom(roomId)
|
||||
val message = buildString {
|
||||
append(getString(CommonStrings.room_participants_leave_prompt_msg))
|
||||
if (!isPublicRoom) {
|
||||
append("\n\n")
|
||||
append(getString(CommonStrings.room_participants_leave_private_warning))
|
||||
}
|
||||
private suspend fun promptLeaveRoom(roomId: String) {
|
||||
val warning = roomListViewModel.getLeaveRoomWarning(roomId)
|
||||
LeaveRoomPrompt.show(requireContext(), warning) {
|
||||
roomListViewModel.handle(HomeRoomListAction.LeaveRoom(roomId))
|
||||
}
|
||||
MaterialAlertDialogBuilder(
|
||||
requireContext(),
|
||||
if (isPublicRoom) 0 else im.vector.lib.ui.styles.R.style.ThemeOverlay_Vector_MaterialAlertDialog_Destructive
|
||||
)
|
||||
.setTitle(CommonStrings.room_participants_leave_prompt_title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(CommonStrings.action_leave) { _, _ ->
|
||||
roomListViewModel.handle(HomeRoomListAction.LeaveRoom(roomId))
|
||||
}
|
||||
.setNegativeButton(CommonStrings.action_cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun onInvitesCounterClicked() {
|
||||
|
@@ -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
|
||||
@@ -331,8 +332,8 @@ class HomeRoomListViewModel @AssistedInject constructor(
|
||||
filteredPagedRoomSummariesLive.queryParams = getFilteredQueryParams(newFilter, filteredPagedRoomSummariesLive.queryParams)
|
||||
}
|
||||
|
||||
fun isPublicRoom(roomId: String): Boolean {
|
||||
return session.getRoom(roomId)?.stateService()?.isPublic().orFalse()
|
||||
suspend fun getLeaveRoomWarning(roomId: String): LeaveRoomPrompt.Warning {
|
||||
return session.getLeaveRoomWarning(roomId)
|
||||
}
|
||||
|
||||
private fun handleSelectRoom(action: HomeRoomListAction.SelectRoom) = withState {
|
||||
|
@@ -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<ActivitySimpleBinding>() {
|
||||
addFragment(views.simpleFragmentContainer, InvitesFragment::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCoordinatorLayout() = views.coordinatorLayout
|
||||
override val rootView: View
|
||||
get() = views.coordinatorLayout
|
||||
}
|
||||
|
@@ -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<ActivitySimpleBinding>() {
|
||||
|
||||
override fun getCoordinatorLayout() = views.coordinatorLayout
|
||||
|
||||
override val rootView: View
|
||||
get() = views.coordinatorLayout
|
||||
|
||||
override fun initUiAndData() {
|
||||
orientationLocker.lockPhonesToPortrait(this)
|
||||
if (isFirstCreation()) {
|
||||
|
@@ -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<ActivityThreadsBinding>() {
|
||||
|
||||
override fun getCoordinatorLayout() = views.coordinatorLayout
|
||||
|
||||
override val rootView: View
|
||||
get() = views.coordinatorLayout
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
initFragment()
|
||||
|
@@ -32,7 +32,7 @@ class VectorActivityLifecycleCallbacks constructor(private val popupAlertManager
|
||||
/**
|
||||
* The activities information collected from the app manifest.
|
||||
*/
|
||||
private var activitiesInfo: Array<ActivityInfo> = emptyArray()
|
||||
private var activitiesInfo: List<ActivityInfo>? = null
|
||||
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob())
|
||||
|
||||
@@ -51,24 +51,32 @@ class VectorActivityLifecycleCallbacks constructor(private val popupAlertManager
|
||||
override fun onActivityStopped(activity: Activity) {}
|
||||
|
||||
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
|
||||
if (activitiesInfo.isEmpty()) {
|
||||
if (activitiesInfo == null) {
|
||||
val context = activity.applicationContext
|
||||
val packageManager: PackageManager = context.packageManager
|
||||
|
||||
// Get all activities from element android
|
||||
activitiesInfo = packageManager.getPackageInfoCompat(context.packageName, PackageManager.GET_ACTIVITIES).activities
|
||||
|
||||
val activities = packageManager
|
||||
.getPackageInfoCompat(context.packageName, PackageManager.GET_ACTIVITIES)
|
||||
.activities
|
||||
.orEmpty()
|
||||
.toList()
|
||||
// Get all activities from PermissionController module
|
||||
// See https://source.android.com/docs/core/architecture/modular-system/permissioncontroller#package-format
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.S_V2) {
|
||||
activitiesInfo += tryOrNull {
|
||||
val otherActivities = if (Build.VERSION.SDK_INT > Build.VERSION_CODES.S_V2) {
|
||||
(tryOrNull {
|
||||
packageManager.getPackageInfoCompat("com.google.android.permissioncontroller", PackageManager.GET_ACTIVITIES).activities
|
||||
} ?: tryOrNull {
|
||||
packageManager.getModuleInfo("com.google.android.permission", 1).packageName?.let {
|
||||
packageManager.getPackageInfoCompat(it, PackageManager.GET_ACTIVITIES or PackageManager.MATCH_APEX).activities
|
||||
}
|
||||
}.orEmpty()
|
||||
})
|
||||
.orEmpty()
|
||||
.toList()
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
activitiesInfo = activities + otherActivities
|
||||
}
|
||||
|
||||
// restart the app if the task contains an unknown activity
|
||||
@@ -144,5 +152,5 @@ class VectorActivityLifecycleCallbacks constructor(private val popupAlertManager
|
||||
* @param activity the activity of the task
|
||||
* @return true if the activity is potentially malicious
|
||||
*/
|
||||
private fun isPotentialMaliciousActivity(activity: ComponentName): Boolean = activitiesInfo.none { it.name == activity.className }
|
||||
private fun isPotentialMaliciousActivity(activity: ComponentName): Boolean = activitiesInfo.orEmpty().none { it.name == activity.className }
|
||||
}
|
||||
|
@@ -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<ActivityProgressBinding>() {
|
||||
|
||||
override fun getBinding() = ActivityProgressBinding.inflate(layoutInflater)
|
||||
|
||||
override val rootView: View
|
||||
get() = views.mainRoot
|
||||
|
||||
override fun initUiAndData() {
|
||||
handleIntent()
|
||||
}
|
||||
|
@@ -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<ActivityLocationSharingBindin
|
||||
|
||||
override fun getBinding() = ActivityLocationSharingBinding.inflate(layoutInflater)
|
||||
|
||||
override val rootView: View
|
||||
get() = views.mainRoot
|
||||
|
||||
override fun initUiAndData() {
|
||||
val locationSharingArgs: LocationSharingArgs? = intent?.extras?.getParcelableCompat(EXTRA_LOCATION_SHARING_ARGS)
|
||||
if (locationSharingArgs == null) {
|
||||
|
@@ -7,6 +7,7 @@
|
||||
|
||||
package im.vector.app.features.location
|
||||
|
||||
import android.Manifest
|
||||
import android.graphics.drawable.Drawable
|
||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||
import dagger.assisted.Assisted
|
||||
@@ -15,9 +16,9 @@ import dagger.assisted.AssistedInject
|
||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.core.utils.PermissionChecker
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
|
||||
import im.vector.app.features.location.domain.usecase.CompareLocationsUseCase
|
||||
import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
@@ -32,8 +33,8 @@ import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.getRoom
|
||||
import org.matrix.android.sdk.api.session.getUserOrDefault
|
||||
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import org.matrix.android.sdk.flow.flow
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
@@ -48,6 +49,7 @@ class LocationSharingViewModel @AssistedInject constructor(
|
||||
private val session: Session,
|
||||
private val compareLocationsUseCase: CompareLocationsUseCase,
|
||||
private val vectorPreferences: VectorPreferences,
|
||||
private val permissionChecker: PermissionChecker,
|
||||
) : VectorViewModel<LocationSharingViewState, LocationSharingAction, LocationSharingViewEvents>(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() {
|
||||
|
@@ -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<LocationManager>()
|
||||
@@ -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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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<ActivityLocationSharingBi
|
||||
|
||||
override fun getBinding() = ActivityLocationSharingBinding.inflate(layoutInflater)
|
||||
|
||||
override val rootView: View
|
||||
get() = views.mainRoot
|
||||
|
||||
override fun initUiAndData() {
|
||||
val mapViewArgs: LiveLocationMapViewArgs? = intent?.extras?.getParcelableCompat(EXTRA_LIVE_LOCATION_MAP_VIEW_ARGS)
|
||||
if (mapViewArgs == null) {
|
||||
|
@@ -7,6 +7,7 @@
|
||||
|
||||
package im.vector.app.features.location.live.map
|
||||
|
||||
import android.Manifest
|
||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
@@ -14,6 +15,7 @@ import dagger.assisted.AssistedInject
|
||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.core.utils.PermissionChecker
|
||||
import im.vector.app.features.location.LocationData
|
||||
import im.vector.app.features.location.LocationTracker
|
||||
import im.vector.app.features.location.live.StopLiveLocationShareUseCase
|
||||
@@ -23,6 +25,7 @@ import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult
|
||||
import timber.log.Timber
|
||||
|
||||
class LiveLocationMapViewModel @AssistedInject constructor(
|
||||
@Assisted private val initialState: LiveLocationMapViewState,
|
||||
@@ -31,6 +34,7 @@ class LiveLocationMapViewModel @AssistedInject constructor(
|
||||
private val locationSharingServiceConnection: LocationSharingServiceConnection,
|
||||
private val stopLiveLocationShareUseCase: StopLiveLocationShareUseCase,
|
||||
private val locationTracker: LocationTracker,
|
||||
private val permissionChecker: PermissionChecker,
|
||||
) :
|
||||
VectorViewModel<LiveLocationMapViewState, LiveLocationMapAction, LiveLocationMapViewEvents>(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()
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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<LocationPreviewViewState, LocationPreviewAction, LocationPreviewViewEvents>(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()
|
||||
}
|
||||
}
|
||||
|
@@ -76,6 +76,9 @@ open class LoginActivity : VectorBaseActivity<ActivityLoginBinding>(), UnlockedA
|
||||
|
||||
override fun getCoordinatorLayout() = views.coordinatorLayout
|
||||
|
||||
override val rootView: View
|
||||
get() = views.coordinatorLayout
|
||||
|
||||
override fun initUiAndData() {
|
||||
analyticsScreenName = MobileScreen.ScreenName.Login
|
||||
|
||||
|
@@ -10,6 +10,7 @@ package im.vector.app.features.media
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.core.net.toUri
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
@@ -26,6 +27,9 @@ class BigImageViewerActivity : VectorBaseActivity<ActivityBigImageViewerBinding>
|
||||
|
||||
override fun getBinding() = ActivityBigImageViewerBinding.inflate(layoutInflater)
|
||||
|
||||
override val rootView: View
|
||||
get() = views.mainRoot
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
|
@@ -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()
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user