mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-22 01:15:54 +03:00
Merge branch 'develop' into feature/bca/rust_flavor
This commit is contained in:
commit
03379a6636
47 changed files with 428 additions and 210 deletions
1
.github/ISSUE_TEMPLATE/release.yml
vendored
1
.github/ISSUE_TEMPLATE/release.yml
vendored
|
@ -10,7 +10,6 @@ body:
|
||||||
id: checklist
|
id: checklist
|
||||||
attributes:
|
attributes:
|
||||||
label: Release checklist
|
label: Release checklist
|
||||||
description: For the template example, we are releasing the version 1.2.3. Replace 1.2.3 with the version in the issue body.
|
|
||||||
placeholder: |
|
placeholder: |
|
||||||
If you are reading this, you have deleted the content of the release template: undo the deletion or start again.
|
If you are reading this, you have deleted the content of the release template: undo the deletion or start again.
|
||||||
value: |
|
value: |
|
||||||
|
|
2
.github/workflows/danger.yml
vendored
2
.github/workflows/danger.yml
vendored
|
@ -11,7 +11,7 @@ jobs:
|
||||||
- run: |
|
- run: |
|
||||||
npm install --save-dev @babel/plugin-transform-flow-strip-types
|
npm install --save-dev @babel/plugin-transform-flow-strip-types
|
||||||
- name: Danger
|
- name: Danger
|
||||||
uses: danger/danger-js@11.1.4
|
uses: danger/danger-js@11.2.0
|
||||||
with:
|
with:
|
||||||
args: "--dangerfile tools/danger/dangerfile.js"
|
args: "--dangerfile tools/danger/dangerfile.js"
|
||||||
env:
|
env:
|
||||||
|
|
2
.github/workflows/quality.yml
vendored
2
.github/workflows/quality.yml
vendored
|
@ -66,7 +66,7 @@ jobs:
|
||||||
yarn add danger-plugin-lint-report --dev
|
yarn add danger-plugin-lint-report --dev
|
||||||
- name: Danger lint
|
- name: Danger lint
|
||||||
if: always()
|
if: always()
|
||||||
uses: danger/danger-js@11.1.4
|
uses: danger/danger-js@11.2.0
|
||||||
with:
|
with:
|
||||||
args: "--dangerfile tools/danger/dangerfile-lint.js"
|
args: "--dangerfile tools/danger/dangerfile-lint.js"
|
||||||
env:
|
env:
|
||||||
|
|
3
.github/workflows/triage-labelled.yml
vendored
3
.github/workflows/triage-labelled.yml
vendored
|
@ -17,7 +17,8 @@ jobs:
|
||||||
contains(github.event.issue.labels.*.name, 'Z-IA') ||
|
contains(github.event.issue.labels.*.name, 'Z-IA') ||
|
||||||
contains(github.event.issue.labels.*.name, 'A-Themes-Custom') ||
|
contains(github.event.issue.labels.*.name, 'A-Themes-Custom') ||
|
||||||
contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') ||
|
contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') ||
|
||||||
contains(github.event.issue.labels.*.name, 'A-Tags')
|
contains(github.event.issue.labels.*.name, 'A-Tags') ||
|
||||||
|
contains(github.event.issue.labels.*.name, 'A-Rich-Text-Editor')
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@v5
|
- uses: actions/github-script@v5
|
||||||
with:
|
with:
|
||||||
|
|
36
CHANGES.md
36
CHANGES.md
|
@ -1,3 +1,39 @@
|
||||||
|
Changes in Element v1.5.10 (2022-11-30)
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
Features ✨
|
||||||
|
----------
|
||||||
|
- Add setting to allow disabling direct share ([#2725](https://github.com/vector-im/element-android/issues/2725))
|
||||||
|
- [Device Manager] Toggle IP address visibility ([#7546](https://github.com/vector-im/element-android/issues/7546))
|
||||||
|
- New implementation of the full screen mode for the Rich Text Editor. ([#7577](https://github.com/vector-im/element-android/issues/7577))
|
||||||
|
|
||||||
|
Bugfixes 🐛
|
||||||
|
----------
|
||||||
|
- Fix italic text is truncated when bubble mode and markdown is enabled ([#5679](https://github.com/vector-im/element-android/issues/5679))
|
||||||
|
- Missing translations on "replyTo" messages ([#7555](https://github.com/vector-im/element-android/issues/7555))
|
||||||
|
- ANR on session start when sending client info is enabled ([#7604](https://github.com/vector-im/element-android/issues/7604))
|
||||||
|
- Make the plain text mode layout of the RTE more compact. ([#7620](https://github.com/vector-im/element-android/issues/7620))
|
||||||
|
- Push notification for thread message is now shown correctly when user observes rooms main timeline ([#7634](https://github.com/vector-im/element-android/issues/7634))
|
||||||
|
- Voice Broadcast - Fix playback stuck in buffering mode ([#7646](https://github.com/vector-im/element-android/issues/7646))
|
||||||
|
|
||||||
|
In development 🚧
|
||||||
|
----------------
|
||||||
|
- Voice Broadcast - Handle redaction of the state events on the listener and recorder sides ([#7629](https://github.com/vector-im/element-android/issues/7629))
|
||||||
|
- Voice Broadcast - Update the buffering display in the timeline ([#7655](https://github.com/vector-im/element-android/issues/7655))
|
||||||
|
- Voice Broadcast - Remove voice messages related to a VB from the room attachments ([#7656](https://github.com/vector-im/element-android/issues/7656))
|
||||||
|
|
||||||
|
SDK API changes ⚠️
|
||||||
|
------------------
|
||||||
|
- Added support for read receipts in threads. Now user in a room can have multiple read receipts (one per thread + one in main thread + one without threadId) ([#6996](https://github.com/vector-im/element-android/issues/6996))
|
||||||
|
- Sync Filter now taking in account homeserver capabilities to not pass unsupported parameters.
|
||||||
|
Sync Filter is now configured by providing SyncFilterBuilder class instance, instead of Filter to identify Filter changes related to homeserver capabilities ([#7626](https://github.com/vector-im/element-android/issues/7626))
|
||||||
|
|
||||||
|
Other changes
|
||||||
|
-------------
|
||||||
|
- Remove usage of Buildkite. ([#7583](https://github.com/vector-im/element-android/issues/7583))
|
||||||
|
- Better validation of edits ([#7594](https://github.com/vector-im/element-android/issues/7594))
|
||||||
|
|
||||||
|
|
||||||
Changes in Element v1.5.8 (2022-11-17)
|
Changes in Element v1.5.8 (2022-11-17)
|
||||||
======================================
|
======================================
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Add setting to allow disabling direct share
|
|
|
@ -1 +0,0 @@
|
||||||
Fix italic text is truncated when bubble mode and markdown is enabled
|
|
|
@ -1 +0,0 @@
|
||||||
Added support for read receipts in threads. Now user in a room can have multiple read receipts (one per thread + one in main thread + one without threadId)
|
|
1
changelog.d/7477.misc
Normal file
1
changelog.d/7477.misc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Add Z-Labs label for rich text editor and migrate to new label naming.
|
|
@ -1 +0,0 @@
|
||||||
[Device Manager] Toggle IP address visibility
|
|
|
@ -1 +0,0 @@
|
||||||
Missing translations on "replyTo" messages
|
|
|
@ -1 +0,0 @@
|
||||||
New implementation of the full screen mode for the Rich Text Editor.
|
|
|
@ -1 +0,0 @@
|
||||||
Remove usage of Buildkite.
|
|
|
@ -1 +0,0 @@
|
||||||
Better validation of edits
|
|
|
@ -1 +0,0 @@
|
||||||
ANR on session start when sending client info is enabled
|
|
|
@ -1 +0,0 @@
|
||||||
Make the plain text mode layout of the RTE more compact.
|
|
|
@ -1,2 +0,0 @@
|
||||||
Sync Filter now taking in account homeserver capabilities to not pass unsupported parameters.
|
|
||||||
Sync Filter is now configured by providing SyncFilterBuilder class instance, instead of Filter to identify Filter changes related to homeserver capabilities
|
|
|
@ -1 +0,0 @@
|
||||||
Voice Broadcast - Handle redaction of the state events on the listener and recorder sides
|
|
|
@ -1 +0,0 @@
|
||||||
Push notification for thread message is now shown correctly when user observes rooms main timeline
|
|
|
@ -1 +0,0 @@
|
||||||
Voice Broadcast - Fix playback stuck in buffering mode
|
|
|
@ -1 +0,0 @@
|
||||||
Voice Broadcast - Update the buffering display in the timeline
|
|
|
@ -1 +0,0 @@
|
||||||
Voice Broadcast - Remove voice messages related to a VB from the room attachments
|
|
1
changelog.d/7658.bugfix
Normal file
1
changelog.d/7658.bugfix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[Rich text editor] Fix design and spacing of rich text editor
|
1
changelog.d/7659.bugfix
Normal file
1
changelog.d/7659.bugfix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[Rich text editor] Fix keyboard closing after collapsing editor
|
3
changelog.d/7680.bugfix
Normal file
3
changelog.d/7680.bugfix
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
Rich Text Editor: fix several issues related to insets:
|
||||||
|
* Empty space displayed at the bottom when you don't have permissions to send messages into a room.
|
||||||
|
* Wrong insets being kept when you exit the room screen and the keyboard is displayed, then come back to it.
|
2
changelog.d/7683.bugfix
Normal file
2
changelog.d/7683.bugfix
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Fix crash in message composer when room is missing
|
||||||
|
|
1
changelog.d/7684.bugfix
Normal file
1
changelog.d/7684.bugfix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Fix crash when invalid homeserver url is entered.
|
|
@ -17,7 +17,7 @@ def markwon = "4.6.2"
|
||||||
def moshi = "1.14.0"
|
def moshi = "1.14.0"
|
||||||
def lifecycle = "2.5.1"
|
def lifecycle = "2.5.1"
|
||||||
def flowBinding = "1.2.0"
|
def flowBinding = "1.2.0"
|
||||||
def flipper = "0.174.0"
|
def flipper = "0.176.0"
|
||||||
def epoxy = "5.0.0"
|
def epoxy = "5.0.0"
|
||||||
def mavericks = "3.0.1"
|
def mavericks = "3.0.1"
|
||||||
def glide = "4.14.2"
|
def glide = "4.14.2"
|
||||||
|
@ -26,7 +26,7 @@ def jjwt = "0.11.5"
|
||||||
// Temporary version to unblock #6929. Once 0.16.0 is released we should use it, and revert
|
// Temporary version to unblock #6929. Once 0.16.0 is released we should use it, and revert
|
||||||
// the whole commit which set version 0.16.0-SNAPSHOT
|
// the whole commit which set version 0.16.0-SNAPSHOT
|
||||||
def vanniktechEmoji = "0.16.0-SNAPSHOT"
|
def vanniktechEmoji = "0.16.0-SNAPSHOT"
|
||||||
def sentry = "6.7.0"
|
def sentry = "6.9.0"
|
||||||
def fragment = "1.5.4"
|
def fragment = "1.5.4"
|
||||||
// Testing
|
// Testing
|
||||||
def mockk = "1.12.3" // We need to use 1.12.3 to have mocking in androidTest until a new version is released: https://github.com/mockk/mockk/issues/819
|
def mockk = "1.12.3" // We need to use 1.12.3 to have mocking in androidTest until a new version is released: https://github.com/mockk/mockk/issues/819
|
||||||
|
|
2
fastlane/metadata/android/en-US/changelogs/40105100.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40105100.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Main changes in this version: New implementation of the full screen mode for the Rich Text Editor and bugfixes.
|
||||||
|
Full changelog: https://github.com/vector-im/element-android/releases
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
5
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,6 +1,7 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionSha256Sum=db9c8211ed63f61f60292c69e80d89196f9eb36665e369e7f00ac4cc841c2219
|
distributionSha256Sum=312eb12875e1747e05c2f81a4789902d7e4ec5defbd1eefeaccc08acf096505d
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip
|
||||||
|
networkTimeout=10000
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|
12
gradlew
vendored
12
gradlew
vendored
|
@ -55,7 +55,7 @@
|
||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
@ -80,10 +80,10 @@ do
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
APP_NAME="Gradle"
|
|
||||||
APP_BASE_NAME=${0##*/}
|
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.
|
# 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"'
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
@ -143,12 +143,16 @@ fi
|
||||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
max*)
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC3045
|
||||||
MAX_FD=$( ulimit -H -n ) ||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
warn "Could not query maximum file descriptor limit"
|
warn "Could not query maximum file descriptor limit"
|
||||||
esac
|
esac
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
'' | soft) :;; #(
|
'' | soft) :;; #(
|
||||||
*)
|
*)
|
||||||
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC3045
|
||||||
ulimit -n "$MAX_FD" ||
|
ulimit -n "$MAX_FD" ||
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
esac
|
esac
|
||||||
|
|
1
gradlew.bat
vendored
1
gradlew.bat
vendored
|
@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%"=="" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,7 @@ android {
|
||||||
// that the app's state is completely cleared between tests.
|
// that the app's state is completely cleared between tests.
|
||||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||||
|
|
||||||
buildConfigField "String", "SDK_VERSION", "\"1.5.10\""
|
buildConfigField "String", "SDK_VERSION", "\"1.5.12\""
|
||||||
|
|
||||||
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
|
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
|
||||||
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
|
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
|
||||||
|
|
|
@ -19,32 +19,81 @@
|
||||||
# Ignore any error to not stop the script
|
# Ignore any error to not stop the script
|
||||||
set +e
|
set +e
|
||||||
|
|
||||||
printf "\n"
|
printf "\n================================================================================\n"
|
||||||
printf "================================================================================\n"
|
|
||||||
printf "| Welcome to the release script! |\n"
|
printf "| Welcome to the release script! |\n"
|
||||||
printf "================================================================================\n"
|
printf "================================================================================\n"
|
||||||
|
|
||||||
releaseScriptLocation="${RELEASE_SCRIPT_PATH}"
|
printf "Checking environment...\n"
|
||||||
|
envError=0
|
||||||
|
|
||||||
if [[ -z "${releaseScriptLocation}" ]]; then
|
# Path of the key store (it's a file)
|
||||||
printf "Fatal: RELEASE_SCRIPT_PATH is not defined in the environment. Please set to the path of your local file 'releaseElement2.sh'.\n"
|
keyStorePath="${ELEMENT_KEYSTORE_PATH}"
|
||||||
exit 1
|
if [[ -z "${keyStorePath}" ]]; then
|
||||||
|
printf "Fatal: ELEMENT_KEYSTORE_PATH is not defined in the environment.\n"
|
||||||
|
envError=1
|
||||||
|
fi
|
||||||
|
# Keystore password
|
||||||
|
keyStorePassword="${ELEMENT_KEYSTORE_PASSWORD}"
|
||||||
|
if [[ -z "${keyStorePassword}" ]]; then
|
||||||
|
printf "Fatal: ELEMENT_KEYSTORE_PASSWORD is not defined in the environment.\n"
|
||||||
|
envError=1
|
||||||
|
fi
|
||||||
|
# Key password
|
||||||
|
keyPassword="${ELEMENT_KEY_PASSWORD}"
|
||||||
|
if [[ -z "${keyPassword}" ]]; then
|
||||||
|
printf "Fatal: ELEMENT_KEY_PASSWORD is not defined in the environment.\n"
|
||||||
|
envError=1
|
||||||
|
fi
|
||||||
|
# GitHub token
|
||||||
|
gitHubToken="${ELEMENT_GITHUB_TOKEN}"
|
||||||
|
if [[ -z "${gitHubToken}" ]]; then
|
||||||
|
printf "Fatal: ELEMENT_GITHUB_TOKEN is not defined in the environment.\n"
|
||||||
|
envError=1
|
||||||
|
fi
|
||||||
|
# Android home
|
||||||
|
androidHome="${ANDROID_HOME}"
|
||||||
|
if [[ -z "${androidHome}" ]]; then
|
||||||
|
printf "Fatal: ANDROID_HOME is not defined in the environment.\n"
|
||||||
|
envError=1
|
||||||
|
fi
|
||||||
|
# @elementbot:matrix.org matrix token / Not mandatory
|
||||||
|
elementBotToken="${ELEMENT_BOT_MATRIX_TOKEN}"
|
||||||
|
if [[ -z "${elementBotToken}" ]]; then
|
||||||
|
printf "Warning: ELEMENT_BOT_MATRIX_TOKEN is not defined in the environment.\n"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
releaseScriptFullPath="${releaseScriptLocation}/releaseElement2.sh"
|
if [ ${envError} == 1 ]; then
|
||||||
|
|
||||||
if [[ ! -f ${releaseScriptFullPath} ]]; then
|
|
||||||
printf "Fatal: release script not found at ${releaseScriptFullPath}.\n"
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
buildToolsVersion="30.0.2"
|
||||||
|
buildToolsPath="${androidHome}/build-tools/${buildToolsVersion}"
|
||||||
|
|
||||||
|
if [[ ! -d ${buildToolsPath} ]]; then
|
||||||
|
printf "Fatal: ${buildToolsPath} folder not found, ensure that you have installed the SDK version ${buildToolsVersion}.\n"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if git flow is enabled
|
||||||
|
git flow config >/dev/null 2>&1
|
||||||
|
if [[ $? == 0 ]]
|
||||||
|
then
|
||||||
|
printf "Git flow is initialized\n"
|
||||||
|
else
|
||||||
|
printf "Git flow is not initialized. Initializing...\n"
|
||||||
|
# All default value, just set 'v' for tag prefix
|
||||||
|
git flow init -d -t 'v'
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "OK\n"
|
||||||
|
|
||||||
|
printf "\n================================================================================\n"
|
||||||
# Guessing version to propose a default version
|
# Guessing version to propose a default version
|
||||||
versionMajorCandidate=`grep "ext.versionMajor" ./vector-app/build.gradle | cut -d " " -f3`
|
versionMajorCandidate=`grep "ext.versionMajor" ./vector-app/build.gradle | cut -d " " -f3`
|
||||||
versionMinorCandidate=`grep "ext.versionMinor" ./vector-app/build.gradle | cut -d " " -f3`
|
versionMinorCandidate=`grep "ext.versionMinor" ./vector-app/build.gradle | cut -d " " -f3`
|
||||||
versionPatchCandidate=`grep "ext.versionPatch" ./vector-app/build.gradle | cut -d " " -f3`
|
versionPatchCandidate=`grep "ext.versionPatch" ./vector-app/build.gradle | cut -d " " -f3`
|
||||||
versionCandidate="${versionMajorCandidate}.${versionMinorCandidate}.${versionPatchCandidate}"
|
versionCandidate="${versionMajorCandidate}.${versionMinorCandidate}.${versionPatchCandidate}"
|
||||||
|
|
||||||
printf "\n"
|
|
||||||
read -p "Please enter the release version (example: ${versionCandidate}). Just press enter if ${versionCandidate} is correct. " version
|
read -p "Please enter the release version (example: ${versionCandidate}). Just press enter if ${versionCandidate} is correct. " version
|
||||||
version=${version:-${versionCandidate}}
|
version=${version:-${versionCandidate}}
|
||||||
|
|
||||||
|
@ -77,7 +126,7 @@ fi
|
||||||
cp ./vector-app/build.gradle ./vector-app/build.gradle.bak
|
cp ./vector-app/build.gradle ./vector-app/build.gradle.bak
|
||||||
sed "s/ext.versionMajor = .*/ext.versionMajor = ${versionMajor}/" ./vector-app/build.gradle.bak > ./vector-app/build.gradle
|
sed "s/ext.versionMajor = .*/ext.versionMajor = ${versionMajor}/" ./vector-app/build.gradle.bak > ./vector-app/build.gradle
|
||||||
sed "s/ext.versionMinor = .*/ext.versionMinor = ${versionMinor}/" ./vector-app/build.gradle > ./vector-app/build.gradle.bak
|
sed "s/ext.versionMinor = .*/ext.versionMinor = ${versionMinor}/" ./vector-app/build.gradle > ./vector-app/build.gradle.bak
|
||||||
sed "s/ext.versionPatch = .*/ext.versionPatch = ${patchVersion}/" ./vector-app/build.gradle.bak > ./vector-app/build.gradle
|
sed "s/ext.versionPatch = .*/ext.versionPatch = ${versionPatch}/" ./vector-app/build.gradle.bak > ./vector-app/build.gradle
|
||||||
rm ./vector-app/build.gradle.bak
|
rm ./vector-app/build.gradle.bak
|
||||||
cp ./matrix-sdk-android/build.gradle ./matrix-sdk-android/build.gradle.bak
|
cp ./matrix-sdk-android/build.gradle ./matrix-sdk-android/build.gradle.bak
|
||||||
sed "s/\"SDK_VERSION\", .*$/\"SDK_VERSION\", \"\\\\\"${version}\\\\\"\"/" ./matrix-sdk-android/build.gradle.bak > ./matrix-sdk-android/build.gradle
|
sed "s/\"SDK_VERSION\", .*$/\"SDK_VERSION\", \"\\\\\"${version}\\\\\"\"/" ./matrix-sdk-android/build.gradle.bak > ./matrix-sdk-android/build.gradle
|
||||||
|
@ -155,6 +204,7 @@ fastlanePathFile="./fastlane/metadata/android/en-US/changelogs/${fastlaneFile}"
|
||||||
printf "Main changes in this version: TODO.\nFull changelog: https://github.com/vector-im/element-android/releases" > ${fastlanePathFile}
|
printf "Main changes in this version: TODO.\nFull changelog: https://github.com/vector-im/element-android/releases" > ${fastlanePathFile}
|
||||||
|
|
||||||
read -p "I have created the file ${fastlanePathFile}, please edit it and press enter when it's done."
|
read -p "I have created the file ${fastlanePathFile}, please edit it and press enter when it's done."
|
||||||
|
git add ${fastlanePathFile}
|
||||||
git commit -a -m "Adding fastlane file for version ${version}"
|
git commit -a -m "Adding fastlane file for version ${version}"
|
||||||
|
|
||||||
printf "\n================================================================================\n"
|
printf "\n================================================================================\n"
|
||||||
|
@ -184,7 +234,6 @@ git checkout develop
|
||||||
# Set next version
|
# Set next version
|
||||||
printf "\n================================================================================\n"
|
printf "\n================================================================================\n"
|
||||||
printf "Setting next version on file './vector-app/build.gradle'...\n"
|
printf "Setting next version on file './vector-app/build.gradle'...\n"
|
||||||
nextPatchVersion=$((versionPatch + 2))
|
|
||||||
cp ./vector-app/build.gradle ./vector-app/build.gradle.bak
|
cp ./vector-app/build.gradle ./vector-app/build.gradle.bak
|
||||||
sed "s/ext.versionPatch = .*/ext.versionPatch = ${nextPatchVersion}/" ./vector-app/build.gradle.bak > ./vector-app/build.gradle
|
sed "s/ext.versionPatch = .*/ext.versionPatch = ${nextPatchVersion}/" ./vector-app/build.gradle.bak > ./vector-app/build.gradle
|
||||||
rm ./vector-app/build.gradle.bak
|
rm ./vector-app/build.gradle.bak
|
||||||
|
@ -214,17 +263,93 @@ else
|
||||||
fi
|
fi
|
||||||
|
|
||||||
printf "\n================================================================================\n"
|
printf "\n================================================================================\n"
|
||||||
read -p "Wait for the GitHub action https://github.com/vector-im/element-android/actions/workflows/build.yml?query=branch%3Amain to build the 'main' branch. Press enter when it's done."
|
printf "Wait for the GitHub action https://github.com/vector-im/element-android/actions/workflows/build.yml?query=branch%3Amain to build the 'main' branch.\n"
|
||||||
|
read -p "After GHA is finished, please enter the artifact URL (for 'vector-gplay-release-unsigned'): " artifactUrl
|
||||||
|
|
||||||
printf "\n================================================================================\n"
|
printf "\n================================================================================\n"
|
||||||
printf "Running the release script...\n"
|
printf "Downloading the artifact...\n"
|
||||||
cd ${releaseScriptLocation}
|
|
||||||
${releaseScriptFullPath} "v${version}"
|
# Download files
|
||||||
cd -
|
targetPath="./tmp/Element/${version}"
|
||||||
|
|
||||||
|
# Ignore error
|
||||||
|
set +e
|
||||||
|
|
||||||
|
python3 ./tools/release/download_github_artifacts.py \
|
||||||
|
--token ${gitHubToken} \
|
||||||
|
--artifactUrl ${artifactUrl} \
|
||||||
|
--directory ${targetPath} \
|
||||||
|
--ignoreErrors
|
||||||
|
|
||||||
|
# Do not ignore error
|
||||||
|
set -e
|
||||||
|
|
||||||
printf "\n================================================================================\n"
|
printf "\n================================================================================\n"
|
||||||
apkPath="${releaseScriptLocation}/Element/v${version}/vector-gplay-arm64-v8a-release-signed.apk"
|
printf "Unzipping the artifact...\n"
|
||||||
printf "Installing apk on a real device...\n"
|
|
||||||
|
unzip ${targetPath}/vector-gplay-release-unsigned.zip -d ${targetPath}
|
||||||
|
|
||||||
|
# Flatten folder hierarchy
|
||||||
|
mv ${targetPath}/gplay/release/* ${targetPath}
|
||||||
|
rm -rf ${targetPath}/gplay
|
||||||
|
|
||||||
|
printf "\n================================================================================\n"
|
||||||
|
printf "Signing the APKs...\n"
|
||||||
|
|
||||||
|
cp ${targetPath}/vector-gplay-arm64-v8a-release-unsigned.apk \
|
||||||
|
${targetPath}/vector-gplay-arm64-v8a-release-signed.apk
|
||||||
|
./tools/release/sign_apk_unsafe.sh \
|
||||||
|
${keyStorePath} \
|
||||||
|
${targetPath}/vector-gplay-arm64-v8a-release-signed.apk \
|
||||||
|
${keyStorePassword} \
|
||||||
|
${keyPassword}
|
||||||
|
|
||||||
|
cp ${targetPath}/vector-gplay-armeabi-v7a-release-unsigned.apk \
|
||||||
|
${targetPath}/vector-gplay-armeabi-v7a-release-signed.apk
|
||||||
|
./tools/release/sign_apk_unsafe.sh \
|
||||||
|
${keyStorePath} \
|
||||||
|
${targetPath}/vector-gplay-armeabi-v7a-release-signed.apk \
|
||||||
|
${keyStorePassword} \
|
||||||
|
${keyPassword}
|
||||||
|
|
||||||
|
cp ${targetPath}/vector-gplay-x86-release-unsigned.apk \
|
||||||
|
${targetPath}/vector-gplay-x86-release-signed.apk
|
||||||
|
./tools/release/sign_apk_unsafe.sh \
|
||||||
|
${keyStorePath} \
|
||||||
|
${targetPath}/vector-gplay-x86-release-signed.apk \
|
||||||
|
${keyStorePassword} \
|
||||||
|
${keyPassword}
|
||||||
|
|
||||||
|
cp ${targetPath}/vector-gplay-x86_64-release-unsigned.apk \
|
||||||
|
${targetPath}/vector-gplay-x86_64-release-signed.apk
|
||||||
|
./tools/release/sign_apk_unsafe.sh \
|
||||||
|
${keyStorePath} \
|
||||||
|
${targetPath}/vector-gplay-x86_64-release-signed.apk \
|
||||||
|
${keyStorePassword} \
|
||||||
|
${keyPassword}
|
||||||
|
|
||||||
|
# Ref: https://docs.fastlane.tools/getting-started/android/beta-deployment/#uploading-your-app
|
||||||
|
# set SUPPLY_APK_PATHS="${targetPath}/vector-gplay-arm64-v8a-release-unsigned.apk,${targetPath}/vector-gplay-armeabi-v7a-release-unsigned.apk,${targetPath}/vector-gplay-x86-release-unsigned.apk,${targetPath}/vector-gplay-x86_64-release-unsigned.apk"
|
||||||
|
#
|
||||||
|
# ./fastlane beta
|
||||||
|
|
||||||
|
printf "\n================================================================================\n"
|
||||||
|
printf "Please check the information below:\n"
|
||||||
|
|
||||||
|
printf "File vector-gplay-arm64-v8a-release-signed.apk:\n"
|
||||||
|
${buildToolsPath}/aapt dump badging ${targetPath}/vector-gplay-arm64-v8a-release-signed.apk | grep package
|
||||||
|
printf "File vector-gplay-armeabi-v7a-release-signed.apk:\n"
|
||||||
|
${buildToolsPath}/aapt dump badging ${targetPath}/vector-gplay-armeabi-v7a-release-signed.apk | grep package
|
||||||
|
printf "File vector-gplay-x86-release-signed.apk:\n"
|
||||||
|
${buildToolsPath}/aapt dump badging ${targetPath}/vector-gplay-x86-release-signed.apk | grep package
|
||||||
|
printf "File vector-gplay-x86_64-release-signed.apk:\n"
|
||||||
|
${buildToolsPath}/aapt dump badging ${targetPath}/vector-gplay-x86_64-release-signed.apk | grep package
|
||||||
|
|
||||||
|
read -p "\nDoes it look correct? Press enter when it's done."
|
||||||
|
|
||||||
|
printf "\n================================================================================\n"
|
||||||
|
read -p "Installing apk on a real device, press enter when a real device is connected. "
|
||||||
|
apkPath="${targetPath}/vector-gplay-arm64-v8a-release-signed.apk"
|
||||||
adb -d install ${apkPath}
|
adb -d install ${apkPath}
|
||||||
|
|
||||||
read -p "Please run the APK on your phone to check that the upgrade went well (no init sync, etc.). Press enter when it's done."
|
read -p "Please run the APK on your phone to check that the upgrade went well (no init sync, etc.). Press enter when it's done."
|
||||||
|
@ -234,9 +359,25 @@ read -p "Create the release on gitHub from the tag https://github.com/vector-im/
|
||||||
read -p "Add the 4 signed APKs to the GitHub release. Press enter when it's done."
|
read -p "Add the 4 signed APKs to the GitHub release. Press enter when it's done."
|
||||||
|
|
||||||
printf "\n================================================================================\n"
|
printf "\n================================================================================\n"
|
||||||
printf "Ping the Android Internal room. Here is an example of message which can be sent:\n\n"
|
printf "Message for the Android internal room:\n\n"
|
||||||
printf "@room Element Android ${version} is ready to be tested. You can get if from https://github.com/vector-im/element-android/releases/tag/v${version}. Please report any feedback here. Thanks!\n\n"
|
message="@room Element Android ${version} is ready to be tested. You can get if from https://github.com/vector-im/element-android/releases/tag/v${version}. Please report any feedback here. Thanks!"
|
||||||
read -p "Press enter when it's done."
|
printf "${message}\n\n"
|
||||||
|
|
||||||
|
if [[ -z "${elementBotToken}" ]]; then
|
||||||
|
read -p "ELEMENT_BOT_MATRIX_TOKEN is not defined in the environment. Cannot send the message for you. Please send it manually, and press enter when it's done "
|
||||||
|
else
|
||||||
|
read -p "Send this message to the room (yes/no) default to yes? " doSend
|
||||||
|
doSend=${doSend:-yes}
|
||||||
|
if [ ${doSend} == "yes" ]; then
|
||||||
|
printf "Sending message...\n"
|
||||||
|
transactionId=`openssl rand -hex 16`
|
||||||
|
# Element Android internal
|
||||||
|
matrixRoomId="!LiSLXinTDCsepePiYW:matrix.org"
|
||||||
|
curl -X PUT --data $"{\"msgtype\":\"m.text\",\"body\":\"${message}\"}" -H "Authorization: Bearer ${elementBotToken}" https://matrix-client.matrix.org/_matrix/client/r0/rooms/${matrixRoomId}/send/m.room.message/\$local.${transactionId}
|
||||||
|
else
|
||||||
|
printf "Message not sent, please send it manually!\n"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
printf "\n================================================================================\n"
|
printf "\n================================================================================\n"
|
||||||
printf "Congratulation! Kudos for using this script! Have a nice day!\n"
|
printf "Congratulation! Kudos for using this script! Have a nice day!\n"
|
||||||
|
|
|
@ -37,7 +37,7 @@ ext.versionMinor = 5
|
||||||
// Note: even values are reserved for regular release, odd values for hotfix release.
|
// 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
|
// When creating a hotfix, you should decrease the value, since the current value
|
||||||
// is the value for the next regular release.
|
// is the value for the next regular release.
|
||||||
ext.versionPatch = 10
|
ext.versionPatch = 12
|
||||||
|
|
||||||
static def getGitTimestamp() {
|
static def getGitTimestamp() {
|
||||||
def cmd = 'git show -s --format=%ct'
|
def cmd = 'git show -s --format=%ct'
|
||||||
|
@ -440,13 +440,13 @@ dependencies {
|
||||||
// Plant Timber tree for test
|
// Plant Timber tree for test
|
||||||
androidTestImplementation libs.tests.timberJunitRule
|
androidTestImplementation libs.tests.timberJunitRule
|
||||||
// "The one who serves a great Espresso"
|
// "The one who serves a great Espresso"
|
||||||
androidTestImplementation('com.adevinta.android:barista:4.2.0') {
|
androidTestImplementation('com.adevinta.android:barista:4.3.0') {
|
||||||
exclude group: 'org.jetbrains.kotlin'
|
exclude group: 'org.jetbrains.kotlin'
|
||||||
}
|
}
|
||||||
androidTestImplementation libs.mockk.mockkAndroid
|
androidTestImplementation libs.mockk.mockkAndroid
|
||||||
androidTestUtil libs.androidx.orchestrator
|
androidTestUtil libs.androidx.orchestrator
|
||||||
androidTestImplementation libs.androidx.fragmentTesting
|
androidTestImplementation libs.androidx.fragmentTesting
|
||||||
androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.7.21"
|
androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.7.22"
|
||||||
debugImplementation libs.androidx.fragmentTesting
|
debugImplementation libs.androidx.fragmentTesting
|
||||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
|
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
|
||||||
}
|
}
|
||||||
|
|
|
@ -328,11 +328,11 @@ dependencies {
|
||||||
// Plant Timber tree for test
|
// Plant Timber tree for test
|
||||||
androidTestImplementation libs.tests.timberJunitRule
|
androidTestImplementation libs.tests.timberJunitRule
|
||||||
// "The one who serves a great Espresso"
|
// "The one who serves a great Espresso"
|
||||||
androidTestImplementation('com.adevinta.android:barista:4.2.0') {
|
androidTestImplementation('com.adevinta.android:barista:4.3.0') {
|
||||||
exclude group: 'org.jetbrains.kotlin'
|
exclude group: 'org.jetbrains.kotlin'
|
||||||
}
|
}
|
||||||
androidTestImplementation libs.mockk.mockkAndroid
|
androidTestImplementation libs.mockk.mockkAndroid
|
||||||
androidTestUtil libs.androidx.orchestrator
|
androidTestUtil libs.androidx.orchestrator
|
||||||
debugImplementation libs.androidx.fragmentTesting
|
debugImplementation libs.androidx.fragmentTesting
|
||||||
androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.7.21"
|
androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.7.22"
|
||||||
}
|
}
|
||||||
|
|
|
@ -608,26 +608,33 @@ class ExpandingBottomSheetBehavior<V : View> : CoordinatorLayout.Behavior<V> {
|
||||||
initialPaddingBottom = view.paddingBottom
|
initialPaddingBottom = view.paddingBottom
|
||||||
|
|
||||||
// This should only be used to set initial insets and other edge cases where the insets can't be applied using an animation.
|
// This should only be used to set initial insets and other edge cases where the insets can't be applied using an animation.
|
||||||
var applyInsetsFromAnimation = false
|
var isAnimating = false
|
||||||
|
|
||||||
// This will animated inset changes, making them look a lot better. However, it won't update initial insets.
|
// This will animate inset changes, making them look a lot better. However, it won't update initial insets.
|
||||||
ViewCompat.setWindowInsetsAnimationCallback(view, object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) {
|
ViewCompat.setWindowInsetsAnimationCallback(view, object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) {
|
||||||
|
override fun onPrepare(animation: WindowInsetsAnimationCompat) {
|
||||||
|
isAnimating = true
|
||||||
|
}
|
||||||
|
|
||||||
override fun onProgress(insets: WindowInsetsCompat, runningAnimations: MutableList<WindowInsetsAnimationCompat>): WindowInsetsCompat {
|
override fun onProgress(insets: WindowInsetsCompat, runningAnimations: MutableList<WindowInsetsAnimationCompat>): WindowInsetsCompat {
|
||||||
return applyInsets(view, insets)
|
return if (isAnimating) {
|
||||||
|
applyInsets(view, insets)
|
||||||
|
} else {
|
||||||
|
insets
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEnd(animation: WindowInsetsAnimationCompat) {
|
override fun onEnd(animation: WindowInsetsAnimationCompat) {
|
||||||
applyInsetsFromAnimation = false
|
isAnimating = false
|
||||||
view.requestApplyInsets()
|
view.requestApplyInsets()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(view) { _: View, insets: WindowInsetsCompat ->
|
ViewCompat.setOnApplyWindowInsetsListener(view) { _: View, insets: WindowInsetsCompat ->
|
||||||
if (!applyInsetsFromAnimation) {
|
if (isAnimating) {
|
||||||
applyInsetsFromAnimation = true
|
|
||||||
applyInsets(view, insets)
|
|
||||||
} else {
|
|
||||||
insets
|
insets
|
||||||
|
} else {
|
||||||
|
applyInsets(view, insets)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -255,7 +255,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
|
||||||
) { mainState, messageComposerState, attachmentState ->
|
) { mainState, messageComposerState, attachmentState ->
|
||||||
if (mainState.tombstoneEvent != null) return@withState
|
if (mainState.tombstoneEvent != null) return@withState
|
||||||
|
|
||||||
(composer as? View)?.isInvisible = !messageComposerState.isComposerVisible
|
(composer as? View)?.isVisible = messageComposerState.isComposerVisible
|
||||||
composer.sendButton.isInvisible = !messageComposerState.isSendButtonVisible
|
composer.sendButton.isInvisible = !messageComposerState.isSendButtonVisible
|
||||||
(composer as? RichTextComposerLayout)?.isTextFormattingEnabled = attachmentState.isTextFormattingEnabled
|
(composer as? RichTextComposerLayout)?.isTextFormattingEnabled = attachmentState.isTextFormattingEnabled
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,7 @@ 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.events.model.toModel
|
||||||
import org.matrix.android.sdk.api.session.getRoom
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
import org.matrix.android.sdk.api.session.getRoomSummary
|
import org.matrix.android.sdk.api.session.getRoomSummary
|
||||||
|
import org.matrix.android.sdk.api.session.room.Room
|
||||||
import org.matrix.android.sdk.api.session.room.getStateEvent
|
import org.matrix.android.sdk.api.session.room.getStateEvent
|
||||||
import org.matrix.android.sdk.api.session.room.getTimelineEvent
|
import org.matrix.android.sdk.api.session.room.getTimelineEvent
|
||||||
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
||||||
|
@ -89,39 +90,44 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
private val voiceBroadcastHelper: VoiceBroadcastHelper,
|
private val voiceBroadcastHelper: VoiceBroadcastHelper,
|
||||||
) : VectorViewModel<MessageComposerViewState, MessageComposerAction, MessageComposerViewEvents>(initialState) {
|
) : VectorViewModel<MessageComposerViewState, MessageComposerAction, MessageComposerViewEvents>(initialState) {
|
||||||
|
|
||||||
private val room = session.getRoom(initialState.roomId)!!
|
private val room = session.getRoom(initialState.roomId)
|
||||||
|
|
||||||
// Keep it out of state to avoid invalidate being called
|
// Keep it out of state to avoid invalidate being called
|
||||||
private var currentComposerText: CharSequence = ""
|
private var currentComposerText: CharSequence = ""
|
||||||
|
|
||||||
init {
|
init {
|
||||||
loadDraftIfAny()
|
if (room != null) {
|
||||||
observePowerLevelAndEncryption()
|
loadDraftIfAny(room)
|
||||||
observeVoiceBroadcast()
|
observePowerLevelAndEncryption(room)
|
||||||
subscribeToStateInternal()
|
observeVoiceBroadcast(room)
|
||||||
|
subscribeToStateInternal()
|
||||||
|
} else {
|
||||||
|
onRoomError()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handle(action: MessageComposerAction) {
|
override fun handle(action: MessageComposerAction) {
|
||||||
|
val room = this.room ?: return
|
||||||
when (action) {
|
when (action) {
|
||||||
is MessageComposerAction.EnterEditMode -> handleEnterEditMode(action)
|
is MessageComposerAction.EnterEditMode -> handleEnterEditMode(room, action)
|
||||||
is MessageComposerAction.EnterQuoteMode -> handleEnterQuoteMode(action)
|
is MessageComposerAction.EnterQuoteMode -> handleEnterQuoteMode(room, action)
|
||||||
is MessageComposerAction.EnterRegularMode -> handleEnterRegularMode(action)
|
is MessageComposerAction.EnterRegularMode -> handleEnterRegularMode(action)
|
||||||
is MessageComposerAction.EnterReplyMode -> handleEnterReplyMode(action)
|
is MessageComposerAction.EnterReplyMode -> handleEnterReplyMode(room, action)
|
||||||
is MessageComposerAction.SendMessage -> handleSendMessage(action)
|
is MessageComposerAction.SendMessage -> handleSendMessage(room, action)
|
||||||
is MessageComposerAction.UserIsTyping -> handleUserIsTyping(action)
|
is MessageComposerAction.UserIsTyping -> handleUserIsTyping(room, action)
|
||||||
is MessageComposerAction.OnTextChanged -> handleOnTextChanged(action)
|
is MessageComposerAction.OnTextChanged -> handleOnTextChanged(action)
|
||||||
is MessageComposerAction.OnVoiceRecordingUiStateChanged -> handleOnVoiceRecordingUiStateChanged(action)
|
is MessageComposerAction.OnVoiceRecordingUiStateChanged -> handleOnVoiceRecordingUiStateChanged(action)
|
||||||
is MessageComposerAction.StartRecordingVoiceMessage -> handleStartRecordingVoiceMessage()
|
is MessageComposerAction.StartRecordingVoiceMessage -> handleStartRecordingVoiceMessage(room)
|
||||||
is MessageComposerAction.EndRecordingVoiceMessage -> handleEndRecordingVoiceMessage(action.isCancelled, action.rootThreadEventId)
|
is MessageComposerAction.EndRecordingVoiceMessage -> handleEndRecordingVoiceMessage(room, action.isCancelled, action.rootThreadEventId)
|
||||||
is MessageComposerAction.PlayOrPauseVoicePlayback -> handlePlayOrPauseVoicePlayback(action)
|
is MessageComposerAction.PlayOrPauseVoicePlayback -> handlePlayOrPauseVoicePlayback(action)
|
||||||
MessageComposerAction.PauseRecordingVoiceMessage -> handlePauseRecordingVoiceMessage()
|
MessageComposerAction.PauseRecordingVoiceMessage -> handlePauseRecordingVoiceMessage()
|
||||||
MessageComposerAction.PlayOrPauseRecordingPlayback -> handlePlayOrPauseRecordingPlayback()
|
MessageComposerAction.PlayOrPauseRecordingPlayback -> handlePlayOrPauseRecordingPlayback()
|
||||||
is MessageComposerAction.InitializeVoiceRecorder -> handleInitializeVoiceRecorder(action.attachmentData)
|
is MessageComposerAction.InitializeVoiceRecorder -> handleInitializeVoiceRecorder(room, action.attachmentData)
|
||||||
is MessageComposerAction.OnEntersBackground -> handleEntersBackground(action.composerText)
|
is MessageComposerAction.OnEntersBackground -> handleEntersBackground(room, action.composerText)
|
||||||
is MessageComposerAction.VoiceWaveformTouchedUp -> handleVoiceWaveformTouchedUp(action)
|
is MessageComposerAction.VoiceWaveformTouchedUp -> handleVoiceWaveformTouchedUp(action)
|
||||||
is MessageComposerAction.VoiceWaveformMovedTo -> handleVoiceWaveformMovedTo(action)
|
is MessageComposerAction.VoiceWaveformMovedTo -> handleVoiceWaveformMovedTo(action)
|
||||||
is MessageComposerAction.AudioSeekBarMovedTo -> handleAudioSeekBarMovedTo(action)
|
is MessageComposerAction.AudioSeekBarMovedTo -> handleAudioSeekBarMovedTo(action)
|
||||||
is MessageComposerAction.SlashCommandConfirmed -> handleSlashCommandConfirmed(action)
|
is MessageComposerAction.SlashCommandConfirmed -> handleSlashCommandConfirmed(room, action)
|
||||||
is MessageComposerAction.InsertUserDisplayName -> handleInsertUserDisplayName(action)
|
is MessageComposerAction.InsertUserDisplayName -> handleInsertUserDisplayName(action)
|
||||||
is MessageComposerAction.SetFullScreen -> handleSetFullScreen(action)
|
is MessageComposerAction.SetFullScreen -> handleSetFullScreen(action)
|
||||||
}
|
}
|
||||||
|
@ -157,7 +163,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
copy(sendMode = SendMode.Regular(currentComposerText, action.fromSharing))
|
copy(sendMode = SendMode.Regular(currentComposerText, action.fromSharing))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleEnterEditMode(action: MessageComposerAction.EnterEditMode) {
|
private fun handleEnterEditMode(room: Room, action: MessageComposerAction.EnterEditMode) {
|
||||||
room.getTimelineEvent(action.eventId)?.let { timelineEvent ->
|
room.getTimelineEvent(action.eventId)?.let { timelineEvent ->
|
||||||
val formatted = vectorPreferences.isRichTextEditorEnabled()
|
val formatted = vectorPreferences.isRichTextEditorEnabled()
|
||||||
setState { copy(sendMode = SendMode.Edit(timelineEvent, timelineEvent.getTextEditableContent(formatted))) }
|
setState { copy(sendMode = SendMode.Edit(timelineEvent, timelineEvent.getTextEditableContent(formatted))) }
|
||||||
|
@ -168,7 +174,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
setState { copy(isFullScreen = action.isFullScreen) }
|
setState { copy(isFullScreen = action.isFullScreen) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observePowerLevelAndEncryption() {
|
private fun observePowerLevelAndEncryption(room: Room) {
|
||||||
combine(
|
combine(
|
||||||
PowerLevelsFlowFactory(room).createFlow(),
|
PowerLevelsFlowFactory(room).createFlow(),
|
||||||
room.flow().liveRoomSummary().unwrap()
|
room.flow().liveRoomSummary().unwrap()
|
||||||
|
@ -194,7 +200,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeVoiceBroadcast() {
|
private fun observeVoiceBroadcast(room: Room) {
|
||||||
room.stateService().getStateEventLive(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId))
|
room.stateService().getStateEventLive(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId))
|
||||||
.asFlow()
|
.asFlow()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -204,19 +210,19 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleEnterQuoteMode(action: MessageComposerAction.EnterQuoteMode) {
|
private fun handleEnterQuoteMode(room: Room, action: MessageComposerAction.EnterQuoteMode) {
|
||||||
room.getTimelineEvent(action.eventId)?.let { timelineEvent ->
|
room.getTimelineEvent(action.eventId)?.let { timelineEvent ->
|
||||||
setState { copy(sendMode = SendMode.Quote(timelineEvent, currentComposerText)) }
|
setState { copy(sendMode = SendMode.Quote(timelineEvent, currentComposerText)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleEnterReplyMode(action: MessageComposerAction.EnterReplyMode) {
|
private fun handleEnterReplyMode(room: Room, action: MessageComposerAction.EnterReplyMode) {
|
||||||
room.getTimelineEvent(action.eventId)?.let { timelineEvent ->
|
room.getTimelineEvent(action.eventId)?.let { timelineEvent ->
|
||||||
setState { copy(sendMode = SendMode.Reply(timelineEvent, currentComposerText)) }
|
setState { copy(sendMode = SendMode.Reply(timelineEvent, currentComposerText)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSendMessage(action: MessageComposerAction.SendMessage) {
|
private fun handleSendMessage(room: Room, action: MessageComposerAction.SendMessage) {
|
||||||
withState { state ->
|
withState { state ->
|
||||||
analyticsTracker.capture(state.toAnalyticsComposer()).also {
|
analyticsTracker.capture(state.toAnalyticsComposer()).also {
|
||||||
setState { copy(startsThread = false) }
|
setState { copy(startsThread = false) }
|
||||||
|
@ -246,7 +252,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
_viewEvents.post(MessageComposerViewEvents.MessageSent)
|
_viewEvents.post(MessageComposerViewEvents.MessageSent)
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.ErrorSyntax -> {
|
is ParsedCommand.ErrorSyntax -> {
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandError(parsedCommand.command))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandError(parsedCommand.command))
|
||||||
|
@ -272,7 +278,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
room.sendService().sendTextMessage(parsedCommand.message, autoMarkdown = false)
|
room.sendService().sendTextMessage(parsedCommand.message, autoMarkdown = false)
|
||||||
}
|
}
|
||||||
_viewEvents.post(MessageComposerViewEvents.MessageSent)
|
_viewEvents.post(MessageComposerViewEvents.MessageSent)
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.SendFormattedText -> {
|
is ParsedCommand.SendFormattedText -> {
|
||||||
// Send the text message to the room, without markdown
|
// Send the text message to the room, without markdown
|
||||||
|
@ -290,23 +296,23 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_viewEvents.post(MessageComposerViewEvents.MessageSent)
|
_viewEvents.post(MessageComposerViewEvents.MessageSent)
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.ChangeRoomName -> {
|
is ParsedCommand.ChangeRoomName -> {
|
||||||
handleChangeRoomNameSlashCommand(parsedCommand)
|
handleChangeRoomNameSlashCommand(room, parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.Invite -> {
|
is ParsedCommand.Invite -> {
|
||||||
handleInviteSlashCommand(parsedCommand)
|
handleInviteSlashCommand(room, parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.Invite3Pid -> {
|
is ParsedCommand.Invite3Pid -> {
|
||||||
handleInvite3pidSlashCommand(parsedCommand)
|
handleInvite3pidSlashCommand(room, parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.SetUserPowerLevel -> {
|
is ParsedCommand.SetUserPowerLevel -> {
|
||||||
handleSetUserPowerLevel(parsedCommand)
|
handleSetUserPowerLevel(room, parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.DevTools -> {
|
is ParsedCommand.DevTools -> {
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.ClearScalarToken -> {
|
is ParsedCommand.ClearScalarToken -> {
|
||||||
// TODO
|
// TODO
|
||||||
|
@ -315,29 +321,29 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
is ParsedCommand.SetMarkdown -> {
|
is ParsedCommand.SetMarkdown -> {
|
||||||
vectorPreferences.setMarkdownEnabled(parsedCommand.enable)
|
vectorPreferences.setMarkdownEnabled(parsedCommand.enable)
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.BanUser -> {
|
is ParsedCommand.BanUser -> {
|
||||||
handleBanSlashCommand(parsedCommand)
|
handleBanSlashCommand(room, parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.UnbanUser -> {
|
is ParsedCommand.UnbanUser -> {
|
||||||
handleUnbanSlashCommand(parsedCommand)
|
handleUnbanSlashCommand(room, parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.IgnoreUser -> {
|
is ParsedCommand.IgnoreUser -> {
|
||||||
handleIgnoreSlashCommand(parsedCommand)
|
handleIgnoreSlashCommand(room, parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.UnignoreUser -> {
|
is ParsedCommand.UnignoreUser -> {
|
||||||
handleUnignoreSlashCommand(parsedCommand)
|
handleUnignoreSlashCommand(parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.RemoveUser -> {
|
is ParsedCommand.RemoveUser -> {
|
||||||
handleRemoveSlashCommand(parsedCommand)
|
handleRemoveSlashCommand(room, parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.JoinRoom -> {
|
is ParsedCommand.JoinRoom -> {
|
||||||
handleJoinToAnotherRoomSlashCommand(parsedCommand)
|
handleJoinToAnotherRoomSlashCommand(parsedCommand)
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.PartRoom -> {
|
is ParsedCommand.PartRoom -> {
|
||||||
handlePartSlashCommand(parsedCommand)
|
handlePartSlashCommand(room, parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.SendEmote -> {
|
is ParsedCommand.SendEmote -> {
|
||||||
if (state.rootThreadEventId != null) {
|
if (state.rootThreadEventId != null) {
|
||||||
|
@ -355,7 +361,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.SendRainbow -> {
|
is ParsedCommand.SendRainbow -> {
|
||||||
val message = parsedCommand.message.toString()
|
val message = parsedCommand.message.toString()
|
||||||
|
@ -369,7 +375,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
room.sendService().sendFormattedTextMessage(message, rainbowGenerator.generate(message))
|
room.sendService().sendFormattedTextMessage(message, rainbowGenerator.generate(message))
|
||||||
}
|
}
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.SendRainbowEmote -> {
|
is ParsedCommand.SendRainbowEmote -> {
|
||||||
val message = parsedCommand.message.toString()
|
val message = parsedCommand.message.toString()
|
||||||
|
@ -385,7 +391,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.SendSpoiler -> {
|
is ParsedCommand.SendSpoiler -> {
|
||||||
val text = "[${stringProvider.getString(R.string.spoiler)}](${parsedCommand.message})"
|
val text = "[${stringProvider.getString(R.string.spoiler)}](${parsedCommand.message})"
|
||||||
|
@ -403,53 +409,53 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.SendShrug -> {
|
is ParsedCommand.SendShrug -> {
|
||||||
sendPrefixedMessage("¯\\_(ツ)_/¯", parsedCommand.message, state.rootThreadEventId)
|
sendPrefixedMessage(room, "¯\\_(ツ)_/¯", parsedCommand.message, state.rootThreadEventId)
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.SendLenny -> {
|
is ParsedCommand.SendLenny -> {
|
||||||
sendPrefixedMessage("( ͡° ͜ʖ ͡°)", parsedCommand.message, state.rootThreadEventId)
|
sendPrefixedMessage(room, "( ͡° ͜ʖ ͡°)", parsedCommand.message, state.rootThreadEventId)
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.SendTableFlip -> {
|
is ParsedCommand.SendTableFlip -> {
|
||||||
sendPrefixedMessage("(╯°□°)╯︵ ┻━┻", parsedCommand.message, state.rootThreadEventId)
|
sendPrefixedMessage(room, "(╯°□°)╯︵ ┻━┻", parsedCommand.message, state.rootThreadEventId)
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.SendChatEffect -> {
|
is ParsedCommand.SendChatEffect -> {
|
||||||
sendChatEffect(parsedCommand)
|
sendChatEffect(room, parsedCommand)
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.ChangeTopic -> {
|
is ParsedCommand.ChangeTopic -> {
|
||||||
handleChangeTopicSlashCommand(parsedCommand)
|
handleChangeTopicSlashCommand(room, parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.ChangeDisplayName -> {
|
is ParsedCommand.ChangeDisplayName -> {
|
||||||
handleChangeDisplayNameSlashCommand(parsedCommand)
|
handleChangeDisplayNameSlashCommand(room, parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.ChangeDisplayNameForRoom -> {
|
is ParsedCommand.ChangeDisplayNameForRoom -> {
|
||||||
handleChangeDisplayNameForRoomSlashCommand(parsedCommand)
|
handleChangeDisplayNameForRoomSlashCommand(room, parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.ChangeRoomAvatar -> {
|
is ParsedCommand.ChangeRoomAvatar -> {
|
||||||
handleChangeRoomAvatarSlashCommand(parsedCommand)
|
handleChangeRoomAvatarSlashCommand(room, parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.ChangeAvatarForRoom -> {
|
is ParsedCommand.ChangeAvatarForRoom -> {
|
||||||
handleChangeAvatarForRoomSlashCommand(parsedCommand)
|
handleChangeAvatarForRoomSlashCommand(room, parsedCommand)
|
||||||
}
|
}
|
||||||
is ParsedCommand.ShowUser -> {
|
is ParsedCommand.ShowUser -> {
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
handleWhoisSlashCommand(parsedCommand)
|
handleWhoisSlashCommand(parsedCommand)
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is ParsedCommand.DiscardSession -> {
|
is ParsedCommand.DiscardSession -> {
|
||||||
if (room.roomCryptoService().isEncrypted()) {
|
if (room.roomCryptoService().isEncrypted()) {
|
||||||
session.cryptoService().discardOutboundSession(room.roomId)
|
session.cryptoService().discardOutboundSession(room.roomId)
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
popDraft()
|
popDraft(room)
|
||||||
} else {
|
} else {
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
_viewEvents.post(
|
_viewEvents.post(
|
||||||
|
@ -474,7 +480,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
null,
|
null,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
popDraft()
|
popDraft(room)
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
|
||||||
|
@ -493,7 +499,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
null,
|
null,
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
popDraft()
|
popDraft(room)
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
|
||||||
|
@ -506,7 +512,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
session.spaceService().joinSpace(parsedCommand.spaceIdOrAlias)
|
session.spaceService().joinSpace(parsedCommand.spaceIdOrAlias)
|
||||||
popDraft()
|
popDraft(room)
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
|
||||||
|
@ -518,7 +524,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
session.roomService().leaveRoom(parsedCommand.roomId)
|
session.roomService().leaveRoom(parsedCommand.roomId)
|
||||||
popDraft()
|
popDraft(room)
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure))
|
||||||
|
@ -534,7 +540,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -583,7 +589,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_viewEvents.post(MessageComposerViewEvents.MessageSent)
|
_viewEvents.post(MessageComposerViewEvents.MessageSent)
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is SendMode.Quote -> {
|
is SendMode.Quote -> {
|
||||||
room.sendService().sendQuotedTextMessage(
|
room.sendService().sendQuotedTextMessage(
|
||||||
|
@ -594,7 +600,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
rootThreadEventId = state.rootThreadEventId
|
rootThreadEventId = state.rootThreadEventId
|
||||||
)
|
)
|
||||||
_viewEvents.post(MessageComposerViewEvents.MessageSent)
|
_viewEvents.post(MessageComposerViewEvents.MessageSent)
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is SendMode.Reply -> {
|
is SendMode.Reply -> {
|
||||||
val timelineEvent = state.sendMode.timelineEvent
|
val timelineEvent = state.sendMode.timelineEvent
|
||||||
|
@ -619,7 +625,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
|
|
||||||
_viewEvents.post(MessageComposerViewEvents.MessageSent)
|
_viewEvents.post(MessageComposerViewEvents.MessageSent)
|
||||||
popDraft()
|
popDraft(room)
|
||||||
}
|
}
|
||||||
is SendMode.Voice -> {
|
is SendMode.Voice -> {
|
||||||
// do nothing
|
// do nothing
|
||||||
|
@ -628,10 +634,10 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun popDraft() = withState {
|
private fun popDraft(room: Room) = withState {
|
||||||
if (it.sendMode is SendMode.Regular && it.sendMode.fromSharing) {
|
if (it.sendMode is SendMode.Regular && it.sendMode.fromSharing) {
|
||||||
// If we were sharing, we want to get back our last value from draft
|
// If we were sharing, we want to get back our last value from draft
|
||||||
loadDraftIfAny()
|
loadDraftIfAny(room)
|
||||||
} else {
|
} else {
|
||||||
// Otherwise we clear the composer and remove the draft from db
|
// Otherwise we clear the composer and remove the draft from db
|
||||||
setState { copy(sendMode = SendMode.Regular("", false)) }
|
setState { copy(sendMode = SendMode.Regular("", false)) }
|
||||||
|
@ -641,7 +647,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadDraftIfAny() {
|
private fun loadDraftIfAny(room: Room) {
|
||||||
val currentDraft = room.draftService().getDraft()
|
val currentDraft = room.draftService().getDraft()
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
|
@ -670,7 +676,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleUserIsTyping(action: MessageComposerAction.UserIsTyping) {
|
private fun handleUserIsTyping(room: Room, action: MessageComposerAction.UserIsTyping) {
|
||||||
if (vectorPreferences.sendTypingNotifs()) {
|
if (vectorPreferences.sendTypingNotifs()) {
|
||||||
if (action.isTyping) {
|
if (action.isTyping) {
|
||||||
room.typingService().userIsTyping()
|
room.typingService().userIsTyping()
|
||||||
|
@ -680,7 +686,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendChatEffect(sendChatEffect: ParsedCommand.SendChatEffect) {
|
private fun sendChatEffect(room: Room, sendChatEffect: ParsedCommand.SendChatEffect) {
|
||||||
// If message is blank, convert to an emote, with default message
|
// If message is blank, convert to an emote, with default message
|
||||||
if (sendChatEffect.message.isBlank()) {
|
if (sendChatEffect.message.isBlank()) {
|
||||||
val defaultMessage = stringProvider.getString(
|
val defaultMessage = stringProvider.getString(
|
||||||
|
@ -732,25 +738,25 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleChangeTopicSlashCommand(changeTopic: ParsedCommand.ChangeTopic) {
|
private fun handleChangeTopicSlashCommand(room: Room, changeTopic: ParsedCommand.ChangeTopic) {
|
||||||
launchSlashCommandFlowSuspendable(changeTopic) {
|
launchSlashCommandFlowSuspendable(room, changeTopic) {
|
||||||
room.stateService().updateTopic(changeTopic.topic)
|
room.stateService().updateTopic(changeTopic.topic)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleInviteSlashCommand(invite: ParsedCommand.Invite) {
|
private fun handleInviteSlashCommand(room: Room, invite: ParsedCommand.Invite) {
|
||||||
launchSlashCommandFlowSuspendable(invite) {
|
launchSlashCommandFlowSuspendable(room, invite) {
|
||||||
room.membershipService().invite(invite.userId, invite.reason)
|
room.membershipService().invite(invite.userId, invite.reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleInvite3pidSlashCommand(invite: ParsedCommand.Invite3Pid) {
|
private fun handleInvite3pidSlashCommand(room: Room, invite: ParsedCommand.Invite3Pid) {
|
||||||
launchSlashCommandFlowSuspendable(invite) {
|
launchSlashCommandFlowSuspendable(room, invite) {
|
||||||
room.membershipService().invite3pid(invite.threePid)
|
room.membershipService().invite3pid(invite.threePid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSetUserPowerLevel(setUserPowerLevel: ParsedCommand.SetUserPowerLevel) {
|
private fun handleSetUserPowerLevel(room: Room, setUserPowerLevel: ParsedCommand.SetUserPowerLevel) {
|
||||||
val newPowerLevelsContent = room.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty)
|
val newPowerLevelsContent = room.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty)
|
||||||
?.content
|
?.content
|
||||||
?.toModel<PowerLevelsContent>()
|
?.toModel<PowerLevelsContent>()
|
||||||
|
@ -758,19 +764,19 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
?.toContent()
|
?.toContent()
|
||||||
?: return
|
?: return
|
||||||
|
|
||||||
launchSlashCommandFlowSuspendable(setUserPowerLevel) {
|
launchSlashCommandFlowSuspendable(room, setUserPowerLevel) {
|
||||||
room.stateService().sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent)
|
room.stateService().sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleChangeDisplayNameSlashCommand(changeDisplayName: ParsedCommand.ChangeDisplayName) {
|
private fun handleChangeDisplayNameSlashCommand(room: Room, changeDisplayName: ParsedCommand.ChangeDisplayName) {
|
||||||
launchSlashCommandFlowSuspendable(changeDisplayName) {
|
launchSlashCommandFlowSuspendable(room, changeDisplayName) {
|
||||||
session.profileService().setDisplayName(session.myUserId, changeDisplayName.displayName)
|
session.profileService().setDisplayName(session.myUserId, changeDisplayName.displayName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handlePartSlashCommand(command: ParsedCommand.PartRoom) {
|
private fun handlePartSlashCommand(room: Room, command: ParsedCommand.PartRoom) {
|
||||||
launchSlashCommandFlowSuspendable(command) {
|
launchSlashCommandFlowSuspendable(room, command) {
|
||||||
if (command.roomAlias == null) {
|
if (command.roomAlias == null) {
|
||||||
// Leave the current room
|
// Leave the current room
|
||||||
room
|
room
|
||||||
|
@ -785,39 +791,39 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRemoveSlashCommand(removeUser: ParsedCommand.RemoveUser) {
|
private fun handleRemoveSlashCommand(room: Room, removeUser: ParsedCommand.RemoveUser) {
|
||||||
launchSlashCommandFlowSuspendable(removeUser) {
|
launchSlashCommandFlowSuspendable(room, removeUser) {
|
||||||
room.membershipService().remove(removeUser.userId, removeUser.reason)
|
room.membershipService().remove(removeUser.userId, removeUser.reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleBanSlashCommand(ban: ParsedCommand.BanUser) {
|
private fun handleBanSlashCommand(room: Room, ban: ParsedCommand.BanUser) {
|
||||||
launchSlashCommandFlowSuspendable(ban) {
|
launchSlashCommandFlowSuspendable(room, ban) {
|
||||||
room.membershipService().ban(ban.userId, ban.reason)
|
room.membershipService().ban(ban.userId, ban.reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleUnbanSlashCommand(unban: ParsedCommand.UnbanUser) {
|
private fun handleUnbanSlashCommand(room: Room, unban: ParsedCommand.UnbanUser) {
|
||||||
launchSlashCommandFlowSuspendable(unban) {
|
launchSlashCommandFlowSuspendable(room, unban) {
|
||||||
room.membershipService().unban(unban.userId, unban.reason)
|
room.membershipService().unban(unban.userId, unban.reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleChangeRoomNameSlashCommand(changeRoomName: ParsedCommand.ChangeRoomName) {
|
private fun handleChangeRoomNameSlashCommand(room: Room, changeRoomName: ParsedCommand.ChangeRoomName) {
|
||||||
launchSlashCommandFlowSuspendable(changeRoomName) {
|
launchSlashCommandFlowSuspendable(room, changeRoomName) {
|
||||||
room.stateService().updateName(changeRoomName.name)
|
room.stateService().updateName(changeRoomName.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getMyRoomMemberContent(): RoomMemberContent? {
|
private fun getMyRoomMemberContent(room: Room): RoomMemberContent? {
|
||||||
return room.getStateEvent(EventType.STATE_ROOM_MEMBER, QueryStringValue.Equals(session.myUserId))
|
return room.getStateEvent(EventType.STATE_ROOM_MEMBER, QueryStringValue.Equals(session.myUserId))
|
||||||
?.content
|
?.content
|
||||||
?.toModel<RoomMemberContent>()
|
?.toModel<RoomMemberContent>()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleChangeDisplayNameForRoomSlashCommand(changeDisplayName: ParsedCommand.ChangeDisplayNameForRoom) {
|
private fun handleChangeDisplayNameForRoomSlashCommand(room: Room, changeDisplayName: ParsedCommand.ChangeDisplayNameForRoom) {
|
||||||
launchSlashCommandFlowSuspendable(changeDisplayName) {
|
launchSlashCommandFlowSuspendable(room, changeDisplayName) {
|
||||||
getMyRoomMemberContent()
|
getMyRoomMemberContent(room)
|
||||||
?.copy(displayName = changeDisplayName.displayName)
|
?.copy(displayName = changeDisplayName.displayName)
|
||||||
?.toContent()
|
?.toContent()
|
||||||
?.let {
|
?.let {
|
||||||
|
@ -826,15 +832,15 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleChangeRoomAvatarSlashCommand(changeAvatar: ParsedCommand.ChangeRoomAvatar) {
|
private fun handleChangeRoomAvatarSlashCommand(room: Room, changeAvatar: ParsedCommand.ChangeRoomAvatar) {
|
||||||
launchSlashCommandFlowSuspendable(changeAvatar) {
|
launchSlashCommandFlowSuspendable(room, changeAvatar) {
|
||||||
room.stateService().sendStateEvent(EventType.STATE_ROOM_AVATAR, stateKey = "", RoomAvatarContent(changeAvatar.url).toContent())
|
room.stateService().sendStateEvent(EventType.STATE_ROOM_AVATAR, stateKey = "", RoomAvatarContent(changeAvatar.url).toContent())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleChangeAvatarForRoomSlashCommand(changeAvatar: ParsedCommand.ChangeAvatarForRoom) {
|
private fun handleChangeAvatarForRoomSlashCommand(room: Room, changeAvatar: ParsedCommand.ChangeAvatarForRoom) {
|
||||||
launchSlashCommandFlowSuspendable(changeAvatar) {
|
launchSlashCommandFlowSuspendable(room, changeAvatar) {
|
||||||
getMyRoomMemberContent()
|
getMyRoomMemberContent(room)
|
||||||
?.copy(avatarUrl = changeAvatar.url)
|
?.copy(avatarUrl = changeAvatar.url)
|
||||||
?.toContent()
|
?.toContent()
|
||||||
?.let {
|
?.let {
|
||||||
|
@ -843,8 +849,8 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleIgnoreSlashCommand(ignore: ParsedCommand.IgnoreUser) {
|
private fun handleIgnoreSlashCommand(room: Room, ignore: ParsedCommand.IgnoreUser) {
|
||||||
launchSlashCommandFlowSuspendable(ignore) {
|
launchSlashCommandFlowSuspendable(room, ignore) {
|
||||||
session.userService().ignoreUserIds(listOf(ignore.userId))
|
session.userService().ignoreUserIds(listOf(ignore.userId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -853,15 +859,15 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandConfirmationRequest(unignore))
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandConfirmationRequest(unignore))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSlashCommandConfirmed(action: MessageComposerAction.SlashCommandConfirmed) {
|
private fun handleSlashCommandConfirmed(room: Room, action: MessageComposerAction.SlashCommandConfirmed) {
|
||||||
when (action.parsedCommand) {
|
when (action.parsedCommand) {
|
||||||
is ParsedCommand.UnignoreUser -> handleUnignoreSlashCommandConfirmed(action.parsedCommand)
|
is ParsedCommand.UnignoreUser -> handleUnignoreSlashCommandConfirmed(room, action.parsedCommand)
|
||||||
else -> TODO("Not handled yet")
|
else -> TODO("Not handled yet")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleUnignoreSlashCommandConfirmed(unignore: ParsedCommand.UnignoreUser) {
|
private fun handleUnignoreSlashCommandConfirmed(room: Room, unignore: ParsedCommand.UnignoreUser) {
|
||||||
launchSlashCommandFlowSuspendable(unignore) {
|
launchSlashCommandFlowSuspendable(room, unignore) {
|
||||||
session.userService().unIgnoreUserIds(listOf(unignore.userId))
|
session.userService().unIgnoreUserIds(listOf(unignore.userId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -870,7 +876,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
_viewEvents.post(MessageComposerViewEvents.OpenRoomMemberProfile(whois.userId))
|
_viewEvents.post(MessageComposerViewEvents.OpenRoomMemberProfile(whois.userId))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendPrefixedMessage(prefix: String, message: CharSequence, rootThreadEventId: String?) {
|
private fun sendPrefixedMessage(room: Room, prefix: String, message: CharSequence, rootThreadEventId: String?) {
|
||||||
val sequence = buildString {
|
val sequence = buildString {
|
||||||
append(prefix)
|
append(prefix)
|
||||||
if (message.isNotEmpty()) {
|
if (message.isNotEmpty()) {
|
||||||
|
@ -886,7 +892,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
/**
|
/**
|
||||||
* Convert a send mode to a draft and save the draft.
|
* Convert a send mode to a draft and save the draft.
|
||||||
*/
|
*/
|
||||||
private fun handleSaveTextDraft(draft: String) = withState {
|
private fun handleSaveTextDraft(room: Room, draft: String) = withState {
|
||||||
session.coroutineScope.launch {
|
session.coroutineScope.launch {
|
||||||
when {
|
when {
|
||||||
it.sendMode is SendMode.Regular && !it.sendMode.fromSharing -> {
|
it.sendMode is SendMode.Regular && !it.sendMode.fromSharing -> {
|
||||||
|
@ -909,7 +915,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleStartRecordingVoiceMessage() {
|
private fun handleStartRecordingVoiceMessage(room: Room) {
|
||||||
try {
|
try {
|
||||||
audioMessageHelper.startRecording(room.roomId)
|
audioMessageHelper.startRecording(room.roomId)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
|
@ -917,7 +923,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleEndRecordingVoiceMessage(isCancelled: Boolean, rootThreadEventId: String? = null) {
|
private fun handleEndRecordingVoiceMessage(room: Room, isCancelled: Boolean, rootThreadEventId: String? = null) {
|
||||||
audioMessageHelper.stopPlayback()
|
audioMessageHelper.stopPlayback()
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
audioMessageHelper.deleteRecording()
|
audioMessageHelper.deleteRecording()
|
||||||
|
@ -964,7 +970,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
audioMessageHelper.stopAllVoiceActions(deleteRecord)
|
audioMessageHelper.stopAllVoiceActions(deleteRecord)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleInitializeVoiceRecorder(attachmentData: ContentAttachmentData) {
|
private fun handleInitializeVoiceRecorder(room: Room, attachmentData: ContentAttachmentData) {
|
||||||
audioMessageHelper.initializeRecorder(room.roomId, attachmentData)
|
audioMessageHelper.initializeRecorder(room.roomId, attachmentData)
|
||||||
setState { copy(voiceRecordingUiState = VoiceMessageRecorderView.RecordingUiState.Draft) }
|
setState { copy(voiceRecordingUiState = VoiceMessageRecorderView.RecordingUiState.Draft) }
|
||||||
}
|
}
|
||||||
|
@ -985,7 +991,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
audioMessageHelper.movePlaybackTo(action.eventId, action.percentage, action.duration)
|
audioMessageHelper.movePlaybackTo(action.eventId, action.percentage, action.duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleEntersBackground(composerText: String) {
|
private fun handleEntersBackground(room: Room, composerText: String) {
|
||||||
// Always stop all voice actions. It may be playing in timeline or active recording
|
// Always stop all voice actions. It may be playing in timeline or active recording
|
||||||
val playingAudioContent = audioMessageHelper.stopAllVoiceActions(deleteRecord = false)
|
val playingAudioContent = audioMessageHelper.stopAllVoiceActions(deleteRecord = false)
|
||||||
// TODO remove this when there will be a listening indicator outside of the timeline
|
// TODO remove this when there will be a listening indicator outside of the timeline
|
||||||
|
@ -1001,7 +1007,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
handleSaveTextDraft(draft = composerText)
|
handleSaveTextDraft(room = room, draft = composerText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1009,12 +1015,12 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
_viewEvents.post(MessageComposerViewEvents.InsertUserDisplayName(action.userId))
|
_viewEvents.post(MessageComposerViewEvents.InsertUserDisplayName(action.userId))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun launchSlashCommandFlowSuspendable(parsedCommand: ParsedCommand, block: suspend () -> Unit) {
|
private fun launchSlashCommandFlowSuspendable(room: Room, parsedCommand: ParsedCommand, block: suspend () -> Unit) {
|
||||||
_viewEvents.post(MessageComposerViewEvents.SlashCommandLoading)
|
_viewEvents.post(MessageComposerViewEvents.SlashCommandLoading)
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val event = try {
|
val event = try {
|
||||||
block()
|
block()
|
||||||
popDraft()
|
popDraft(room)
|
||||||
MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)
|
MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
MessageComposerViewEvents.SlashCommandResultError(failure)
|
MessageComposerViewEvents.SlashCommandResultError(failure)
|
||||||
|
@ -1023,6 +1029,10 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onRoomError() = setState {
|
||||||
|
copy(isRoomError = true)
|
||||||
|
}
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory : MavericksAssistedViewModelFactory<MessageComposerViewModel, MessageComposerViewState> {
|
interface Factory : MavericksAssistedViewModelFactory<MessageComposerViewModel, MessageComposerViewState> {
|
||||||
override fun create(initialState: MessageComposerViewState): MessageComposerViewModel
|
override fun create(initialState: MessageComposerViewState): MessageComposerViewModel
|
||||||
|
|
|
@ -62,6 +62,7 @@ fun CanSendStatus.boolean(): Boolean {
|
||||||
|
|
||||||
data class MessageComposerViewState(
|
data class MessageComposerViewState(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
|
val isRoomError: Boolean = false,
|
||||||
val canSendMessage: CanSendStatus = CanSendStatus.Allowed,
|
val canSendMessage: CanSendStatus = CanSendStatus.Allowed,
|
||||||
val isSendButtonVisible: Boolean = false,
|
val isSendButtonVisible: Boolean = false,
|
||||||
val rootThreadEventId: String? = null,
|
val rootThreadEventId: String? = null,
|
||||||
|
@ -88,8 +89,8 @@ data class MessageComposerViewState(
|
||||||
|
|
||||||
val isVoiceMessageIdle = !isVoiceRecording
|
val isVoiceMessageIdle = !isVoiceRecording
|
||||||
|
|
||||||
val isComposerVisible = canSendMessage.boolean() && !isVoiceRecording
|
val isComposerVisible = canSendMessage.boolean() && !isVoiceRecording && !isRoomError
|
||||||
val isVoiceMessageRecorderVisible = canSendMessage.boolean() && !isSendButtonVisible
|
val isVoiceMessageRecorderVisible = canSendMessage.boolean() && !isSendButtonVisible && !isRoomError
|
||||||
|
|
||||||
constructor(args: TimelineArgs) : this(
|
constructor(args: TimelineArgs) : this(
|
||||||
roomId = args.roomId,
|
roomId = args.roomId,
|
||||||
|
|
|
@ -42,7 +42,6 @@ import androidx.core.view.isVisible
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import com.google.android.material.shape.MaterialShapeDrawable
|
import com.google.android.material.shape.MaterialShapeDrawable
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.extensions.hideKeyboard
|
|
||||||
import im.vector.app.core.extensions.setTextIfDifferent
|
import im.vector.app.core.extensions.setTextIfDifferent
|
||||||
import im.vector.app.core.extensions.showKeyboard
|
import im.vector.app.core.extensions.showKeyboard
|
||||||
import im.vector.app.core.utils.DimensionConverter
|
import im.vector.app.core.utils.DimensionConverter
|
||||||
|
@ -132,8 +131,6 @@ class RichTextComposerLayout @JvmOverloads constructor(
|
||||||
views.bottomSheetHandle.isVisible = isFullScreen
|
views.bottomSheetHandle.isVisible = isFullScreen
|
||||||
if (isFullScreen) {
|
if (isFullScreen) {
|
||||||
editText.showKeyboard(true)
|
editText.showKeyboard(true)
|
||||||
} else {
|
|
||||||
editText.hideKeyboard()
|
|
||||||
}
|
}
|
||||||
this.isFullScreen = isFullScreen
|
this.isFullScreen = isFullScreen
|
||||||
}
|
}
|
||||||
|
@ -274,8 +271,8 @@ class RichTextComposerLayout @JvmOverloads constructor(
|
||||||
connect(R.id.composerEditTextOuterBorder, ConstraintSet.START, R.id.composerLayoutContent, ConstraintSet.START, dpToPx(12))
|
connect(R.id.composerEditTextOuterBorder, ConstraintSet.START, R.id.composerLayoutContent, ConstraintSet.START, dpToPx(12))
|
||||||
connect(R.id.composerEditTextOuterBorder, ConstraintSet.END, R.id.composerLayoutContent, ConstraintSet.END, dpToPx(12))
|
connect(R.id.composerEditTextOuterBorder, ConstraintSet.END, R.id.composerLayoutContent, ConstraintSet.END, dpToPx(12))
|
||||||
} else {
|
} else {
|
||||||
connect(R.id.composerEditTextOuterBorder, ConstraintSet.TOP, R.id.composerLayoutContent, ConstraintSet.TOP, dpToPx(10))
|
connect(R.id.composerEditTextOuterBorder, ConstraintSet.TOP, R.id.composerLayoutContent, ConstraintSet.TOP, dpToPx(8))
|
||||||
connect(R.id.composerEditTextOuterBorder, ConstraintSet.BOTTOM, R.id.composerLayoutContent, ConstraintSet.BOTTOM, dpToPx(10))
|
connect(R.id.composerEditTextOuterBorder, ConstraintSet.BOTTOM, R.id.composerLayoutContent, ConstraintSet.BOTTOM, dpToPx(8))
|
||||||
connect(R.id.composerEditTextOuterBorder, ConstraintSet.START, R.id.attachmentButton, ConstraintSet.END, 0)
|
connect(R.id.composerEditTextOuterBorder, ConstraintSet.START, R.id.attachmentButton, ConstraintSet.END, 0)
|
||||||
connect(R.id.composerEditTextOuterBorder, ConstraintSet.END, R.id.sendButton, ConstraintSet.START, 0)
|
connect(R.id.composerEditTextOuterBorder, ConstraintSet.END, R.id.sendButton, ConstraintSet.START, 0)
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,7 +118,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkQrCodeLoginCapability(homeServerUrl: String) {
|
private suspend fun checkQrCodeLoginCapability(config: HomeServerConnectionConfig) {
|
||||||
if (!vectorFeatures.isQrCodeLoginEnabled()) {
|
if (!vectorFeatures.isQrCodeLoginEnabled()) {
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
|
@ -133,16 +133,12 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
viewModelScope.launch {
|
// check if selected server supports MSC3882 first
|
||||||
// check if selected server supports MSC3882 first
|
val canLoginWithQrCode = authenticationService.isQrLoginSupported(config)
|
||||||
homeServerConnectionConfigFactory.create(homeServerUrl)?.let {
|
setState {
|
||||||
val canLoginWithQrCode = authenticationService.isQrLoginSupported(it)
|
copy(
|
||||||
setState {
|
canLoginWithQrCode = canLoginWithQrCode
|
||||||
copy(
|
)
|
||||||
canLoginWithQrCode = canLoginWithQrCode
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -710,7 +706,6 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
_viewEvents.post(OnboardingViewEvents.Failure(Throwable("Unable to create a HomeServerConnectionConfig")))
|
_viewEvents.post(OnboardingViewEvents.Failure(Throwable("Unable to create a HomeServerConnectionConfig")))
|
||||||
} else {
|
} else {
|
||||||
startAuthenticationFlow(action, homeServerConnectionConfig, serverTypeOverride, postAction)
|
startAuthenticationFlow(action, homeServerConnectionConfig, serverTypeOverride, postAction)
|
||||||
checkQrCodeLoginCapability(homeServerConnectionConfig.homeServerUri.toString())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -769,6 +764,8 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
_viewEvents.post(OnboardingViewEvents.OutdatedHomeserver)
|
_viewEvents.post(OnboardingViewEvents.OutdatedHomeserver)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkQrCodeLoginCapability(config)
|
||||||
|
|
||||||
when (trigger) {
|
when (trigger) {
|
||||||
is OnboardingAction.HomeServerChange.SelectHomeServer -> {
|
is OnboardingAction.HomeServerChange.SelectHomeServer -> {
|
||||||
onHomeServerSelected(config, serverTypeOverride, authResult)
|
onHomeServerSelected(config, serverTypeOverride, authResult)
|
||||||
|
|
|
@ -32,26 +32,26 @@
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/attachmentButton"
|
android:id="@+id/attachmentButton"
|
||||||
android:layout_width="56dp"
|
android:layout_width="60dp"
|
||||||
android:layout_height="60dp"
|
android:layout_height="56dp"
|
||||||
android:layout_margin="@dimen/composer_attachment_margin"
|
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
|
||||||
android:contentDescription="@string/option_send_files"
|
android:contentDescription="@string/option_send_files"
|
||||||
android:src="@drawable/ic_rich_composer_add"
|
android:src="@drawable/ic_rich_composer_add"
|
||||||
android:paddingStart="4dp"
|
|
||||||
app:layout_constraintVertical_bias="1"
|
app:layout_constraintVertical_bias="1"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/sendButton"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="@id/sendButton"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_goneMarginBottom="57dp"
|
app:layout_goneMarginBottom="56dp"
|
||||||
tools:ignore="MissingPrefix,RtlSymmetry" />
|
tools:ignore="MissingPrefix,RtlSymmetry" />
|
||||||
|
|
||||||
|
<!-- Constraints are updated programmatically -->
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/composerEditTextOuterBorder"
|
android:id="@+id/composerEditTextOuterBorder"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:minHeight="40dp"
|
android:minHeight="40dp"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
android:layout_marginHorizontal="12dp"
|
android:layout_marginHorizontal="12dp"
|
||||||
app:layout_constraintVertical_bias="0"
|
app:layout_constraintVertical_bias="0"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
@ -156,19 +156,19 @@
|
||||||
app:layout_constraintBottom_toBottomOf="@id/composerEditTextOuterBorder"
|
app:layout_constraintBottom_toBottomOf="@id/composerEditTextOuterBorder"
|
||||||
app:layout_constraintVertical_bias="0"
|
app:layout_constraintVertical_bias="0"
|
||||||
android:src="@drawable/ic_composer_full_screen"
|
android:src="@drawable/ic_composer_full_screen"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||||
android:contentDescription="@string/rich_text_editor_full_screen_toggle" />
|
android:contentDescription="@string/rich_text_editor_full_screen_toggle" />
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/sendButton"
|
android:id="@+id/sendButton"
|
||||||
android:layout_width="56dp"
|
android:layout_width="56dp"
|
||||||
android:layout_height="60dp"
|
android:layout_height="56dp"
|
||||||
android:paddingEnd="4dp"
|
android:paddingEnd="4dp"
|
||||||
android:contentDescription="@string/action_send"
|
android:contentDescription="@string/action_send"
|
||||||
android:scaleType="center"
|
android:scaleType="center"
|
||||||
android:src="@drawable/ic_rich_composer_send"
|
android:src="@drawable/ic_rich_composer_send"
|
||||||
android:visibility="invisible"
|
android:visibility="invisible"
|
||||||
android:background="?android:selectableItemBackground"
|
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||||
app:layout_constraintTop_toBottomOf="@id/composerEditTextOuterBorder"
|
app:layout_constraintTop_toBottomOf="@id/composerEditTextOuterBorder"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
|
|
@ -75,7 +75,7 @@
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:overScrollMode="always"
|
android:overScrollMode="always"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toTopOf="@id/notificationAreaView"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/removeJitsiWidgetView"
|
app:layout_constraintTop_toBottomOf="@id/removeJitsiWidgetView"
|
||||||
|
|
|
@ -160,6 +160,28 @@ class OnboardingViewModelTest {
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given combined login enabled, when handling sign in splash action, then emits OpenCombinedLogin with default homeserver qrCode supported`() = runTest {
|
||||||
|
val test = viewModel.test()
|
||||||
|
fakeVectorFeatures.givenCombinedLoginEnabled()
|
||||||
|
givenCanSuccessfullyUpdateHomeserver(A_DEFAULT_HOMESERVER_URL, DEFAULT_SELECTED_HOMESERVER_STATE, canLoginWithQrCode = true)
|
||||||
|
|
||||||
|
viewModel.handle(OnboardingAction.SplashAction.OnIAlreadyHaveAnAccount(OnboardingFlow.SignIn))
|
||||||
|
|
||||||
|
test
|
||||||
|
.assertStatesChanges(
|
||||||
|
initialState,
|
||||||
|
{ copy(onboardingFlow = OnboardingFlow.SignIn) },
|
||||||
|
{ copy(isLoading = true) },
|
||||||
|
{ copy(canLoginWithQrCode = true) },
|
||||||
|
{ copy(selectedHomeserver = DEFAULT_SELECTED_HOMESERVER_STATE) },
|
||||||
|
{ copy(signMode = SignMode.SignIn) },
|
||||||
|
{ copy(isLoading = false) }
|
||||||
|
)
|
||||||
|
.assertEvents(OnboardingViewEvents.OpenCombinedLogin)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given can successfully login in with token, when logging in with token, then emits AccountSignedIn`() = runTest {
|
fun `given can successfully login in with token, when logging in with token, then emits AccountSignedIn`() = runTest {
|
||||||
val test = viewModel.test()
|
val test = viewModel.test()
|
||||||
|
@ -1152,11 +1174,13 @@ class OnboardingViewModelTest {
|
||||||
resultingState: SelectedHomeserverState,
|
resultingState: SelectedHomeserverState,
|
||||||
config: HomeServerConnectionConfig = A_HOMESERVER_CONFIG,
|
config: HomeServerConnectionConfig = A_HOMESERVER_CONFIG,
|
||||||
fingerprint: Fingerprint? = null,
|
fingerprint: Fingerprint? = null,
|
||||||
|
canLoginWithQrCode: Boolean = false,
|
||||||
) {
|
) {
|
||||||
fakeHomeServerConnectionConfigFactory.givenConfigFor(homeserverUrl, fingerprint, config)
|
fakeHomeServerConnectionConfigFactory.givenConfigFor(homeserverUrl, fingerprint, config)
|
||||||
fakeStartAuthenticationFlowUseCase.givenResult(config, StartAuthenticationResult(isHomeserverOutdated = false, resultingState))
|
fakeStartAuthenticationFlowUseCase.givenResult(config, StartAuthenticationResult(isHomeserverOutdated = false, resultingState))
|
||||||
givenRegistrationResultFor(RegisterAction.StartRegistration, RegistrationActionHandler.Result.StartRegistration)
|
givenRegistrationResultFor(RegisterAction.StartRegistration, RegistrationActionHandler.Result.StartRegistration)
|
||||||
fakeHomeServerHistoryService.expectUrlToBeAdded(config.homeServerUri.toString())
|
fakeHomeServerHistoryService.expectUrlToBeAdded(config.homeServerUri.toString())
|
||||||
|
fakeAuthenticationService.givenIsQrLoginSupported(config, canLoginWithQrCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun givenUpdatingHomeserverErrors(homeserverUrl: String, resultingState: SelectedHomeserverState, error: Throwable) {
|
private fun givenUpdatingHomeserverErrors(homeserverUrl: String, resultingState: SelectedHomeserverState, error: Throwable) {
|
||||||
|
@ -1164,6 +1188,7 @@ class OnboardingViewModelTest {
|
||||||
fakeStartAuthenticationFlowUseCase.givenResult(A_HOMESERVER_CONFIG, StartAuthenticationResult(isHomeserverOutdated = false, resultingState))
|
fakeStartAuthenticationFlowUseCase.givenResult(A_HOMESERVER_CONFIG, StartAuthenticationResult(isHomeserverOutdated = false, resultingState))
|
||||||
givenRegistrationResultFor(RegisterAction.StartRegistration, RegistrationActionHandler.Result.Error(error))
|
givenRegistrationResultFor(RegisterAction.StartRegistration, RegistrationActionHandler.Result.Error(error))
|
||||||
fakeHomeServerHistoryService.expectUrlToBeAdded(A_HOMESERVER_CONFIG.homeServerUri.toString())
|
fakeHomeServerHistoryService.expectUrlToBeAdded(A_HOMESERVER_CONFIG.homeServerUri.toString())
|
||||||
|
fakeAuthenticationService.givenIsQrLoginSupported(A_HOMESERVER_CONFIG, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun givenUserNameIsAvailable(userName: String) {
|
private fun givenUserNameIsAvailable(userName: String) {
|
||||||
|
|
|
@ -58,6 +58,10 @@ class FakeAuthenticationService : AuthenticationService by mockk() {
|
||||||
coEvery { getWellKnownData(matrixId, config) } returns result
|
coEvery { getWellKnownData(matrixId, config) } returns result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun givenIsQrLoginSupported(config: HomeServerConnectionConfig, result: Boolean) {
|
||||||
|
coEvery { isQrLoginSupported(config) } returns result
|
||||||
|
}
|
||||||
|
|
||||||
fun givenWellKnownThrows(matrixId: String, config: HomeServerConnectionConfig?, cause: Throwable) {
|
fun givenWellKnownThrows(matrixId: String, config: HomeServerConnectionConfig?, cause: Throwable) {
|
||||||
coEvery { getWellKnownData(matrixId, config) } throws cause
|
coEvery { getWellKnownData(matrixId, config) } throws cause
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue