diff --git a/.github/workflows/triage-incoming.yml b/.github/workflows/triage-incoming.yml
index 4ecc824424..3bb5ab73aa 100644
--- a/.github/workflows/triage-incoming.yml
+++ b/.github/workflows/triage-incoming.yml
@@ -2,11 +2,13 @@ name: Move new issues onto Issue triage board
on:
issues:
- types: [opened]
+ types: [ opened ]
jobs:
automate-project-columns:
runs-on: ubuntu-latest
+ if: |
+ github.repository == 'vector-im/element-android' # Skip in forks
steps:
- uses: alex-page/github-project-automation-plus@bb266ff4dde9242060e2d5418e120a133586d488
with:
diff --git a/.github/workflows/triage-move-labelled.yml b/.github/workflows/triage-move-labelled.yml
index 67c4e9dbab..96d302ceea 100644
--- a/.github/workflows/triage-move-labelled.yml
+++ b/.github/workflows/triage-move-labelled.yml
@@ -2,12 +2,14 @@ name: Move labelled issues to correct boards and columns
on:
issues:
- types: [labeled]
-
+ types: [ labeled ]
+
jobs:
move_needs_info_issues:
name: X-Needs-Info issues to Need info column on triage board
runs-on: ubuntu-latest
+ if: |
+ github.repository == 'vector-im/element-android' # Skip in forks
steps:
- uses: konradpabjan/move-labeled-or-milestoned-issue@219d384e03fa4b6460cd24f9f37d19eb033a4338
with:
@@ -19,15 +21,16 @@ jobs:
add_priority_design_issues_to_project:
name: P1 X-Needs-Design to Design project board
runs-on: ubuntu-latest
- if: >
- contains(github.event.issue.labels.*.name, 'X-Needs-Design') &&
- (contains(github.event.issue.labels.*.name, 'S-Critical') &&
- (contains(github.event.issue.labels.*.name, 'O-Frequent') ||
+ if: |
+ github.repository == 'vector-im/element-android' && # Skip in forks
+ contains(github.event.issue.labels.*.name, 'X-Needs-Design') &&
+ (contains(github.event.issue.labels.*.name, 'S-Critical') &&
+ (contains(github.event.issue.labels.*.name, 'O-Frequent') ||
contains(github.event.issue.labels.*.name, 'O-Occasional')) ||
- contains(github.event.issue.labels.*.name, 'S-Major') &&
- contains(github.event.issue.labels.*.name, 'O-Frequent') ||
- contains(github.event.issue.labels.*.name, 'A11y') &&
- contains(github.event.issue.labels.*.name, 'O-Frequent'))
+ contains(github.event.issue.labels.*.name, 'S-Major') &&
+ contains(github.event.issue.labels.*.name, 'O-Frequent') ||
+ contains(github.event.issue.labels.*.name, 'A11y') &&
+ contains(github.event.issue.labels.*.name, 'O-Frequent'))
steps:
- uses: octokit/graphql-action@v2.x
id: add_to_project
@@ -47,36 +50,38 @@ jobs:
PROJECT_ID: "PN_kwDOAM0swc0sUA"
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
-# delight_issues_to_board:
-# name: Spaces issues to new Delight project board
-# runs-on: ubuntu-latest
-# if: >
-# contains(github.event.issue.labels.*.name, 'A-Spaces') ||
-# contains(github.event.issue.labels.*.name, 'A-Space-Settings') ||
-# contains(github.event.issue.labels.*.name, 'A-Subspaces')
-# steps:
-# - uses: octokit/graphql-action@v2.x
-# with:
-# headers: '{"GraphQL-Features": "projects_next_graphql"}'
-# query: |
-# mutation add_to_project($projectid:ID!,$contentid:ID!) {
-# addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
-# projectNextItem {
-# id
-# }
-# }
-# }
-# projectid: ${{ env.PROJECT_ID }}
-# contentid: ${{ github.event.issue.node_id }}
-# env:
-# PROJECT_ID: "PN_kwDOAM0swc1HvQ"
-# GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
+ # delight_issues_to_board:
+ # name: Spaces issues to new Delight project board
+ # runs-on: ubuntu-latest
+ # if: |
+ # github.repository == 'vector-im/element-android' && # Skip in forks
+ # contains(github.event.issue.labels.*.name, 'A-Spaces') ||
+ # contains(github.event.issue.labels.*.name, 'A-Space-Settings') ||
+ # contains(github.event.issue.labels.*.name, 'A-Subspaces')
+ # steps:
+ # - uses: octokit/graphql-action@v2.x
+ # with:
+ # headers: '{"GraphQL-Features": "projects_next_graphql"}'
+ # query: |
+ # mutation add_to_project($projectid:ID!,$contentid:ID!) {
+ # addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
+ # projectNextItem {
+ # id
+ # }
+ # }
+ # }
+ # projectid: ${{ env.PROJECT_ID }}
+ # contentid: ${{ github.event.issue.node_id }}
+ # env:
+ # PROJECT_ID: "PN_kwDOAM0swc1HvQ"
+ # GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
move_voice-message_issues:
name: A-Voice Messages to voice message board
runs-on: ubuntu-latest
- if: >
- contains(github.event.issue.labels.*.name, 'A-Voice Messages')
+ if: |
+ github.repository == 'vector-im/element-android' && # Skip in forks
+ contains(github.event.issue.labels.*.name, 'A-Voice Messages')
steps:
- uses: octokit/graphql-action@v2.x
with:
@@ -98,8 +103,9 @@ jobs:
move_threads_issues:
name: A-Threads to Thread board
runs-on: ubuntu-latest
- if: >
- contains(github.event.issue.labels.*.name, 'A-Threads')
+ if: |
+ github.repository == 'vector-im/element-android' && # Skip in forks
+ contains(github.event.issue.labels.*.name, 'A-Threads')
steps:
- uses: octokit/graphql-action@v2.x
with:
@@ -121,8 +127,9 @@ jobs:
move_message_bubbles_issues:
name: A-Message-Bubbles to Message bubbles board
runs-on: ubuntu-latest
- if: >
- contains(github.event.issue.labels.*.name, 'A-Message-Bubbles')
+ if: |
+ github.repository == 'vector-im/element-android' && # Skip in forks
+ contains(github.event.issue.labels.*.name, 'A-Message-Bubbles')
steps:
- uses: octokit/graphql-action@v2.x
with:
diff --git a/.github/workflows/triage-move-unlabelled.yml b/.github/workflows/triage-move-unlabelled.yml
index 94bd049b91..5f13165939 100644
--- a/.github/workflows/triage-move-unlabelled.yml
+++ b/.github/workflows/triage-move-unlabelled.yml
@@ -2,15 +2,15 @@ name: Move unlabelled from needs info columns to triaged
on:
issues:
- types: [unlabeled]
-
+ types: [ unlabeled ]
+
jobs:
Move_Unabeled_Issue_On_Project_Board:
name: Move no longer X-Needs-Info issues to Triaged
runs-on: ubuntu-latest
- if: >
- ${{
- !contains(github.event.issue.labels.*.name, 'X-Needs-Info') }}
+ if: |
+ github.repository == 'vector-im/element-android' && # Skip in forks
+ !contains(github.event.issue.labels.*.name, 'X-Needs-Info')
env:
BOARD_NAME: "Issue triage"
OWNER: ${{ github.repository_owner }}
diff --git a/.github/workflows/triage-priority-bugs.yml b/.github/workflows/triage-priority-bugs.yml
index 976879a3ae..7564387a1c 100644
--- a/.github/workflows/triage-priority-bugs.yml
+++ b/.github/workflows/triage-priority-bugs.yml
@@ -2,28 +2,29 @@ name: Move P1 bugs to boards
on:
issues:
- types: [labeled, unlabeled]
+ types: [ labeled, unlabeled ]
jobs:
p1_issues_to_team_workboard:
runs-on: ubuntu-latest
- if: >
- (!contains(github.event.issue.labels.*.name, 'A-E2EE') &&
- !contains(github.event.issue.labels.*.name, 'A-E2EE-Cross-Signing') &&
- !contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') &&
- !contains(github.event.issue.labels.*.name, 'A-E2EE-Key-Backup') &&
- !contains(github.event.issue.labels.*.name, 'A-E2EE-SAS-Verification') &&
- !contains(github.event.issue.labels.*.name, 'A-Spaces') &&
- !contains(github.event.issue.labels.*.name, 'A-Spaces-Settings') &&
- !contains(github.event.issue.labels.*.name, 'A-Subspaces')) &&
- (contains(github.event.issue.labels.*.name, 'T-Defect') &&
- contains(github.event.issue.labels.*.name, 'S-Critical') &&
- (contains(github.event.issue.labels.*.name, 'O-Frequent') ||
+ if: |
+ github.repository == 'vector-im/element-android' && # Skip in forks
+ (!contains(github.event.issue.labels.*.name, 'A-E2EE') &&
+ !contains(github.event.issue.labels.*.name, 'A-E2EE-Cross-Signing') &&
+ !contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') &&
+ !contains(github.event.issue.labels.*.name, 'A-E2EE-Key-Backup') &&
+ !contains(github.event.issue.labels.*.name, 'A-E2EE-SAS-Verification') &&
+ !contains(github.event.issue.labels.*.name, 'A-Spaces') &&
+ !contains(github.event.issue.labels.*.name, 'A-Spaces-Settings') &&
+ !contains(github.event.issue.labels.*.name, 'A-Subspaces')) &&
+ (contains(github.event.issue.labels.*.name, 'T-Defect') &&
+ contains(github.event.issue.labels.*.name, 'S-Critical') &&
+ (contains(github.event.issue.labels.*.name, 'O-Frequent') ||
contains(github.event.issue.labels.*.name, 'O-Occasional')) ||
- contains(github.event.issue.labels.*.name, 'S-Major') &&
- contains(github.event.issue.labels.*.name, 'O-Frequent') ||
- contains(github.event.issue.labels.*.name, 'A11y') &&
- contains(github.event.issue.labels.*.name, 'O-Frequent'))
+ contains(github.event.issue.labels.*.name, 'S-Major') &&
+ contains(github.event.issue.labels.*.name, 'O-Frequent') ||
+ contains(github.event.issue.labels.*.name, 'A11y') &&
+ contains(github.event.issue.labels.*.name, 'O-Frequent'))
steps:
- uses: alex-page/github-project-automation-plus@bb266ff4dde9242060e2d5418e120a133586d488
with:
@@ -33,20 +34,21 @@ jobs:
P1_issues_to_crypto_team_workboard:
runs-on: ubuntu-latest
- if: >
- (contains(github.event.issue.labels.*.name, 'A-E2EE') ||
- contains(github.event.issue.labels.*.name, 'A-E2EE-Cross-Signing') ||
- contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') ||
- contains(github.event.issue.labels.*.name, 'A-E2EE-Key-Backup') ||
- contains(github.event.issue.labels.*.name, 'A-E2EE-SAS-Verification')) &&
- (contains(github.event.issue.labels.*.name, 'T-Defect') &&
- contains(github.event.issue.labels.*.name, 'S-Critical') &&
- (contains(github.event.issue.labels.*.name, 'O-Frequent') ||
+ if: |
+ github.repository == 'vector-im/element-android' && # Skip in forks
+ (contains(github.event.issue.labels.*.name, 'A-E2EE') ||
+ contains(github.event.issue.labels.*.name, 'A-E2EE-Cross-Signing') ||
+ contains(github.event.issue.labels.*.name, 'A-E2EE-Dehydration') ||
+ contains(github.event.issue.labels.*.name, 'A-E2EE-Key-Backup') ||
+ contains(github.event.issue.labels.*.name, 'A-E2EE-SAS-Verification')) &&
+ (contains(github.event.issue.labels.*.name, 'T-Defect') &&
+ contains(github.event.issue.labels.*.name, 'S-Critical') &&
+ (contains(github.event.issue.labels.*.name, 'O-Frequent') ||
contains(github.event.issue.labels.*.name, 'O-Occasional')) ||
- contains(github.event.issue.labels.*.name, 'S-Major') &&
- contains(github.event.issue.labels.*.name, 'O-Frequent') ||
- contains(github.event.issue.labels.*.name, 'A11y') &&
- contains(github.event.issue.labels.*.name, 'O-Frequent'))
+ contains(github.event.issue.labels.*.name, 'S-Major') &&
+ contains(github.event.issue.labels.*.name, 'O-Frequent') ||
+ contains(github.event.issue.labels.*.name, 'A11y') &&
+ contains(github.event.issue.labels.*.name, 'O-Frequent'))
steps:
- uses: alex-page/github-project-automation-plus@bb266ff4dde9242060e2d5418e120a133586d488
with:
diff --git a/build.gradle b/build.gradle
index f057d234e5..9d83268a56 100644
--- a/build.gradle
+++ b/build.gradle
@@ -29,7 +29,7 @@ buildscript {
// ktlint Plugin
plugins {
- id "org.jlleitschuh.gradle.ktlint" version "10.2.0"
+ id "org.jlleitschuh.gradle.ktlint" version "10.2.1"
}
allprojects {
diff --git a/changelog.d/3444.bugfix b/changelog.d/3444.bugfix
new file mode 100644
index 0000000000..bf397da5b7
--- /dev/null
+++ b/changelog.d/3444.bugfix
@@ -0,0 +1 @@
+Attachment picker UI improvements
\ No newline at end of file
diff --git a/changelog.d/4612.misc b/changelog.d/4612.misc
new file mode 100644
index 0000000000..43b5007b7e
--- /dev/null
+++ b/changelog.d/4612.misc
@@ -0,0 +1 @@
+Workaround to fetch all the pending toDevice events from a Synapse homeserver
\ No newline at end of file
diff --git a/changelog.d/4747.misc b/changelog.d/4747.misc
new file mode 100644
index 0000000000..37a960671c
--- /dev/null
+++ b/changelog.d/4747.misc
@@ -0,0 +1 @@
+Cleaning rendering of state events in timeline
\ No newline at end of file
diff --git a/changelog.d/4756.bugfix b/changelog.d/4756.bugfix
new file mode 100644
index 0000000000..8e0c373557
--- /dev/null
+++ b/changelog.d/4756.bugfix
@@ -0,0 +1 @@
+Fixes newer emojis rendering strangely when inserting from the system keyboard
\ No newline at end of file
diff --git a/changelog.d/4767.bugfix b/changelog.d/4767.bugfix
new file mode 100644
index 0000000000..172e9d80ca
--- /dev/null
+++ b/changelog.d/4767.bugfix
@@ -0,0 +1 @@
+Fixing unable to change change avatar in some scenarios
\ No newline at end of file
diff --git a/changelog.d/4804.bugfix b/changelog.d/4804.bugfix
new file mode 100644
index 0000000000..8f845662ab
--- /dev/null
+++ b/changelog.d/4804.bugfix
@@ -0,0 +1 @@
+Fixing encrypted non message events showing up as notification messages (eg when a participant joins, mutes or leaves a voice call)
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index fa58fc5aae..ee6ba9a3ac 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionSha256Sum=dd54e87b4d7aa8ff3c6afb0f7805aa121d4b70bca55b8c9b1b896eb103184582
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.2-all.zip
+distributionSha256Sum=c9490e938b221daf0094982288e4038deed954a3f12fb54cbf270ddf4e37d879
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml
index 8e86eab51a..07852c72aa 100644
--- a/library/ui-styles/src/main/res/values/dimens.xml
+++ b/library/ui-styles/src/main/res/values/dimens.xml
@@ -46,4 +46,9 @@
24dp
48dp
48dp
+
+
+ 56dp
+ 52dp
+ 1dp
\ No newline at end of file
diff --git a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt
index 2a0abd3d24..669e27edfd 100644
--- a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt
+++ b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt
@@ -152,6 +152,13 @@ class FlowSession(private val session: Session) {
}
}
+ fun liveUserAccountData(type: String): Flow> {
+ return session.accountDataService().getLiveUserAccountDataEvent(type).asFlow()
+ .startWith(session.coroutineDispatchers.io) {
+ session.accountDataService().getUserAccountDataEvent(type).toOptional()
+ }
+ }
+
fun liveRoomAccountData(types: Set): Flow> {
return session.accountDataService().getLiveRoomAccountDataEvents(types).asFlow()
.startWith(session.coroutineDispatchers.io) {
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index 49620676b3..3fb6b81505 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -160,7 +160,7 @@ dependencies {
implementation libs.apache.commonsImaging
// Phone number https://github.com/google/libphonenumber
- implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.39'
+ implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.40'
testImplementation libs.tests.junit
testImplementation 'org.robolectric:robolectric:4.7.3'
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
index 7d9c351410..9dd369f426 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
@@ -429,7 +429,17 @@ internal class DefaultCryptoService @Inject constructor(
val currentCount = syncResponse.deviceOneTimeKeysCount.signedCurve25519 ?: 0
oneTimeKeysUploader.updateOneTimeKeyCount(currentCount)
}
- if (isStarted()) {
+ // There is a limit of to_device events returned per sync.
+ // If we are in a case of such limited to_device sync we can't try to generate/upload
+ // new otk now, because there might be some pending olm pre-key to_device messages that would fail if we rotate
+ // the old otk too early. In this case we want to wait for the pending to_device before doing anything
+ // As per spec:
+ // If there is a large queue of send-to-device messages, the server should limit the number sent in each /sync response.
+ // 100 messages is recommended as a reasonable limit.
+ // The limit is not part of the spec, so it's probably safer to handle that when there are no more to_device ( so we are sure
+ // that there are no pending to_device
+ val toDevices = syncResponse.toDevice?.events.orEmpty()
+ if (isStarted() && toDevices.isEmpty()) {
// Make sure we process to-device messages before generating new one-time-keys #2782
deviceListManager.refreshOutdatedDeviceLists()
// The presence of device_unused_fallback_key_types indicates that the server supports fallback keys.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
index 1b0ccbb489..b988f2253c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
@@ -109,18 +109,23 @@ internal class FileUploader @Inject constructor(
filename: String?,
mimeType: String?,
progressListener: ProgressRequestBody.Listener? = null): ContentUploadResponse {
- val inputStream = withContext(Dispatchers.IO) {
- context.contentResolver.openInputStream(uri)
- } ?: throw FileNotFoundException()
- val workingFile = temporaryFileCreator.create()
- workingFile.outputStream().use {
- inputStream.copyTo(it)
- }
+ val workingFile = context.copyUriToTempFile(uri)
return uploadFile(workingFile, filename, mimeType, progressListener).also {
tryOrNull { workingFile.delete() }
}
}
+ private suspend fun Context.copyUriToTempFile(uri: Uri): File {
+ return withContext(Dispatchers.IO) {
+ val inputStream = contentResolver.openInputStream(uri) ?: throw FileNotFoundException()
+ val workingFile = temporaryFileCreator.create()
+ workingFile.outputStream().use {
+ inputStream.copyTo(it)
+ }
+ workingFile
+ }
+ }
+
private suspend fun upload(uploadBody: RequestBody,
filename: String?,
progressListener: ProgressRequestBody.Listener?): ContentUploadResponse {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt
index a19832c523..caf4158657 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt
@@ -68,7 +68,7 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto
}
override suspend fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String) {
- withContext(coroutineDispatchers.main) {
+ withContext(coroutineDispatchers.io) {
val response = fileUploader.uploadFromUri(newAvatarUri, fileName, MimeTypes.Jpeg)
setAvatarUrlTask.execute(SetAvatarUrlTask.Params(userId = userId, newAvatarUrl = response.contentUri))
userStore.updateAvatar(userId, response.contentUri)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt
index 3faa0c9488..b6ea7a68f7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt
@@ -30,6 +30,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
+import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.isTokenError
import org.matrix.android.sdk.api.logger.LoggerTag
@@ -71,6 +72,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
private var isStarted = false
private var isTokenValid = true
private var retryNoNetworkTask: TimerTask? = null
+ private var previousSyncResponseHasToDevice = false
private val activeCallListObserver = Observer> { activeCalls ->
if (activeCalls.isEmpty() && backgroundDetectionObserver.isInBackground) {
@@ -171,12 +173,15 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
if (state !is SyncState.Running) {
updateStateTo(SyncState.Running(afterPause = true))
}
- // No timeout after a pause
- val timeout = state.let { if (it is SyncState.Running && it.afterPause) 0 else DEFAULT_LONG_POOL_TIMEOUT }
+ val timeout = when {
+ previousSyncResponseHasToDevice -> 0L /* Force timeout to 0 */
+ state.let { it is SyncState.Running && it.afterPause } -> 0L /* No timeout after a pause */
+ else -> DEFAULT_LONG_POOL_TIMEOUT
+ }
Timber.tag(loggerTag.value).d("Execute sync request with timeout $timeout")
val params = SyncTask.Params(timeout, SyncPresence.Online)
val sync = syncScope.launch {
- doSync(params)
+ previousSyncResponseHasToDevice = doSync(params)
}
runBlocking {
sync.join()
@@ -203,10 +208,14 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
}
}
- private suspend fun doSync(params: SyncTask.Params) {
- try {
+ /**
+ * Will return true if the sync response contains some toDevice events.
+ */
+ private suspend fun doSync(params: SyncTask.Params): Boolean {
+ return try {
val syncResponse = syncTask.execute(params)
_syncFlow.emit(syncResponse)
+ syncResponse.toDevice?.events?.isNotEmpty().orFalse()
} catch (failure: Throwable) {
if (failure is Failure.NetworkConnection) {
canReachServer = false
@@ -229,6 +238,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
delay(RETRY_WAIT_TIME_MS)
}
}
+ false
} finally {
state.let {
if (it is SyncState.Running && it.afterPause) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt
index 763cd55714..2f1241f4d8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt
@@ -20,6 +20,7 @@ import androidx.work.BackoffPolicy
import androidx.work.ExistingWorkPolicy
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
+import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.failure.isTokenError
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.di.WorkManagerProvider
@@ -34,8 +35,8 @@ import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
-private const val DEFAULT_LONG_POOL_TIMEOUT = 6L
-private const val DEFAULT_DELAY_TIMEOUT = 30_000L
+private const val DEFAULT_LONG_POOL_TIMEOUT_SECONDS = 6L
+private const val DEFAULT_DELAY_MILLIS = 30_000L
/**
* Possible previous worker: None
@@ -47,9 +48,12 @@ internal class SyncWorker(context: Context, workerParameters: WorkerParameters,
@JsonClass(generateAdapter = true)
internal data class Params(
override val sessionId: String,
- val timeout: Long = DEFAULT_LONG_POOL_TIMEOUT,
- val delay: Long = DEFAULT_DELAY_TIMEOUT,
+ // In seconds
+ val timeout: Long = DEFAULT_LONG_POOL_TIMEOUT_SECONDS,
+ // In milliseconds
+ val delay: Long = DEFAULT_DELAY_MILLIS,
val periodic: Boolean = false,
+ val forceImmediate: Boolean = false,
override val lastFailureMessage: String? = null
) : SessionWorkerParams
@@ -65,13 +69,26 @@ internal class SyncWorker(context: Context, workerParameters: WorkerParameters,
Timber.i("Sync work starting")
return runCatching {
- doSync(params.timeout)
+ doSync(if (params.forceImmediate) 0 else params.timeout)
}.fold(
- {
+ { hasToDeviceEvents ->
Result.success().also {
if (params.periodic) {
- // we want to schedule another one after delay
- automaticallyBackgroundSync(workManagerProvider, params.sessionId, params.timeout, params.delay)
+ // we want to schedule another one after a delay, or immediately if hasToDeviceEvents
+ automaticallyBackgroundSync(
+ workManagerProvider = workManagerProvider,
+ sessionId = params.sessionId,
+ serverTimeoutInSeconds = params.timeout,
+ delayInSeconds = params.delay,
+ forceImmediate = hasToDeviceEvents
+ )
+ } else if (hasToDeviceEvents) {
+ // Previous response has toDevice events, request an immediate sync request
+ requireBackgroundSync(
+ workManagerProvider = workManagerProvider,
+ sessionId = params.sessionId,
+ serverTimeoutInSeconds = 0
+ )
}
}
},
@@ -92,16 +109,29 @@ internal class SyncWorker(context: Context, workerParameters: WorkerParameters,
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
}
- private suspend fun doSync(timeout: Long) {
+ /**
+ * Will return true if the sync response contains some toDevice events.
+ */
+ private suspend fun doSync(timeout: Long): Boolean {
val taskParams = SyncTask.Params(timeout * 1000, SyncPresence.Offline)
- syncTask.execute(taskParams)
+ val syncResponse = syncTask.execute(taskParams)
+ return syncResponse.toDevice?.events?.isNotEmpty().orFalse()
}
companion object {
private const val BG_SYNC_WORK_NAME = "BG_SYNCP"
- fun requireBackgroundSync(workManagerProvider: WorkManagerProvider, sessionId: String, serverTimeout: Long = 0) {
- val data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, 0L, false))
+ fun requireBackgroundSync(workManagerProvider: WorkManagerProvider,
+ sessionId: String,
+ serverTimeoutInSeconds: Long = 0) {
+ val data = WorkerParamsFactory.toData(
+ Params(
+ sessionId = sessionId,
+ timeout = serverTimeoutInSeconds,
+ delay = 0L,
+ periodic = false
+ )
+ )
val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder()
.setConstraints(WorkManagerProvider.workConstraints)
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
@@ -111,13 +141,24 @@ internal class SyncWorker(context: Context, workerParameters: WorkerParameters,
.enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest)
}
- fun automaticallyBackgroundSync(workManagerProvider: WorkManagerProvider, sessionId: String, serverTimeout: Long = 0, delayInSeconds: Long = 30) {
- val data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, delayInSeconds, true))
+ fun automaticallyBackgroundSync(workManagerProvider: WorkManagerProvider,
+ sessionId: String,
+ serverTimeoutInSeconds: Long = 0,
+ delayInSeconds: Long = 30,
+ forceImmediate: Boolean = false) {
+ val data = WorkerParamsFactory.toData(
+ Params(
+ sessionId = sessionId,
+ timeout = serverTimeoutInSeconds,
+ delay = delayInSeconds,
+ forceImmediate = forceImmediate
+ )
+ )
val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder()
.setConstraints(WorkManagerProvider.workConstraints)
.setInputData(data)
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
- .setInitialDelay(delayInSeconds, TimeUnit.SECONDS)
+ .setInitialDelay(if (forceImmediate) 0 else delayInSeconds, TimeUnit.SECONDS)
.build()
// Avoid risking multiple chains of syncs by replacing the existing chain
workManagerProvider.workManager
diff --git a/vector/build.gradle b/vector/build.gradle
index 18015fdf3b..4a26717782 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -140,7 +140,7 @@ android {
buildConfigField "String", "BUILD_NUMBER", "\"${buildNumber}\""
resValue "string", "build_number", "\"${buildNumber}\""
- buildConfigField "im.vector.app.features.VectorFeatures.LoginVersion", "LOGIN_VERSION", "im.vector.app.features.VectorFeatures.LoginVersion.V1"
+ buildConfigField "im.vector.app.features.VectorFeatures.LoginVariant", "LOGIN_VARIANT", "im.vector.app.features.VectorFeatures.LoginVariant.LEGACY"
buildConfigField "im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy", "outboundSessionKeySharingStrategy", "im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy.WhenTyping"
@@ -362,7 +362,7 @@ dependencies {
implementation 'com.facebook.stetho:stetho:1.6.0'
// Phone number https://github.com/google/libphonenumber
- implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.39'
+ implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.40'
// FlowBinding
implementation libs.github.flowBinding
diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt
index 8d22fc599f..ca5d26aaeb 100644
--- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt
+++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt
@@ -28,8 +28,8 @@ class DebugFeaturesStateFactory @Inject constructor(
return FeaturesState(listOf(
createEnumFeature(
label = "Login version",
- selection = debugFeatures.loginVersion(),
- default = defaultFeatures.loginVersion()
+ selection = debugFeatures.loginVariant(),
+ default = defaultFeatures.loginVariant()
)
))
}
diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt
index 0831609e4f..638509e76b 100644
--- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt
+++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt
@@ -38,8 +38,8 @@ class DebugVectorFeatures(
private val dataStore = context.dataStore
- override fun loginVersion(): VectorFeatures.LoginVersion {
- return readPreferences().getEnum() ?: vectorFeatures.loginVersion()
+ override fun loginVariant(): VectorFeatures.LoginVariant {
+ return readPreferences().getEnum() ?: vectorFeatures.loginVariant()
}
fun > hasEnumOverride(type: KClass) = readPreferences().containsEnum(type)
diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
index 91fb8bee3b..667c9e4fa8 100644
--- a/vector/src/main/AndroidManifest.xml
+++ b/vector/src/main/AndroidManifest.xml
@@ -137,7 +137,7 @@
android:windowSoftInputMode="adjustResize" />
diff --git a/vector/src/main/java/im/vector/app/core/extensions/MavericksViewModel.kt b/vector/src/main/java/im/vector/app/core/extensions/MavericksViewModel.kt
new file mode 100644
index 0000000000..6120a84d7c
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/extensions/MavericksViewModel.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.core.extensions
+
+import androidx.activity.ComponentActivity
+import com.airbnb.mvrx.ActivityViewModelContext
+import com.airbnb.mvrx.Mavericks
+import com.airbnb.mvrx.MavericksState
+import com.airbnb.mvrx.MavericksViewModel
+import com.airbnb.mvrx.MavericksViewModelProvider
+
+inline fun , reified S : MavericksState> ComponentActivity.lazyViewModel(): Lazy {
+ return lazy(mode = LazyThreadSafetyMode.NONE) {
+ MavericksViewModelProvider.get(
+ viewModelClass = VM::class.java,
+ stateClass = S::class.java,
+ viewModelContext = ActivityViewModelContext(this, intent.extras?.get(Mavericks.KEY_ARG)),
+ key = VM::class.java.name
+ )
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
index 57a3f53373..21419d55cf 100644
--- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
+++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
@@ -105,7 +105,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver
protected val viewModelProvider
get() = ViewModelProvider(this, viewModelFactory)
- protected fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) {
+ fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) {
viewEvents
.stream()
.onEach {
diff --git a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt
index e106f7f75f..58594be293 100644
--- a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt
+++ b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt
@@ -20,11 +20,12 @@ import im.vector.app.BuildConfig
interface VectorFeatures {
- fun loginVersion(): LoginVersion
+ fun loginVariant(): LoginVariant
- enum class LoginVersion {
- V1,
- V2
+ enum class LoginVariant {
+ LEGACY,
+ FTUE,
+ FTUE_WIP
}
enum class NotificationSettingsVersion {
@@ -34,5 +35,5 @@ interface VectorFeatures {
}
class DefaultVectorFeatures : VectorFeatures {
- override fun loginVersion(): VectorFeatures.LoginVersion = BuildConfig.LOGIN_VERSION
+ override fun loginVariant(): VectorFeatures.LoginVariant = BuildConfig.LOGIN_VARIANT
}
diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt
index ccc07ef118..c56b3ac832 100644
--- a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt
+++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt
@@ -26,24 +26,18 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewAnimationUtils
import android.view.animation.Animation
-import android.view.animation.AnimationSet
-import android.view.animation.OvershootInterpolator
-import android.view.animation.ScaleAnimation
import android.view.animation.TranslateAnimation
import android.widget.ImageButton
import android.widget.LinearLayout
import android.widget.PopupWindow
import androidx.core.view.doOnNextLayout
import androidx.core.view.isVisible
-import com.amulyakhare.textdrawable.TextDrawable
-import com.amulyakhare.textdrawable.util.ColorGenerator
import im.vector.app.R
-import im.vector.app.core.extensions.getMeasurements
+import im.vector.app.core.epoxy.onClick
import im.vector.app.core.utils.PERMISSIONS_EMPTY
import im.vector.app.core.utils.PERMISSIONS_FOR_PICKING_CONTACT
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
import im.vector.app.databinding.ViewAttachmentTypeSelectorBinding
-import im.vector.app.features.attachments.AttachmentTypeSelectorView.Callback
import kotlin.math.max
private const val ANIMATION_DURATION = 250
@@ -52,17 +46,16 @@ private const val ANIMATION_DURATION = 250
* This class is the view presenting choices for picking attachments.
* It will return result through [Callback].
*/
+
class AttachmentTypeSelectorView(context: Context,
inflater: LayoutInflater,
- var callback: Callback?) :
- PopupWindow(context) {
+ var callback: Callback?
+) : PopupWindow(context) {
interface Callback {
fun onTypeSelected(type: Type)
}
- private val iconColorGenerator = ColorGenerator.MATERIAL
-
private val views: ViewAttachmentTypeSelectorBinding
private var anchor: View? = null
@@ -85,35 +78,40 @@ class AttachmentTypeSelectorView(context: Context,
inputMethodMode = INPUT_METHOD_NOT_NEEDED
isFocusable = true
isTouchable = true
+
+ views.attachmentCloseButton.onClick {
+ dismiss()
+ }
}
- fun show(anchor: View, isKeyboardOpen: Boolean) {
+ private fun animateOpen() {
+ views.attachmentCloseButton.animate()
+ .setDuration(200)
+ .rotation(135f)
+ }
+
+ private fun animateClose() {
+ views.attachmentCloseButton.animate()
+ .setDuration(200)
+ .rotation(0f)
+ }
+
+ fun show(anchor: View) {
+ animateOpen()
+
this.anchor = anchor
val anchorCoordinates = IntArray(2)
anchor.getLocationOnScreen(anchorCoordinates)
- if (isKeyboardOpen) {
- showAtLocation(anchor, Gravity.NO_GRAVITY, 0, anchorCoordinates[1] + anchor.height)
- } else {
- val contentViewHeight = if (contentView.height == 0) {
- contentView.getMeasurements().second
- } else {
- contentView.height
- }
- showAtLocation(anchor, Gravity.NO_GRAVITY, 0, anchorCoordinates[1] - contentViewHeight)
- }
+ showAtLocation(anchor, Gravity.NO_GRAVITY, 0, anchorCoordinates[1])
+
contentView.doOnNextLayout {
animateWindowInCircular(anchor, contentView)
}
- animateButtonIn(views.attachmentGalleryButton, ANIMATION_DURATION / 2)
- animateButtonIn(views.attachmentCameraButton, ANIMATION_DURATION / 4)
- animateButtonIn(views.attachmentFileButton, ANIMATION_DURATION / 2)
- animateButtonIn(views.attachmentAudioButton, 0)
- animateButtonIn(views.attachmentContactButton, ANIMATION_DURATION / 4)
- animateButtonIn(views.attachmentStickersButton, ANIMATION_DURATION / 2)
- animateButtonIn(views.attachmentPollButton, ANIMATION_DURATION / 4)
}
override fun dismiss() {
+ animateClose()
+
val capturedAnchor = anchor
if (capturedAnchor != null) {
animateWindowOutCircular(capturedAnchor, contentView)
@@ -124,28 +122,18 @@ class AttachmentTypeSelectorView(context: Context,
fun setAttachmentVisibility(type: Type, isVisible: Boolean) {
when (type) {
- Type.CAMERA -> views.attachmentCameraButtonContainer
- Type.GALLERY -> views.attachmentGalleryButtonContainer
- Type.FILE -> views.attachmentFileButtonContainer
- Type.STICKER -> views.attachmentStickersButtonContainer
- Type.AUDIO -> views.attachmentAudioButtonContainer
- Type.CONTACT -> views.attachmentContactButtonContainer
- Type.POLL -> views.attachmentPollButtonContainer
+ Type.CAMERA -> views.attachmentCameraButton
+ Type.GALLERY -> views.attachmentGalleryButton
+ Type.FILE -> views.attachmentFileButton
+ Type.STICKER -> views.attachmentStickersButton
+ Type.AUDIO -> views.attachmentAudioButton
+ Type.CONTACT -> views.attachmentContactButton
+ Type.POLL -> views.attachmentPollButton
}.let {
it.isVisible = isVisible
}
}
- private fun animateButtonIn(button: View, delay: Int) {
- val animation = AnimationSet(true)
- val scale = ScaleAnimation(0.0f, 1.0f, 0.0f, 1.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.0f)
- animation.addAnimation(scale)
- animation.interpolator = OvershootInterpolator(1f)
- animation.duration = ANIMATION_DURATION.toLong()
- animation.startOffset = delay.toLong()
- button.startAnimation(animation)
- }
-
private fun animateWindowInCircular(anchor: View, contentView: View) {
val coordinates = getClickCoordinates(anchor, contentView)
val animator = ViewAnimationUtils.createCircularReveal(contentView,
@@ -157,12 +145,6 @@ class AttachmentTypeSelectorView(context: Context,
animator.start()
}
- private fun animateWindowInTranslate(contentView: View) {
- val animation = TranslateAnimation(0f, 0f, contentView.height.toFloat(), 0f)
- animation.duration = ANIMATION_DURATION.toLong()
- getContentView().startAnimation(animation)
- }
-
private fun animateWindowOutCircular(anchor: View, contentView: View) {
val coordinates = getClickCoordinates(anchor, contentView)
val animator = ViewAnimationUtils.createCircularReveal(getContentView(),
@@ -207,7 +189,6 @@ class AttachmentTypeSelectorView(context: Context,
}
private fun ImageButton.configure(type: Type): ImageButton {
- this.background = TextDrawable.builder().buildRound("", iconColorGenerator.getColor(type.ordinal))
this.setOnClickListener(TypeClickListener(type))
return this
}
diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
index 3221a5bf66..f73799d0e9 100644
--- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
@@ -168,11 +168,8 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
}
}
- private fun renderCreationSuccess(roomId: String?) {
- // Navigate to freshly created room
- if (roomId != null) {
- navigator.openRoom(this, roomId)
- }
+ private fun renderCreationSuccess(roomId: String) {
+ navigator.openRoom(this, roomId)
finish()
}
diff --git a/vector/src/main/java/im/vector/app/features/ftue/DefaultFTUEVariant.kt b/vector/src/main/java/im/vector/app/features/ftue/DefaultFTUEVariant.kt
new file mode 100644
index 0000000000..98b1f98df0
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/ftue/DefaultFTUEVariant.kt
@@ -0,0 +1,367 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.ftue
+
+import android.content.Intent
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.view.ViewCompat
+import androidx.core.view.children
+import androidx.core.view.isVisible
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.FragmentTransaction
+import com.airbnb.mvrx.withState
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import im.vector.app.R
+import im.vector.app.core.extensions.POP_BACK_STACK_EXCLUSIVE
+import im.vector.app.core.extensions.addFragment
+import im.vector.app.core.extensions.addFragmentToBackstack
+import im.vector.app.core.extensions.exhaustive
+import im.vector.app.core.platform.VectorBaseActivity
+import im.vector.app.databinding.ActivityLoginBinding
+import im.vector.app.features.home.HomeActivity
+import im.vector.app.features.login.LoginAction
+import im.vector.app.features.login.LoginCaptchaFragment
+import im.vector.app.features.login.LoginCaptchaFragmentArgument
+import im.vector.app.features.login.LoginConfig
+import im.vector.app.features.login.LoginFragment
+import im.vector.app.features.login.LoginGenericTextInputFormFragment
+import im.vector.app.features.login.LoginGenericTextInputFormFragmentArgument
+import im.vector.app.features.login.LoginMode
+import im.vector.app.features.login.LoginResetPasswordFragment
+import im.vector.app.features.login.LoginResetPasswordMailConfirmationFragment
+import im.vector.app.features.login.LoginResetPasswordSuccessFragment
+import im.vector.app.features.login.LoginServerSelectionFragment
+import im.vector.app.features.login.LoginServerUrlFormFragment
+import im.vector.app.features.login.LoginSignUpSignInSelectionFragment
+import im.vector.app.features.login.LoginSplashFragment
+import im.vector.app.features.login.LoginViewEvents
+import im.vector.app.features.login.LoginViewModel
+import im.vector.app.features.login.LoginViewState
+import im.vector.app.features.login.LoginWaitForEmailFragment
+import im.vector.app.features.login.LoginWaitForEmailFragmentArgument
+import im.vector.app.features.login.LoginWebFragment
+import im.vector.app.features.login.ServerType
+import im.vector.app.features.login.SignMode
+import im.vector.app.features.login.TextInputFormFragmentMode
+import im.vector.app.features.login.isSupported
+import im.vector.app.features.login.terms.LoginTermsFragment
+import im.vector.app.features.login.terms.LoginTermsFragmentArgument
+import im.vector.app.features.login.terms.toLocalizedLoginTerms
+import org.matrix.android.sdk.api.auth.registration.FlowResult
+import org.matrix.android.sdk.api.auth.registration.Stage
+import org.matrix.android.sdk.api.extensions.tryOrNull
+
+private const val FRAGMENT_REGISTRATION_STAGE_TAG = "FRAGMENT_REGISTRATION_STAGE_TAG"
+private const val FRAGMENT_LOGIN_TAG = "FRAGMENT_LOGIN_TAG"
+
+class DefaultFTUEVariant(
+ private val views: ActivityLoginBinding,
+ private val loginViewModel: LoginViewModel,
+ private val activity: VectorBaseActivity,
+ private val supportFragmentManager: FragmentManager
+) : FTUEVariant {
+
+ private val enterAnim = R.anim.enter_fade_in
+ private val exitAnim = R.anim.exit_fade_out
+
+ private val popEnterAnim = R.anim.no_anim
+ private val popExitAnim = R.anim.exit_fade_out
+
+ private val topFragment: Fragment?
+ get() = supportFragmentManager.findFragmentById(views.loginFragmentContainer.id)
+
+ private val commonOption: (FragmentTransaction) -> Unit = { ft ->
+ // Find the loginLogo on the current Fragment, this should not return null
+ (topFragment?.view as? ViewGroup)
+ // Find findViewById does not work, I do not know why
+ // findViewById(R.id.loginLogo)
+ ?.children
+ ?.firstOrNull { it.id == R.id.loginLogo }
+ ?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
+ ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
+ }
+
+ override fun initUiAndData(isFirstCreation: Boolean) {
+ if (isFirstCreation) {
+ addFirstFragment()
+ }
+
+ with(activity) {
+ loginViewModel.onEach {
+ updateWithState(it)
+ }
+ loginViewModel.observeViewEvents { handleLoginViewEvents(it) }
+ }
+
+ // Get config extra
+ val loginConfig = activity.intent.getParcelableExtra(FTUEActivity.EXTRA_CONFIG)
+ if (isFirstCreation) {
+ loginViewModel.handle(LoginAction.InitWith(loginConfig))
+ }
+ }
+
+ override fun setIsLoading(isLoading: Boolean) {
+ // do nothing
+ }
+
+ private fun addFirstFragment() {
+ activity.addFragment(views.loginFragmentContainer, LoginSplashFragment::class.java)
+ }
+
+ private fun handleLoginViewEvents(loginViewEvents: LoginViewEvents) {
+ when (loginViewEvents) {
+ is LoginViewEvents.RegistrationFlowResult -> {
+ // Check that all flows are supported by the application
+ if (loginViewEvents.flowResult.missingStages.any { !it.isSupported() }) {
+ // Display a popup to propose use web fallback
+ onRegistrationStageNotSupported()
+ } else {
+ if (loginViewEvents.isRegistrationStarted) {
+ // Go on with registration flow
+ handleRegistrationNavigation(loginViewEvents.flowResult)
+ } else {
+ // First ask for login and password
+ // I add a tag to indicate that this fragment is a registration stage.
+ // This way it will be automatically popped in when starting the next registration stage
+ activity.addFragmentToBackstack(views.loginFragmentContainer,
+ LoginFragment::class.java,
+ tag = FRAGMENT_REGISTRATION_STAGE_TAG,
+ option = commonOption
+ )
+ }
+ }
+ }
+ is LoginViewEvents.OutdatedHomeserver -> {
+ MaterialAlertDialogBuilder(activity)
+ .setTitle(R.string.login_error_outdated_homeserver_title)
+ .setMessage(R.string.login_error_outdated_homeserver_warning_content)
+ .setPositiveButton(R.string.ok, null)
+ .show()
+ Unit
+ }
+ is LoginViewEvents.OpenServerSelection ->
+ activity.addFragmentToBackstack(views.loginFragmentContainer,
+ LoginServerSelectionFragment::class.java,
+ option = { ft ->
+ activity.findViewById(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
+ // Disable transition of text
+ // findViewById(R.id.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
+ // No transition here now actually
+ // findViewById(R.id.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
+ // TODO Disabled because it provokes a flickering
+ // ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
+ })
+ is LoginViewEvents.OnServerSelectionDone -> onServerSelectionDone(loginViewEvents)
+ is LoginViewEvents.OnSignModeSelected -> onSignModeSelected(loginViewEvents)
+ is LoginViewEvents.OnLoginFlowRetrieved ->
+ activity.addFragmentToBackstack(views.loginFragmentContainer,
+ LoginSignUpSignInSelectionFragment::class.java,
+ option = commonOption)
+ is LoginViewEvents.OnWebLoginError -> onWebLoginError(loginViewEvents)
+ is LoginViewEvents.OnForgetPasswordClicked ->
+ activity.addFragmentToBackstack(views.loginFragmentContainer,
+ LoginResetPasswordFragment::class.java,
+ option = commonOption)
+ is LoginViewEvents.OnResetPasswordSendThreePidDone -> {
+ supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE)
+ activity.addFragmentToBackstack(views.loginFragmentContainer,
+ LoginResetPasswordMailConfirmationFragment::class.java,
+ option = commonOption)
+ }
+ is LoginViewEvents.OnResetPasswordMailConfirmationSuccess -> {
+ supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE)
+ activity.addFragmentToBackstack(views.loginFragmentContainer,
+ LoginResetPasswordSuccessFragment::class.java,
+ option = commonOption)
+ }
+ is LoginViewEvents.OnResetPasswordMailConfirmationSuccessDone -> {
+ // Go back to the login fragment
+ supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE)
+ }
+ is LoginViewEvents.OnSendEmailSuccess -> {
+ // Pop the enter email Fragment
+ supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE)
+ activity.addFragmentToBackstack(views.loginFragmentContainer,
+ LoginWaitForEmailFragment::class.java,
+ LoginWaitForEmailFragmentArgument(loginViewEvents.email),
+ tag = FRAGMENT_REGISTRATION_STAGE_TAG,
+ option = commonOption)
+ }
+ is LoginViewEvents.OnSendMsisdnSuccess -> {
+ // Pop the enter Msisdn Fragment
+ supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE)
+ activity.addFragmentToBackstack(views.loginFragmentContainer,
+ LoginGenericTextInputFormFragment::class.java,
+ LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, loginViewEvents.msisdn),
+ tag = FRAGMENT_REGISTRATION_STAGE_TAG,
+ option = commonOption)
+ }
+ is LoginViewEvents.Failure,
+ is LoginViewEvents.Loading ->
+ // This is handled by the Fragments
+ Unit
+ }.exhaustive
+ }
+
+ private fun updateWithState(loginViewState: LoginViewState) {
+ if (loginViewState.isUserLogged()) {
+ val intent = HomeActivity.newIntent(
+ activity,
+ accountCreation = loginViewState.signMode == SignMode.SignUp
+ )
+ activity.startActivity(intent)
+ activity.finish()
+ return
+ }
+
+ // Loading
+ views.loginLoading.isVisible = loginViewState.isLoading()
+ }
+
+ private fun onWebLoginError(onWebLoginError: LoginViewEvents.OnWebLoginError) {
+ // Pop the backstack
+ supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
+
+ // And inform the user
+ MaterialAlertDialogBuilder(activity)
+ .setTitle(R.string.dialog_title_error)
+ .setMessage(activity.getString(R.string.login_sso_error_message, onWebLoginError.description, onWebLoginError.errorCode))
+ .setPositiveButton(R.string.ok, null)
+ .show()
+ }
+
+ private fun onServerSelectionDone(loginViewEvents: LoginViewEvents.OnServerSelectionDone) {
+ when (loginViewEvents.serverType) {
+ ServerType.MatrixOrg -> Unit // In this case, we wait for the login flow
+ ServerType.EMS,
+ ServerType.Other -> activity.addFragmentToBackstack(views.loginFragmentContainer,
+ LoginServerUrlFormFragment::class.java,
+ option = commonOption)
+ ServerType.Unknown -> Unit /* Should not happen */
+ }
+ }
+
+ private fun onSignModeSelected(loginViewEvents: LoginViewEvents.OnSignModeSelected) = withState(loginViewModel) { state ->
+ // state.signMode could not be ready yet. So use value from the ViewEvent
+ when (loginViewEvents.signMode) {
+ SignMode.Unknown -> error("Sign mode has to be set before calling this method")
+ SignMode.SignUp -> {
+ // This is managed by the LoginViewEvents
+ }
+ SignMode.SignIn -> {
+ // It depends on the LoginMode
+ when (state.loginMode) {
+ LoginMode.Unknown,
+ is LoginMode.Sso -> error("Developer error")
+ is LoginMode.SsoAndPassword,
+ LoginMode.Password -> activity.addFragmentToBackstack(views.loginFragmentContainer,
+ LoginFragment::class.java,
+ tag = FRAGMENT_LOGIN_TAG,
+ option = commonOption)
+ LoginMode.Unsupported -> onLoginModeNotSupported(state.loginModeSupportedTypes)
+ }.exhaustive
+ }
+ SignMode.SignInWithMatrixId -> activity.addFragmentToBackstack(views.loginFragmentContainer,
+ LoginFragment::class.java,
+ tag = FRAGMENT_LOGIN_TAG,
+ option = commonOption)
+ }.exhaustive
+ }
+
+ /**
+ * Handle the SSO redirection here
+ */
+ override fun onNewIntent(intent: Intent?) {
+ intent?.data
+ ?.let { tryOrNull { it.getQueryParameter("loginToken") } }
+ ?.let { loginViewModel.handle(LoginAction.LoginWithToken(it)) }
+ }
+
+ private fun onRegistrationStageNotSupported() {
+ MaterialAlertDialogBuilder(activity)
+ .setTitle(R.string.app_name)
+ .setMessage(activity.getString(R.string.login_registration_not_supported))
+ .setPositiveButton(R.string.yes) { _, _ ->
+ activity.addFragmentToBackstack(views.loginFragmentContainer,
+ LoginWebFragment::class.java,
+ option = commonOption)
+ }
+ .setNegativeButton(R.string.no, null)
+ .show()
+ }
+
+ private fun onLoginModeNotSupported(supportedTypes: List) {
+ MaterialAlertDialogBuilder(activity)
+ .setTitle(R.string.app_name)
+ .setMessage(activity.getString(R.string.login_mode_not_supported, supportedTypes.joinToString { "'$it'" }))
+ .setPositiveButton(R.string.yes) { _, _ ->
+ activity.addFragmentToBackstack(views.loginFragmentContainer,
+ LoginWebFragment::class.java,
+ option = commonOption)
+ }
+ .setNegativeButton(R.string.no, null)
+ .show()
+ }
+
+ private fun handleRegistrationNavigation(flowResult: FlowResult) {
+ // Complete all mandatory stages first
+ val mandatoryStage = flowResult.missingStages.firstOrNull { it.mandatory }
+
+ if (mandatoryStage != null) {
+ doStage(mandatoryStage)
+ } else {
+ // Consider optional stages
+ val optionalStage = flowResult.missingStages.firstOrNull { !it.mandatory && it !is Stage.Dummy }
+ if (optionalStage == null) {
+ // Should not happen...
+ } else {
+ doStage(optionalStage)
+ }
+ }
+ }
+
+ private fun doStage(stage: Stage) {
+ // Ensure there is no fragment for registration stage in the backstack
+ supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE)
+
+ when (stage) {
+ is Stage.ReCaptcha -> activity.addFragmentToBackstack(views.loginFragmentContainer,
+ LoginCaptchaFragment::class.java,
+ LoginCaptchaFragmentArgument(stage.publicKey),
+ tag = FRAGMENT_REGISTRATION_STAGE_TAG,
+ option = commonOption)
+ is Stage.Email -> activity.addFragmentToBackstack(views.loginFragmentContainer,
+ LoginGenericTextInputFormFragment::class.java,
+ LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetEmail, stage.mandatory),
+ tag = FRAGMENT_REGISTRATION_STAGE_TAG,
+ option = commonOption)
+ is Stage.Msisdn -> activity.addFragmentToBackstack(views.loginFragmentContainer,
+ LoginGenericTextInputFormFragment::class.java,
+ LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetMsisdn, stage.mandatory),
+ tag = FRAGMENT_REGISTRATION_STAGE_TAG,
+ option = commonOption)
+ is Stage.Terms -> activity.addFragmentToBackstack(views.loginFragmentContainer,
+ LoginTermsFragment::class.java,
+ LoginTermsFragmentArgument(stage.policies.toLocalizedLoginTerms(activity.getString(R.string.resources_language))),
+ tag = FRAGMENT_REGISTRATION_STAGE_TAG,
+ option = commonOption)
+ else -> Unit // Should not happen
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/ftue/FTUEActivity.kt b/vector/src/main/java/im/vector/app/features/ftue/FTUEActivity.kt
new file mode 100644
index 0000000000..805e39c48d
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/ftue/FTUEActivity.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.ftue
+
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import com.google.android.material.appbar.MaterialToolbar
+import dagger.hilt.android.AndroidEntryPoint
+import im.vector.app.core.extensions.lazyViewModel
+import im.vector.app.core.platform.ToolbarConfigurable
+import im.vector.app.core.platform.VectorBaseActivity
+import im.vector.app.core.platform.lifecycleAwareLazy
+import im.vector.app.databinding.ActivityLoginBinding
+import im.vector.app.features.login.LoginConfig
+import im.vector.app.features.pin.UnlockedActivity
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class FTUEActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedActivity {
+
+ private val ftueVariant by lifecycleAwareLazy {
+ ftueVariantFactory.create(this, loginViewModel = lazyViewModel(), loginViewModel2 = lazyViewModel())
+ }
+
+ @Inject lateinit var ftueVariantFactory: FTUEVariantFactory
+
+ override fun getBinding() = ActivityLoginBinding.inflate(layoutInflater)
+
+ override fun getCoordinatorLayout() = views.coordinatorLayout
+
+ override fun configure(toolbar: MaterialToolbar) {
+ configureToolbar(toolbar)
+ }
+
+ override fun onNewIntent(intent: Intent?) {
+ super.onNewIntent(intent)
+ ftueVariant.onNewIntent(intent)
+ }
+
+ override fun initUiAndData() {
+ ftueVariant.initUiAndData(isFirstCreation())
+ }
+
+ // Hack for AccountCreatedFragment
+ fun setIsLoading(isLoading: Boolean) {
+ ftueVariant.setIsLoading(isLoading)
+ }
+
+ companion object {
+ const val EXTRA_CONFIG = "EXTRA_CONFIG"
+
+ fun newIntent(context: Context, loginConfig: LoginConfig?): Intent {
+ return Intent(context, FTUEActivity::class.java).apply {
+ putExtra(EXTRA_CONFIG, loginConfig)
+ }
+ }
+
+ fun redirectIntent(context: Context, data: Uri?): Intent {
+ return Intent(context, FTUEActivity::class.java).apply {
+ setData(data)
+ }
+ }
+ }
+}
+
+interface FTUEVariant {
+ fun onNewIntent(intent: Intent?)
+ fun initUiAndData(isFirstCreation: Boolean)
+ fun setIsLoading(isLoading: Boolean)
+}
diff --git a/vector/src/main/java/im/vector/app/features/ftue/FTUEVariantFactory.kt b/vector/src/main/java/im/vector/app/features/ftue/FTUEVariantFactory.kt
new file mode 100644
index 0000000000..7efd6023fe
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/ftue/FTUEVariantFactory.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.ftue
+
+import im.vector.app.features.VectorFeatures
+import im.vector.app.features.login.LoginViewModel
+import im.vector.app.features.login2.LoginViewModel2
+import javax.inject.Inject
+
+class FTUEVariantFactory @Inject constructor(
+ private val vectorFeatures: VectorFeatures,
+) {
+
+ fun create(activity: FTUEActivity, loginViewModel: Lazy, loginViewModel2: Lazy) = when (vectorFeatures.loginVariant()) {
+ VectorFeatures.LoginVariant.LEGACY -> error("Legacy is not supported by the FTUE")
+ VectorFeatures.LoginVariant.FTUE -> DefaultFTUEVariant(
+ views = activity.getBinding(),
+ loginViewModel = loginViewModel.value,
+ activity = activity,
+ supportFragmentManager = activity.supportFragmentManager
+ )
+ VectorFeatures.LoginVariant.FTUE_WIP -> FTUEWipVariant(
+ views = activity.getBinding(),
+ loginViewModel = loginViewModel2.value,
+ activity = activity,
+ supportFragmentManager = activity.supportFragmentManager
+ )
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginActivity2.kt b/vector/src/main/java/im/vector/app/features/ftue/FTUEWipVariant.kt
similarity index 70%
rename from vector/src/main/java/im/vector/app/features/login2/LoginActivity2.kt
rename to vector/src/main/java/im/vector/app/features/ftue/FTUEWipVariant.kt
index ce9d9f762e..c1fc49db00 100644
--- a/vector/src/main/java/im/vector/app/features/login2/LoginActivity2.kt
+++ b/vector/src/main/java/im/vector/app/features/ftue/FTUEWipVariant.kt
@@ -1,11 +1,11 @@
/*
- * Copyright 2019 New Vector Ltd
+ * Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,11 +14,9 @@
* limitations under the License.
*/
-package im.vector.app.features.login2
+package im.vector.app.features.ftue
-import android.content.Context
import android.content.Intent
-import android.net.Uri
import android.view.View
import android.view.ViewGroup
import androidx.core.view.ViewCompat
@@ -27,17 +25,13 @@ import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
-import com.airbnb.mvrx.viewModel
-import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
import im.vector.app.core.extensions.POP_BACK_STACK_EXCLUSIVE
import im.vector.app.core.extensions.addFragment
import im.vector.app.core.extensions.addFragmentToBackstack
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.resetBackstack
-import im.vector.app.core.platform.ToolbarConfigurable
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivityLoginBinding
import im.vector.app.features.home.HomeActivity
@@ -49,20 +43,41 @@ import im.vector.app.features.login.TextInputFormFragmentMode
import im.vector.app.features.login.isSupported
import im.vector.app.features.login.terms.LoginTermsFragmentArgument
import im.vector.app.features.login.terms.toLocalizedLoginTerms
+import im.vector.app.features.login2.LoginAction2
+import im.vector.app.features.login2.LoginCaptchaFragment2
+import im.vector.app.features.login2.LoginFragmentSigninPassword2
+import im.vector.app.features.login2.LoginFragmentSigninUsername2
+import im.vector.app.features.login2.LoginFragmentSignupPassword2
+import im.vector.app.features.login2.LoginFragmentSignupUsername2
+import im.vector.app.features.login2.LoginFragmentToAny2
+import im.vector.app.features.login2.LoginGenericTextInputFormFragment2
+import im.vector.app.features.login2.LoginResetPasswordFragment2
+import im.vector.app.features.login2.LoginResetPasswordMailConfirmationFragment2
+import im.vector.app.features.login2.LoginResetPasswordSuccessFragment2
+import im.vector.app.features.login2.LoginServerSelectionFragment2
+import im.vector.app.features.login2.LoginServerUrlFormFragment2
+import im.vector.app.features.login2.LoginSplashSignUpSignInSelectionFragment2
+import im.vector.app.features.login2.LoginSsoOnlyFragment2
+import im.vector.app.features.login2.LoginViewEvents2
+import im.vector.app.features.login2.LoginViewModel2
+import im.vector.app.features.login2.LoginViewState2
+import im.vector.app.features.login2.LoginWaitForEmailFragment2
+import im.vector.app.features.login2.LoginWebFragment2
import im.vector.app.features.login2.created.AccountCreatedFragment
import im.vector.app.features.login2.terms.LoginTermsFragment2
-import im.vector.app.features.pin.UnlockedActivity
import org.matrix.android.sdk.api.auth.registration.FlowResult
import org.matrix.android.sdk.api.auth.registration.Stage
import org.matrix.android.sdk.api.extensions.tryOrNull
-/**
- * The LoginActivity manages the fragment navigation and also display the loading View
- */
-@AndroidEntryPoint
-open class LoginActivity2 : VectorBaseActivity(), ToolbarConfigurable, UnlockedActivity {
+private const val FRAGMENT_REGISTRATION_STAGE_TAG = "FRAGMENT_REGISTRATION_STAGE_TAG"
+private const val FRAGMENT_LOGIN_TAG = "FRAGMENT_LOGIN_TAG"
- private val loginViewModel: LoginViewModel2 by viewModel()
+class FTUEWipVariant(
+ private val views: ActivityLoginBinding,
+ private val loginViewModel: LoginViewModel2,
+ private val activity: VectorBaseActivity,
+ private val supportFragmentManager: FragmentManager
+) : FTUEVariant {
private val enterAnim = R.anim.enter_fade_in
private val exitAnim = R.anim.exit_fade_out
@@ -76,39 +91,36 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC
private val commonOption: (FragmentTransaction) -> Unit = { ft ->
// Find the loginLogo on the current Fragment, this should not return null
(topFragment?.view as? ViewGroup)
- // Find findViewById does not work, I do not know why
- // findViewById(R.id.loginLogo)
+ // Find activity.findViewById does not work, I do not know why
+ // activity.findViewById(views.loginLogo)
?.children
?.firstOrNull { it.id == R.id.loginLogo }
?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
}
- final override fun getBinding() = ActivityLoginBinding.inflate(layoutInflater)
-
- override fun getCoordinatorLayout() = views.coordinatorLayout
-
- override fun initUiAndData() {
- if (isFirstCreation()) {
+ override fun initUiAndData(isFirstCreation: Boolean) {
+ if (isFirstCreation) {
addFirstFragment()
}
- loginViewModel.onEach {
- updateWithState(it)
+ with(activity) {
+ loginViewModel.onEach {
+ updateWithState(it)
+ }
+ loginViewModel.observeViewEvents { handleLoginViewEvents(it) }
}
- loginViewModel.observeViewEvents { handleLoginViewEvents(it) }
-
// Get config extra
- val loginConfig = intent.getParcelableExtra(EXTRA_CONFIG)
- if (isFirstCreation()) {
+ val loginConfig = activity.intent.getParcelableExtra(FTUEActivity.EXTRA_CONFIG)
+ if (isFirstCreation) {
// TODO Check this
loginViewModel.handle(LoginAction2.InitWith(loginConfig))
}
}
- protected open fun addFirstFragment() {
- addFragment(views.loginFragmentContainer, LoginSplashSignUpSignInSelectionFragment2::class.java)
+ private fun addFirstFragment() {
+ activity.addFragment(views.loginFragmentContainer, LoginSplashSignUpSignInSelectionFragment2::class.java)
}
private fun handleLoginViewEvents(event: LoginViewEvents2) {
@@ -127,7 +139,7 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC
// First ask for login and password
// I add a tag to indicate that this fragment is a registration stage.
// This way it will be automatically popped in when starting the next registration stage
- addFragmentToBackstack(views.loginFragmentContainer,
+ activity.addFragmentToBackstack(views.loginFragmentContainer,
LoginFragment2::class.java,
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
option = commonOption
@@ -138,7 +150,7 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC
}
}
is LoginViewEvents2.OutdatedHomeserver -> {
- MaterialAlertDialogBuilder(this)
+ MaterialAlertDialogBuilder(activity)
.setTitle(R.string.login_error_outdated_homeserver_title)
.setMessage(R.string.login_error_outdated_homeserver_warning_content)
.setPositiveButton(R.string.ok, null)
@@ -146,54 +158,54 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC
Unit
}
is LoginViewEvents2.OpenServerSelection ->
- addFragmentToBackstack(views.loginFragmentContainer,
+ activity.addFragmentToBackstack(views.loginFragmentContainer,
LoginServerSelectionFragment2::class.java,
option = { ft ->
- findViewById(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
+ activity.findViewById(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
// Disable transition of text
- // findViewById(R.id.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
+ // activity.findViewById(views.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
// No transition here now actually
- // findViewById(R.id.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
+ // activity.findViewById(views.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
// TODO Disabled because it provokes a flickering
// ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
})
is LoginViewEvents2.OpenHomeServerUrlFormScreen -> {
- addFragmentToBackstack(views.loginFragmentContainer,
+ activity.addFragmentToBackstack(views.loginFragmentContainer,
LoginServerUrlFormFragment2::class.java,
option = commonOption)
}
is LoginViewEvents2.OpenSignInEnterIdentifierScreen -> {
- addFragmentToBackstack(views.loginFragmentContainer,
+ activity.addFragmentToBackstack(views.loginFragmentContainer,
LoginFragmentSigninUsername2::class.java,
option = { ft ->
- findViewById(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
+ activity.findViewById(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
// Disable transition of text
- // findViewById(R.id.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
+ // activity.findViewById(views.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
// No transition here now actually
- // findViewById(R.id.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
+ // activity.findViewById(views.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
// TODO Disabled because it provokes a flickering
// ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
})
}
is LoginViewEvents2.OpenSsoOnlyScreen -> {
- addFragmentToBackstack(views.loginFragmentContainer,
+ activity.addFragmentToBackstack(views.loginFragmentContainer,
LoginSsoOnlyFragment2::class.java,
option = commonOption)
}
is LoginViewEvents2.OnWebLoginError -> onWebLoginError(event)
is LoginViewEvents2.OpenResetPasswordScreen ->
- addFragmentToBackstack(views.loginFragmentContainer,
+ activity.addFragmentToBackstack(views.loginFragmentContainer,
LoginResetPasswordFragment2::class.java,
option = commonOption)
is LoginViewEvents2.OnResetPasswordSendThreePidDone -> {
supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE)
- addFragmentToBackstack(views.loginFragmentContainer,
+ activity.addFragmentToBackstack(views.loginFragmentContainer,
LoginResetPasswordMailConfirmationFragment2::class.java,
option = commonOption)
}
is LoginViewEvents2.OnResetPasswordMailConfirmationSuccess -> {
supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE)
- addFragmentToBackstack(views.loginFragmentContainer,
+ activity.addFragmentToBackstack(views.loginFragmentContainer,
LoginResetPasswordSuccessFragment2::class.java,
option = commonOption)
}
@@ -202,37 +214,37 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC
supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE)
}
is LoginViewEvents2.OnSendEmailSuccess ->
- addFragmentToBackstack(views.loginFragmentContainer,
+ activity.addFragmentToBackstack(views.loginFragmentContainer,
LoginWaitForEmailFragment2::class.java,
LoginWaitForEmailFragmentArgument(event.email),
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
option = commonOption)
is LoginViewEvents2.OpenSigninPasswordScreen -> {
- addFragmentToBackstack(views.loginFragmentContainer,
+ activity.addFragmentToBackstack(views.loginFragmentContainer,
LoginFragmentSigninPassword2::class.java,
tag = FRAGMENT_LOGIN_TAG,
option = commonOption)
}
is LoginViewEvents2.OpenSignupPasswordScreen -> {
- addFragmentToBackstack(views.loginFragmentContainer,
+ activity.addFragmentToBackstack(views.loginFragmentContainer,
LoginFragmentSignupPassword2::class.java,
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
option = commonOption)
}
is LoginViewEvents2.OpenSignUpChooseUsernameScreen -> {
- addFragmentToBackstack(views.loginFragmentContainer,
+ activity.addFragmentToBackstack(views.loginFragmentContainer,
LoginFragmentSignupUsername2::class.java,
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
option = commonOption)
}
is LoginViewEvents2.OpenSignInWithAnythingScreen -> {
- addFragmentToBackstack(views.loginFragmentContainer,
+ activity.addFragmentToBackstack(views.loginFragmentContainer,
LoginFragmentToAny2::class.java,
tag = FRAGMENT_LOGIN_TAG,
option = commonOption)
}
is LoginViewEvents2.OnSendMsisdnSuccess ->
- addFragmentToBackstack(views.loginFragmentContainer,
+ activity.addFragmentToBackstack(views.loginFragmentContainer,
LoginGenericTextInputFormFragment2::class.java,
LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, event.msisdn),
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
@@ -250,14 +262,14 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC
private fun handleCancelRegistration() {
// Cleanup the back stack
- resetBackstack()
+ activity.resetBackstack()
}
private fun handleOnSessionCreated(event: LoginViewEvents2.OnSessionCreated) {
if (event.newAccount) {
// Propose to set avatar and display name
// Back on this Fragment will finish the Activity
- addFragmentToBackstack(views.loginFragmentContainer,
+ activity.addFragmentToBackstack(views.loginFragmentContainer,
AccountCreatedFragment::class.java,
option = commonOption)
} else {
@@ -267,11 +279,11 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC
private fun terminate(newAccount: Boolean) {
val intent = HomeActivity.newIntent(
- this,
+ activity,
accountCreation = newAccount
)
- startActivity(intent)
- finish()
+ activity.startActivity(intent)
+ activity.finish()
}
private fun updateWithState(LoginViewState2: LoginViewState2) {
@@ -280,7 +292,7 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC
}
// Hack for AccountCreatedFragment
- fun setIsLoading(isLoading: Boolean) {
+ override fun setIsLoading(isLoading: Boolean) {
views.loginLoading.isVisible = isLoading
}
@@ -289,9 +301,9 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC
supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
// And inform the user
- MaterialAlertDialogBuilder(this)
+ MaterialAlertDialogBuilder(activity)
.setTitle(R.string.dialog_title_error)
- .setMessage(getString(R.string.login_sso_error_message, onWebLoginError.description, onWebLoginError.errorCode))
+ .setMessage(activity.getString(R.string.login_sso_error_message, onWebLoginError.description, onWebLoginError.errorCode))
.setPositiveButton(R.string.ok, null)
.show()
}
@@ -300,19 +312,17 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC
* Handle the SSO redirection here
*/
override fun onNewIntent(intent: Intent?) {
- super.onNewIntent(intent)
-
intent?.data
?.let { tryOrNull { it.getQueryParameter("loginToken") } }
?.let { loginViewModel.handle(LoginAction2.LoginWithToken(it)) }
}
private fun onRegistrationStageNotSupported() {
- MaterialAlertDialogBuilder(this)
+ MaterialAlertDialogBuilder(activity)
.setTitle(R.string.app_name)
- .setMessage(getString(R.string.login_registration_not_supported))
+ .setMessage(activity.getString(R.string.login_registration_not_supported))
.setPositiveButton(R.string.yes) { _, _ ->
- addFragmentToBackstack(views.loginFragmentContainer,
+ activity.addFragmentToBackstack(views.loginFragmentContainer,
LoginWebFragment2::class.java,
option = commonOption)
}
@@ -321,11 +331,11 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC
}
private fun onLoginModeNotSupported(supportedTypes: List) {
- MaterialAlertDialogBuilder(this)
+ MaterialAlertDialogBuilder(activity)
.setTitle(R.string.app_name)
- .setMessage(getString(R.string.login_mode_not_supported, supportedTypes.joinToString { "'$it'" }))
+ .setMessage(activity.getString(R.string.login_mode_not_supported, supportedTypes.joinToString { "'$it'" }))
.setPositiveButton(R.string.yes) { _, _ ->
- addFragmentToBackstack(views.loginFragmentContainer,
+ activity.addFragmentToBackstack(views.loginFragmentContainer,
LoginWebFragment2::class.java,
option = commonOption)
}
@@ -355,53 +365,27 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC
supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE)
when (stage) {
- is Stage.ReCaptcha -> addFragmentToBackstack(views.loginFragmentContainer,
+ is Stage.ReCaptcha -> activity.addFragmentToBackstack(views.loginFragmentContainer,
LoginCaptchaFragment2::class.java,
LoginCaptchaFragmentArgument(stage.publicKey),
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
option = commonOption)
- is Stage.Email -> addFragmentToBackstack(views.loginFragmentContainer,
+ is Stage.Email -> activity.addFragmentToBackstack(views.loginFragmentContainer,
LoginGenericTextInputFormFragment2::class.java,
LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetEmail, stage.mandatory),
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
option = commonOption)
- is Stage.Msisdn -> addFragmentToBackstack(views.loginFragmentContainer,
+ is Stage.Msisdn -> activity.addFragmentToBackstack(views.loginFragmentContainer,
LoginGenericTextInputFormFragment2::class.java,
LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetMsisdn, stage.mandatory),
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
option = commonOption)
- is Stage.Terms -> addFragmentToBackstack(views.loginFragmentContainer,
+ is Stage.Terms -> activity.addFragmentToBackstack(views.loginFragmentContainer,
LoginTermsFragment2::class.java,
- LoginTermsFragmentArgument(stage.policies.toLocalizedLoginTerms(getString(R.string.resources_language))),
+ LoginTermsFragmentArgument(stage.policies.toLocalizedLoginTerms(activity.getString(R.string.resources_language))),
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
option = commonOption)
else -> Unit // Should not happen
}
}
-
- override fun configure(toolbar: MaterialToolbar) {
- configureToolbar(toolbar)
- }
-
- companion object {
- private const val FRAGMENT_REGISTRATION_STAGE_TAG = "FRAGMENT_REGISTRATION_STAGE_TAG"
- private const val FRAGMENT_LOGIN_TAG = "FRAGMENT_LOGIN_TAG"
-
- private const val EXTRA_CONFIG = "EXTRA_CONFIG"
-
- // Note that the domain can be displayed to the user for confirmation that he trusts it. So use a human readable string
- const val VECTOR_REDIRECT_URL = "element://connect"
-
- fun newIntent(context: Context, loginConfig: LoginConfig?): Intent {
- return Intent(context, LoginActivity2::class.java).apply {
- putExtra(EXTRA_CONFIG, loginConfig)
- }
- }
-
- fun redirectIntent(context: Context, data: Uri?): Intent {
- return Intent(context, LoginActivity2::class.java).apply {
- setData(data)
- }
- }
- }
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
index 652451c1ff..d9475a5e71 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
@@ -1450,7 +1450,7 @@ class TimelineFragment @Inject constructor(
AttachmentTypeSelectorView.Type.POLL,
vectorPreferences.labsEnablePolls() && !isThreadTimeLine())
}
- attachmentTypeSelector.show(views.composerLayout.views.attachmentButton, keyboardStateUtils.isKeyboardShowing)
+ attachmentTypeSelector.show(views.composerLayout.views.attachmentButton)
}
override fun onSendMessage(text: CharSequence) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/ComposerEditText.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/ComposerEditText.kt
index 03107fd3f7..c751053cdf 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/ComposerEditText.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/ComposerEditText.kt
@@ -24,18 +24,21 @@ import android.text.Editable
import android.util.AttributeSet
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputConnection
+import androidx.appcompat.widget.AppCompatEditText
import androidx.core.view.OnReceiveContentListener
import androidx.core.view.ViewCompat
import androidx.core.view.inputmethod.EditorInfoCompat
import androidx.core.view.inputmethod.InputConnectionCompat
-import com.vanniktech.emoji.EmojiEditText
import im.vector.app.core.extensions.ooi
import im.vector.app.core.platform.SimpleTextWatcher
import im.vector.app.features.html.PillImageSpan
import timber.log.Timber
-class ComposerEditText @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = android.R.attr.editTextStyle) :
- EmojiEditText(context, attrs, defStyleAttr) {
+class ComposerEditText @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = android.R.attr.editTextStyle
+) : AppCompatEditText(context, attrs, defStyleAttr) {
interface Callback {
fun onRichContentSelected(contentUri: Uri): Boolean
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt
index 1d30136f27..14d9cce28a 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt
@@ -44,7 +44,7 @@ class EncryptionItemFactory @Inject constructor(
if (!event.root.isStateEvent()) {
return null
}
- val algorithm = event.root.getClearContent().toModel()?.algorithm
+ val algorithm = event.root.content.toModel()?.algorithm
val informationData = informationDataFactory.create(params)
val attributes = messageItemAttributesFactory.create(null, informationData, params.callback)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt
index 382962f98d..523fb8e682 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt
@@ -34,7 +34,7 @@ class RoomCreateItemFactory @Inject constructor(private val stringProvider: Stri
fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? {
val event = params.event
- val createRoomContent = event.root.getClearContent().toModel() ?: return null
+ val createRoomContent = event.root.content.toModel() ?: return null
val predecessorId = createRoomContent.predecessor?.roomId ?: return defaultRendering(params)
val roomLink = session.permalinkService().createRoomPermalink(predecessorId) ?: return null
val text = span {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt
index 1e915d2b29..861f5dd546 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt
@@ -54,67 +54,83 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
params.rootThreadEventId,
params.isFromThreadTimeline())
}
- when (event.root.getClearType()) {
- // Message itemsX
- EventType.STICKER,
- EventType.POLL_START,
- EventType.MESSAGE -> messageItemFactory.create(params)
- EventType.STATE_ROOM_TOMBSTONE,
- EventType.STATE_ROOM_NAME,
- EventType.STATE_ROOM_TOPIC,
- EventType.STATE_ROOM_AVATAR,
- EventType.STATE_ROOM_MEMBER,
- EventType.STATE_ROOM_THIRD_PARTY_INVITE,
- EventType.STATE_ROOM_CANONICAL_ALIAS,
- EventType.STATE_ROOM_JOIN_RULES,
- EventType.STATE_ROOM_HISTORY_VISIBILITY,
- EventType.STATE_ROOM_SERVER_ACL,
- EventType.STATE_ROOM_GUEST_ACCESS,
- EventType.REDACTION,
- EventType.STATE_ROOM_ALIASES,
- EventType.KEY_VERIFICATION_ACCEPT,
- EventType.KEY_VERIFICATION_START,
- EventType.KEY_VERIFICATION_KEY,
- EventType.KEY_VERIFICATION_READY,
- EventType.KEY_VERIFICATION_MAC,
- EventType.CALL_CANDIDATES,
- EventType.CALL_REPLACES,
- EventType.CALL_SELECT_ANSWER,
- EventType.CALL_NEGOTIATE,
- EventType.REACTION,
- EventType.STATE_SPACE_CHILD,
- EventType.STATE_SPACE_PARENT,
- EventType.STATE_ROOM_POWER_LEVELS,
- EventType.POLL_RESPONSE,
- EventType.POLL_END -> noticeItemFactory.create(params)
- EventType.STATE_ROOM_WIDGET_LEGACY,
- EventType.STATE_ROOM_WIDGET -> widgetItemFactory.create(params)
- EventType.STATE_ROOM_ENCRYPTION -> encryptionItemFactory.create(params)
- // State room create
- EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(params)
- // Calls
- EventType.CALL_INVITE,
- EventType.CALL_HANGUP,
- EventType.CALL_REJECT,
- EventType.CALL_ANSWER -> callItemFactory.create(params)
- // Crypto
- EventType.ENCRYPTED -> {
- if (event.root.isRedacted()) {
- // Redacted event, let the MessageItemFactory handle it
- messageItemFactory.create(params)
- } else {
- encryptedItemFactory.create(params)
+
+ // Manage state event differently, to check validity
+ if (event.root.isStateEvent()) {
+ // state event are not e2e
+ when (event.root.type) {
+ EventType.STATE_ROOM_TOMBSTONE,
+ EventType.STATE_ROOM_NAME,
+ EventType.STATE_ROOM_TOPIC,
+ EventType.STATE_ROOM_AVATAR,
+ EventType.STATE_ROOM_MEMBER,
+ EventType.STATE_ROOM_THIRD_PARTY_INVITE,
+ EventType.STATE_ROOM_CANONICAL_ALIAS,
+ EventType.STATE_ROOM_JOIN_RULES,
+ EventType.STATE_ROOM_HISTORY_VISIBILITY,
+ EventType.STATE_ROOM_SERVER_ACL,
+ EventType.STATE_ROOM_GUEST_ACCESS,
+ EventType.STATE_ROOM_ALIASES,
+ EventType.STATE_SPACE_CHILD,
+ EventType.STATE_SPACE_PARENT,
+ EventType.STATE_ROOM_POWER_LEVELS -> {
+ noticeItemFactory.create(params)
+ }
+ EventType.STATE_ROOM_WIDGET_LEGACY,
+ EventType.STATE_ROOM_WIDGET -> widgetItemFactory.create(params)
+ EventType.STATE_ROOM_ENCRYPTION -> encryptionItemFactory.create(params)
+ // State room create
+ EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(params)
+ // Unhandled state event types
+ else -> {
+ // Should only happen when shouldShowHiddenEvents() settings is ON
+ Timber.v("State event type ${event.root.type} not handled")
+ defaultItemFactory.create(params)
}
}
- EventType.KEY_VERIFICATION_CANCEL,
- EventType.KEY_VERIFICATION_DONE -> {
- verificationConclusionItemFactory.create(params)
- }
- // Unhandled event types
- else -> {
- // Should only happen when shouldShowHiddenEvents() settings is ON
- Timber.v("Type ${event.root.getClearType()} not handled")
- defaultItemFactory.create(params)
+ } else {
+ when (event.root.getClearType()) {
+ // Message itemsX
+ EventType.STICKER,
+ EventType.POLL_START,
+ EventType.MESSAGE -> messageItemFactory.create(params)
+ EventType.REDACTION,
+ EventType.KEY_VERIFICATION_ACCEPT,
+ EventType.KEY_VERIFICATION_START,
+ EventType.KEY_VERIFICATION_KEY,
+ EventType.KEY_VERIFICATION_READY,
+ EventType.KEY_VERIFICATION_MAC,
+ EventType.CALL_CANDIDATES,
+ EventType.CALL_REPLACES,
+ EventType.CALL_SELECT_ANSWER,
+ EventType.CALL_NEGOTIATE,
+ EventType.REACTION,
+ EventType.POLL_RESPONSE,
+ EventType.POLL_END -> noticeItemFactory.create(params)
+ // Calls
+ EventType.CALL_INVITE,
+ EventType.CALL_HANGUP,
+ EventType.CALL_REJECT,
+ EventType.CALL_ANSWER -> callItemFactory.create(params)
+ // Crypto
+ EventType.ENCRYPTED -> {
+ if (event.root.isRedacted()) {
+ // Redacted event, let the MessageItemFactory handle it
+ messageItemFactory.create(params)
+ } else {
+ encryptedItemFactory.create(params)
+ }
+ }
+ EventType.KEY_VERIFICATION_CANCEL,
+ EventType.KEY_VERIFICATION_DONE -> {
+ verificationConclusionItemFactory.create(params)
+ }
+ // Unhandled event types
+ else -> {
+ // Should only happen when shouldShowHiddenEvents() settings is ON
+ Timber.v("Type ${event.root.getClearType()} not handled")
+ defaultItemFactory.create(params)
+ }
}
}
} catch (throwable: Throwable) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/WidgetItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/WidgetItemFactory.kt
index 52f72810c9..a08383315c 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/WidgetItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/WidgetItemFactory.kt
@@ -41,7 +41,7 @@ class WidgetItemFactory @Inject constructor(
fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? {
val event = params.event
- val widgetContent: WidgetContent = event.root.getClearContent().toModel() ?: return null
+ val widgetContent: WidgetContent = event.root.content.toModel() ?: return null
val previousWidgetContent: WidgetContent? = event.root.resolvedPrevContent().toModel()
return when (WidgetType.fromString(widgetContent.type ?: previousWidgetContent?.type ?: "")) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt
index 3dc46c9d70..d39b8aec5e 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt
@@ -114,7 +114,7 @@ class NoticeEventFormatter @Inject constructor(
}
private fun formatRoomPowerLevels(event: Event, disambiguatedDisplayName: String): CharSequence? {
- val powerLevelsContent: PowerLevelsContent = event.getClearContent().toModel() ?: return null
+ val powerLevelsContent: PowerLevelsContent = event.content.toModel() ?: return null
val previousPowerLevelsContent: PowerLevelsContent = event.resolvedPrevContent().toModel() ?: return null
val userIds = HashSet()
userIds.addAll(powerLevelsContent.users.orEmpty().keys)
@@ -142,7 +142,7 @@ class NoticeEventFormatter @Inject constructor(
}
private fun formatWidgetEvent(event: Event, disambiguatedDisplayName: String): CharSequence? {
- val widgetContent: WidgetContent = event.getClearContent().toModel() ?: return null
+ val widgetContent: WidgetContent = event.content.toModel() ?: return null
val previousWidgetContent: WidgetContent? = event.resolvedPrevContent().toModel()
return if (widgetContent.isActive()) {
val widgetName = widgetContent.getHumanName()
@@ -198,7 +198,7 @@ class NoticeEventFormatter @Inject constructor(
}
private fun formatRoomCreateEvent(event: Event, isDm: Boolean): CharSequence? {
- return event.getClearContent().toModel()
+ return event.content.toModel()
?.takeIf { it.creator.isNullOrBlank().not() }
?.let {
if (event.isSentByCurrentUser()) {
@@ -210,7 +210,7 @@ class NoticeEventFormatter @Inject constructor(
}
private fun formatRoomNameEvent(event: Event, senderName: String?): CharSequence? {
- val content = event.getClearContent().toModel() ?: return null
+ val content = event.content.toModel() ?: return null
return if (content.name.isNullOrBlank()) {
if (event.isSentByCurrentUser()) {
sp.getString(R.string.notice_room_name_removed_by_you)
@@ -235,7 +235,7 @@ class NoticeEventFormatter @Inject constructor(
}
private fun formatRoomTopicEvent(event: Event, senderName: String?): CharSequence? {
- val content = event.getClearContent().toModel() ?: return null
+ val content = event.content.toModel() ?: return null
return if (content.topic.isNullOrEmpty()) {
if (event.isSentByCurrentUser()) {
sp.getString(R.string.notice_room_topic_removed_by_you)
@@ -252,7 +252,7 @@ class NoticeEventFormatter @Inject constructor(
}
private fun formatRoomAvatarEvent(event: Event, senderName: String?): CharSequence? {
- val content = event.getClearContent().toModel() ?: return null
+ val content = event.content.toModel() ?: return null
return if (content.avatarUrl.isNullOrEmpty()) {
if (event.isSentByCurrentUser()) {
sp.getString(R.string.notice_room_avatar_removed_by_you)
@@ -269,7 +269,7 @@ class NoticeEventFormatter @Inject constructor(
}
private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?, isDm: Boolean): CharSequence? {
- val historyVisibility = event.getClearContent().toModel()?.historyVisibility ?: return null
+ val historyVisibility = event.content.toModel()?.historyVisibility ?: return null
val historyVisibilitySuffix = roomHistoryVisibilityFormatter.getNoticeSuffix(historyVisibility)
return if (event.isSentByCurrentUser()) {
@@ -282,7 +282,7 @@ class NoticeEventFormatter @Inject constructor(
}
private fun formatRoomThirdPartyInvite(event: Event, senderName: String?, isDm: Boolean): CharSequence? {
- val content = event.getClearContent().toModel()
+ val content = event.content.toModel()
val prevContent = event.resolvedPrevContent()?.toModel()
return when {
@@ -363,7 +363,7 @@ class NoticeEventFormatter @Inject constructor(
}
private fun formatRoomMemberEvent(event: Event, senderName: String?, isDm: Boolean): String? {
- val eventContent: RoomMemberContent? = event.getClearContent().toModel()
+ val eventContent: RoomMemberContent? = event.content.toModel()
val prevEventContent: RoomMemberContent? = event.resolvedPrevContent().toModel()
val isMembershipEvent = prevEventContent?.membership != eventContent?.membership ||
eventContent?.membership == Membership.LEAVE
@@ -375,7 +375,7 @@ class NoticeEventFormatter @Inject constructor(
}
private fun formatRoomAliasesEvent(event: Event, senderName: String?): String? {
- val eventContent: RoomAliasesContent? = event.getClearContent().toModel()
+ val eventContent: RoomAliasesContent? = event.content.toModel()
val prevEventContent: RoomAliasesContent? = event.resolvedPrevContent()?.toModel()
val addedAliases = eventContent?.aliases.orEmpty() - prevEventContent?.aliases.orEmpty()
@@ -408,7 +408,7 @@ class NoticeEventFormatter @Inject constructor(
}
private fun formatRoomServerAclEvent(event: Event, senderName: String?): String? {
- val eventContent = event.getClearContent().toModel() ?: return null
+ val eventContent = event.content.toModel() ?: return null
val prevEventContent = event.resolvedPrevContent()?.toModel()
return buildString {
@@ -481,7 +481,7 @@ class NoticeEventFormatter @Inject constructor(
}
private fun formatRoomCanonicalAliasEvent(event: Event, senderName: String?): String? {
- val eventContent: RoomCanonicalAliasContent? = event.getClearContent().toModel()
+ val eventContent: RoomCanonicalAliasContent? = event.content.toModel()
val prevContent: RoomCanonicalAliasContent? = event.resolvedPrevContent().toModel()
val canonicalAlias = eventContent?.canonicalAlias?.takeIf { it.isNotEmpty() }
val prevCanonicalAlias = prevContent?.canonicalAlias?.takeIf { it.isNotEmpty() }
@@ -551,7 +551,7 @@ class NoticeEventFormatter @Inject constructor(
}
private fun formatRoomGuestAccessEvent(event: Event, senderName: String?, isDm: Boolean): String? {
- val eventContent: RoomGuestAccessContent? = event.getClearContent().toModel()
+ val eventContent: RoomGuestAccessContent? = event.content.toModel()
return when (eventContent?.guestAccess) {
GuestAccess.CanJoin ->
if (event.isSentByCurrentUser()) {
@@ -815,7 +815,7 @@ class NoticeEventFormatter @Inject constructor(
}
private fun formatJoinRulesEvent(event: Event, senderName: String?, isDm: Boolean): CharSequence? {
- val content = event.getClearContent().toModel() ?: return null
+ val content = event.content.toModel() ?: return null
return when (content.joinRules) {
RoomJoinRules.INVITE ->
if (event.isSentByCurrentUser()) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt
index 36f6dcab34..e5caaffbda 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt
@@ -53,10 +53,10 @@ import timber.log.Timber
class RoomListViewModel @AssistedInject constructor(
@Assisted initialState: RoomListViewState,
private val session: Session,
- private val stringProvider: StringProvider,
- private val appStateHandler: AppStateHandler,
- private val vectorPreferences: VectorPreferences,
- private val autoAcceptInvites: AutoAcceptInvites
+ stringProvider: StringProvider,
+ appStateHandler: AppStateHandler,
+ vectorPreferences: VectorPreferences,
+ autoAcceptInvites: AutoAcceptInvites
) : VectorViewModel(initialState) {
@AssistedFactory
diff --git a/vector/src/main/java/im/vector/app/features/login/AbstractSSOLoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/AbstractSSOLoginFragment.kt
index 8663b7c73f..b18df6c9cf 100644
--- a/vector/src/main/java/im/vector/app/features/login/AbstractSSOLoginFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/login/AbstractSSOLoginFragment.kt
@@ -88,7 +88,7 @@ abstract class AbstractSSOLoginFragment : AbstractLoginFragmen
if (state.loginMode.hasSso() && state.loginMode.ssoIdentityProviders().isNullOrEmpty()) {
// in this case we can prefetch (not other cases for privacy concerns)
loginViewModel.getSsoUrl(
- redirectUrl = LoginActivity.VECTOR_REDIRECT_URL,
+ redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId,
providerId = null
)
diff --git a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
index c46dca27b3..5ab08ffff7 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
@@ -356,9 +356,6 @@ open class LoginActivity : VectorBaseActivity(), ToolbarCo
private const val EXTRA_CONFIG = "EXTRA_CONFIG"
- // Note that the domain can be displayed to the user for confirmation that he trusts it. So use a human readable string
- const val VECTOR_REDIRECT_URL = "element://connect"
-
fun newIntent(context: Context, loginConfig: LoginConfig?): Intent {
return Intent(context, LoginActivity::class.java).apply {
putExtra(EXTRA_CONFIG, loginConfig)
diff --git a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt
index 9ca8a1dbec..da61d95997 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt
@@ -200,7 +200,7 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment
if (state.loginMode is LoginMode.Sso) {
loginViewModel.getSsoUrl(
- redirectUrl = LoginActivity.VECTOR_REDIRECT_URL,
+ redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId,
providerId = null
)
diff --git a/vector/src/main/java/im/vector/app/features/login/SSORedirectRouterActivity.kt b/vector/src/main/java/im/vector/app/features/login/SSORedirectRouterActivity.kt
index 29f8559362..19c549fd45 100644
--- a/vector/src/main/java/im/vector/app/features/login/SSORedirectRouterActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/login/SSORedirectRouterActivity.kt
@@ -32,4 +32,9 @@ class SSORedirectRouterActivity : AppCompatActivity() {
navigator.loginSSORedirect(this, intent.data)
finish()
}
+
+ companion object {
+ // Note that the domain can be displayed to the user for confirmation that he trusts it. So use a human readable string
+ const val VECTOR_REDIRECT_URL = "element://connect"
+ }
}
diff --git a/vector/src/main/java/im/vector/app/features/login2/AbstractSSOLoginFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/AbstractSSOLoginFragment2.kt
index 43f301d9b4..8bc531b25d 100644
--- a/vector/src/main/java/im/vector/app/features/login2/AbstractSSOLoginFragment2.kt
+++ b/vector/src/main/java/im/vector/app/features/login2/AbstractSSOLoginFragment2.kt
@@ -24,6 +24,7 @@ import androidx.browser.customtabs.CustomTabsSession
import androidx.viewbinding.ViewBinding
import com.airbnb.mvrx.withState
import im.vector.app.core.utils.openUrlInChromeCustomTab
+import im.vector.app.features.login.SSORedirectRouterActivity
import im.vector.app.features.login.hasSso
import im.vector.app.features.login.ssoIdentityProviders
@@ -90,7 +91,7 @@ abstract class AbstractSSOLoginFragment2 : AbstractLoginFragme
if (state.loginMode.hasSso() && state.loginMode.ssoIdentityProviders().isNullOrEmpty()) {
// in this case we can prefetch (not other cases for privacy concerns)
loginViewModel.getSsoUrl(
- redirectUrl = LoginActivity2.VECTOR_REDIRECT_URL,
+ redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId,
providerId = null
)
diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt
index 51044ac153..f9917a4c31 100644
--- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt
+++ b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt
@@ -30,6 +30,7 @@ import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.toReducedUrl
import im.vector.app.databinding.FragmentLoginSignupUsername2Binding
import im.vector.app.features.login.LoginMode
+import im.vector.app.features.login.SSORedirectRouterActivity
import im.vector.app.features.login.SocialLoginButtonsView
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
@@ -97,7 +98,7 @@ class LoginFragmentSignupUsername2 @Inject constructor() : AbstractSSOLoginFragm
views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
override fun onProviderSelected(id: String?) {
loginViewModel.getSsoUrl(
- redirectUrl = LoginActivity2.VECTOR_REDIRECT_URL,
+ redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId,
providerId = id
)
diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt
index 48792da007..3fa0e6c549 100644
--- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt
+++ b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt
@@ -31,6 +31,7 @@ import im.vector.app.core.extensions.hidePassword
import im.vector.app.core.extensions.toReducedUrl
import im.vector.app.databinding.FragmentLoginSigninToAny2Binding
import im.vector.app.features.login.LoginMode
+import im.vector.app.features.login.SSORedirectRouterActivity
import im.vector.app.features.login.SocialLoginButtonsView
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
@@ -124,7 +125,7 @@ class LoginFragmentToAny2 @Inject constructor() : AbstractSSOLoginFragment2
loginViewModel.getSsoUrl(
- redirectUrl = LoginActivity2.VECTOR_REDIRECT_URL,
+ redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId,
providerId = null
)
diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt
index 94784b0605..c1f45c6713 100644
--- a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt
@@ -34,11 +34,11 @@ import im.vector.app.core.resources.ColorProvider
import im.vector.app.databinding.DialogBaseEditTextBinding
import im.vector.app.databinding.FragmentLoginAccountCreatedBinding
import im.vector.app.features.displayname.getBestName
+import im.vector.app.features.ftue.FTUEActivity
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
import im.vector.app.features.login2.AbstractLoginFragment2
import im.vector.app.features.login2.LoginAction2
-import im.vector.app.features.login2.LoginActivity2
import im.vector.app.features.login2.LoginViewState2
import org.matrix.android.sdk.api.util.MatrixItem
import java.util.UUID
@@ -130,7 +130,7 @@ class AccountCreatedFragment @Inject constructor(
private fun invalidateState(state: AccountCreatedViewState) {
// Ugly hack...
- (activity as? LoginActivity2)?.setIsLoading(state.isLoading)
+ (activity as? FTUEActivity)?.setIsLoading(state.isLoading)
views.loginAccountCreatedSubtitle.text = getString(R.string.login_account_created_subtitle, state.userId)
diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
index e3a4766f3d..563de51371 100644
--- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
+++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
@@ -51,6 +51,7 @@ import im.vector.app.features.crypto.verification.SupportedVerificationMethodsPr
import im.vector.app.features.crypto.verification.VerificationBottomSheet
import im.vector.app.features.debug.DebugMenuActivity
import im.vector.app.features.devtools.RoomDevToolActivity
+import im.vector.app.features.ftue.FTUEActivity
import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.home.room.detail.arguments.TimelineArgs
import im.vector.app.features.home.room.detail.search.SearchActivity
@@ -62,7 +63,6 @@ import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs
import im.vector.app.features.invite.InviteUsersToRoomActivity
import im.vector.app.features.login.LoginActivity
import im.vector.app.features.login.LoginConfig
-import im.vector.app.features.login2.LoginActivity2
import im.vector.app.features.matrixto.MatrixToBottomSheet
import im.vector.app.features.media.AttachmentData
import im.vector.app.features.media.BigImageViewerActivity
@@ -84,7 +84,6 @@ import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.settings.VectorSettingsActivity
import im.vector.app.features.share.SharedData
import im.vector.app.features.signout.soft.SoftLogoutActivity
-import im.vector.app.features.signout.soft.SoftLogoutActivity2
import im.vector.app.features.spaces.InviteRoomSpaceChooserBottomSheet
import im.vector.app.features.spaces.SpaceExploreActivity
import im.vector.app.features.spaces.SpacePreviewActivity
@@ -115,27 +114,26 @@ class DefaultNavigator @Inject constructor(
) : Navigator {
override fun openLogin(context: Context, loginConfig: LoginConfig?, flags: Int) {
- val intent = when (features.loginVersion()) {
- VectorFeatures.LoginVersion.V1 -> LoginActivity.newIntent(context, loginConfig)
- VectorFeatures.LoginVersion.V2 -> LoginActivity2.newIntent(context, loginConfig)
+ val intent = when (features.loginVariant()) {
+ VectorFeatures.LoginVariant.LEGACY -> LoginActivity.newIntent(context, loginConfig)
+ VectorFeatures.LoginVariant.FTUE,
+ VectorFeatures.LoginVariant.FTUE_WIP -> FTUEActivity.newIntent(context, loginConfig)
}
intent.addFlags(flags)
context.startActivity(intent)
}
override fun loginSSORedirect(context: Context, data: Uri?) {
- val intent = when (features.loginVersion()) {
- VectorFeatures.LoginVersion.V1 -> LoginActivity.redirectIntent(context, data)
- VectorFeatures.LoginVersion.V2 -> LoginActivity2.redirectIntent(context, data)
+ val intent = when (features.loginVariant()) {
+ VectorFeatures.LoginVariant.LEGACY -> LoginActivity.redirectIntent(context, data)
+ VectorFeatures.LoginVariant.FTUE,
+ VectorFeatures.LoginVariant.FTUE_WIP -> FTUEActivity.redirectIntent(context, data)
}
context.startActivity(intent)
}
override fun softLogout(context: Context) {
- val intent = when (features.loginVersion()) {
- VectorFeatures.LoginVersion.V1 -> SoftLogoutActivity.newIntent(context)
- VectorFeatures.LoginVersion.V2 -> SoftLogoutActivity2.newIntent(context)
- }
+ val intent = SoftLogoutActivity.newIntent(context)
context.startActivity(intent)
}
diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt
index 87b31fa92a..f73e2ab0c3 100644
--- a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt
+++ b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt
@@ -66,12 +66,10 @@ class NotifiableEventResolver @Inject constructor(
return resolveStateRoomEvent(event, session, canBeReplaced = false, isNoisy = isNoisy)
}
val timelineEvent = session.getRoom(roomID)?.getTimeLineEvent(eventId) ?: return null
- when (event.getClearType()) {
- EventType.MESSAGE -> {
- return resolveMessageEvent(timelineEvent, session, canBeReplaced = false, isNoisy = isNoisy)
- }
+ return when (event.getClearType()) {
+ EventType.MESSAGE,
EventType.ENCRYPTED -> {
- return resolveMessageEvent(timelineEvent, session, canBeReplaced = false, isNoisy = isNoisy)
+ resolveMessageEvent(timelineEvent, session, canBeReplaced = false, isNoisy = isNoisy)
}
else -> {
// If the event can be displayed, display it as is
@@ -79,7 +77,7 @@ class NotifiableEventResolver @Inject constructor(
// TODO Better event text display
val bodyPreview = event.type ?: EventType.MISSING_TYPE
- return SimpleNotifiableEvent(
+ SimpleNotifiableEvent(
session.myUserId,
eventId = event.eventId!!,
editedEventId = timelineEvent.getEditedEventId(),
@@ -126,18 +124,18 @@ class NotifiableEventResolver @Inject constructor(
}
}
- private suspend fun resolveMessageEvent(event: TimelineEvent, session: Session, canBeReplaced: Boolean, isNoisy: Boolean): NotifiableEvent {
+ private suspend fun resolveMessageEvent(event: TimelineEvent, session: Session, canBeReplaced: Boolean, isNoisy: Boolean): NotifiableEvent? {
// The event only contains an eventId, and roomId (type is m.room.*) , we need to get the displayable content (names, avatar, text, etc...)
val room = session.getRoom(event.root.roomId!! /*roomID cannot be null*/)
- if (room == null) {
+ return if (room == null) {
Timber.e("## Unable to resolve room for eventId [$event]")
// Ok room is not known in store, but we can still display something
val body = displayableEventFormatter.format(event, isDm = false, appendAuthor = false)
val roomName = stringProvider.getString(R.string.notification_unknown_room_name)
val senderDisplayName = event.senderInfo.disambiguatedDisplayName
- return NotifiableMessageEvent(
+ NotifiableMessageEvent(
eventId = event.root.eventId!!,
editedEventId = event.getEditedEventId(),
canBeReplaced = canBeReplaced,
@@ -152,51 +150,60 @@ class NotifiableEventResolver @Inject constructor(
matrixID = session.myUserId
)
} else {
- if (event.root.isEncrypted() && event.root.mxDecryptionResult == null) {
- // TODO use a global event decryptor? attache to session and that listen to new sessionId?
- // for now decrypt sync
- try {
- val result = session.cryptoService().decryptEvent(event.root, event.root.roomId + UUID.randomUUID().toString())
- event.root.mxDecryptionResult = OlmDecryptionResult(
- payload = result.clearEvent,
- senderKey = result.senderCurve25519Key,
- keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
- forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
+ event.attemptToDecryptIfNeeded(session)
+ // only convert encrypted messages to NotifiableMessageEvents
+ when (event.root.getClearType()) {
+ EventType.MESSAGE -> {
+ val body = displayableEventFormatter.format(event, isDm = room.roomSummary()?.isDirect.orFalse(), appendAuthor = false).toString()
+ val roomName = room.roomSummary()?.displayName ?: ""
+ val senderDisplayName = event.senderInfo.disambiguatedDisplayName
+
+ NotifiableMessageEvent(
+ eventId = event.root.eventId!!,
+ editedEventId = event.getEditedEventId(),
+ canBeReplaced = canBeReplaced,
+ timestamp = event.root.originServerTs ?: 0,
+ noisy = isNoisy,
+ senderName = senderDisplayName,
+ senderId = event.root.senderId,
+ body = body,
+ imageUri = event.fetchImageIfPresent(session),
+ roomId = event.root.roomId!!,
+ roomName = roomName,
+ roomIsDirect = room.roomSummary()?.isDirect ?: false,
+ roomAvatarPath = session.contentUrlResolver()
+ .resolveThumbnail(room.roomSummary()?.avatarUrl,
+ 250,
+ 250,
+ ContentUrlResolver.ThumbnailMethod.SCALE),
+ senderAvatarPath = session.contentUrlResolver()
+ .resolveThumbnail(event.senderInfo.avatarUrl,
+ 250,
+ 250,
+ ContentUrlResolver.ThumbnailMethod.SCALE),
+ matrixID = session.myUserId,
+ soundName = null
)
- } catch (e: MXCryptoError) {
}
+ else -> null
}
+ }
+ }
- val body = displayableEventFormatter.format(event, isDm = room.roomSummary()?.isDirect.orFalse(), appendAuthor = false).toString()
- val roomName = room.roomSummary()?.displayName ?: ""
- val senderDisplayName = event.senderInfo.disambiguatedDisplayName
-
- return NotifiableMessageEvent(
- eventId = event.root.eventId!!,
- editedEventId = event.getEditedEventId(),
- canBeReplaced = canBeReplaced,
- timestamp = event.root.originServerTs ?: 0,
- noisy = isNoisy,
- senderName = senderDisplayName,
- senderId = event.root.senderId,
- body = body,
- imageUri = event.fetchImageIfPresent(session),
- roomId = event.root.roomId!!,
- roomName = roomName,
- roomIsDirect = room.roomSummary()?.isDirect ?: false,
- roomAvatarPath = session.contentUrlResolver()
- .resolveThumbnail(room.roomSummary()?.avatarUrl,
- 250,
- 250,
- ContentUrlResolver.ThumbnailMethod.SCALE),
- senderAvatarPath = session.contentUrlResolver()
- .resolveThumbnail(event.senderInfo.avatarUrl,
- 250,
- 250,
- ContentUrlResolver.ThumbnailMethod.SCALE),
- matrixID = session.myUserId,
- soundName = null
- )
+ private fun TimelineEvent.attemptToDecryptIfNeeded(session: Session) {
+ if (root.isEncrypted() && root.mxDecryptionResult == null) {
+ // TODO use a global event decryptor? attache to session and that listen to new sessionId?
+ // for now decrypt sync
+ try {
+ val result = session.cryptoService().decryptEvent(root, root.roomId + UUID.randomUUID().toString())
+ root.mxDecryptionResult = OlmDecryptionResult(
+ payload = result.clearEvent,
+ senderKey = result.senderCurve25519Key,
+ keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
+ forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
+ )
+ } catch (e: MXCryptoError) {
+ }
}
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
index 64561cbc12..3436c20ce3 100755
--- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
@@ -44,6 +44,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
const val SETTINGS_HOME_SERVER_PREFERENCE_KEY = "SETTINGS_HOME_SERVER_PREFERENCE_KEY"
const val SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY = "SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY"
const val SETTINGS_DISCOVERY_PREFERENCE_KEY = "SETTINGS_DISCOVERY_PREFERENCE_KEY"
+ const val SETTINGS_EMAILS_AND_PHONE_NUMBERS_PREFERENCE_KEY = "SETTINGS_EMAILS_AND_PHONE_NUMBERS_PREFERENCE_KEY"
const val SETTINGS_CLEAR_CACHE_PREFERENCE_KEY = "SETTINGS_CLEAR_CACHE_PREFERENCE_KEY"
const val SETTINGS_CLEAR_MEDIA_CACHE_PREFERENCE_KEY = "SETTINGS_CLEAR_MEDIA_CACHE_PREFERENCE_KEY"
diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity2.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity2.kt
deleted file mode 100644
index 8489b2baef..0000000000
--- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity2.kt
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright 2021 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package im.vector.app.features.signout.soft
-
-import android.content.Context
-import android.content.Intent
-import androidx.core.view.isVisible
-import androidx.fragment.app.FragmentManager
-import com.airbnb.mvrx.Success
-import com.airbnb.mvrx.viewModel
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import dagger.hilt.android.AndroidEntryPoint
-import im.vector.app.R
-import im.vector.app.core.error.ErrorFormatter
-import im.vector.app.core.extensions.replaceFragment
-import im.vector.app.features.MainActivity
-import im.vector.app.features.MainActivityArgs
-import im.vector.app.features.login2.LoginActivity2
-import org.matrix.android.sdk.api.failure.GlobalError
-import org.matrix.android.sdk.api.session.Session
-import timber.log.Timber
-import javax.inject.Inject
-
-/**
- * In this screen, the user is viewing a message informing that he has been logged out
- * Extends LoginActivity to get the login with SSO and forget password functionality for (nearly) free
- *
- * This is just a copy of SoftLogoutActivity2, which extends LoginActivity2
- */
-@AndroidEntryPoint
-class SoftLogoutActivity2 : LoginActivity2() {
-
- private val softLogoutViewModel: SoftLogoutViewModel by viewModel()
-
- @Inject lateinit var session: Session
- @Inject lateinit var errorFormatter: ErrorFormatter
-
- override fun initUiAndData() {
- super.initUiAndData()
-
- softLogoutViewModel.onEach {
- updateWithState(it)
- }
-
- softLogoutViewModel.observeViewEvents { handleSoftLogoutViewEvents(it) }
- }
-
- private fun handleSoftLogoutViewEvents(softLogoutViewEvents: SoftLogoutViewEvents) {
- when (softLogoutViewEvents) {
- is SoftLogoutViewEvents.Failure ->
- showError(errorFormatter.toHumanReadable(softLogoutViewEvents.throwable))
- is SoftLogoutViewEvents.ErrorNotSameUser -> {
- // Pop the backstack
- supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
-
- // And inform the user
- showError(getString(
- R.string.soft_logout_sso_not_same_user_error,
- softLogoutViewEvents.currentUserId,
- softLogoutViewEvents.newUserId)
- )
- }
- is SoftLogoutViewEvents.ClearData -> {
- MainActivity.restartApp(this, MainActivityArgs(clearCredentials = true))
- }
- }
- }
-
- private fun showError(message: String) {
- MaterialAlertDialogBuilder(this)
- .setTitle(R.string.dialog_title_error)
- .setMessage(message)
- .setPositiveButton(R.string.ok, null)
- .show()
- }
-
- override fun addFirstFragment() {
- replaceFragment(views.loginFragmentContainer, SoftLogoutFragment::class.java)
- }
-
- private fun updateWithState(softLogoutViewState: SoftLogoutViewState) {
- if (softLogoutViewState.asyncLoginAction is Success) {
- MainActivity.restartApp(this, MainActivityArgs())
- }
-
- views.loginLoading.isVisible = softLogoutViewState.isLoading()
- }
-
- companion object {
- fun newIntent(context: Context): Intent {
- return Intent(context, SoftLogoutActivity2::class.java)
- }
- }
-
- override fun handleInvalidToken(globalError: GlobalError.InvalidToken) {
- // No op here
- Timber.w("Ignoring invalid token global error")
- }
-}
diff --git a/vector/src/main/res/drawable-hdpi/ic_attachment_stickers_white_24dp.png b/vector/src/main/res/drawable-hdpi/ic_attachment_stickers_white_24dp.png
deleted file mode 100644
index d27e8f406e..0000000000
Binary files a/vector/src/main/res/drawable-hdpi/ic_attachment_stickers_white_24dp.png and /dev/null differ
diff --git a/vector/src/main/res/drawable-mdpi/ic_attachment_stickers_white_24dp.png b/vector/src/main/res/drawable-mdpi/ic_attachment_stickers_white_24dp.png
deleted file mode 100644
index 40d78cf9e2..0000000000
Binary files a/vector/src/main/res/drawable-mdpi/ic_attachment_stickers_white_24dp.png and /dev/null differ
diff --git a/vector/src/main/res/drawable-xhdpi/ic_attachment_stickers_white_24dp.png b/vector/src/main/res/drawable-xhdpi/ic_attachment_stickers_white_24dp.png
deleted file mode 100644
index 46e23b9cdc..0000000000
Binary files a/vector/src/main/res/drawable-xhdpi/ic_attachment_stickers_white_24dp.png and /dev/null differ
diff --git a/vector/src/main/res/drawable-xxhdpi/ic_attachment_stickers_white_24dp.png b/vector/src/main/res/drawable-xxhdpi/ic_attachment_stickers_white_24dp.png
deleted file mode 100644
index 4058b25495..0000000000
Binary files a/vector/src/main/res/drawable-xxhdpi/ic_attachment_stickers_white_24dp.png and /dev/null differ
diff --git a/vector/src/main/res/drawable-xxxhdpi/ic_attachment_stickers_white_24dp.png b/vector/src/main/res/drawable-xxxhdpi/ic_attachment_stickers_white_24dp.png
deleted file mode 100644
index c5b2435646..0000000000
Binary files a/vector/src/main/res/drawable-xxxhdpi/ic_attachment_stickers_white_24dp.png and /dev/null differ
diff --git a/vector/src/main/res/drawable/ic_attachment_camera.xml b/vector/src/main/res/drawable/ic_attachment_camera.xml
new file mode 100644
index 0000000000..8c7bedb3cf
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_attachment_camera.xml
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/vector/src/main/res/drawable/ic_attachment_camera_white_24dp.xml b/vector/src/main/res/drawable/ic_attachment_camera_white_24dp.xml
deleted file mode 100644
index 5c2920d252..0000000000
--- a/vector/src/main/res/drawable/ic_attachment_camera_white_24dp.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/vector/src/main/res/drawable/ic_attachment_file.xml b/vector/src/main/res/drawable/ic_attachment_file.xml
new file mode 100644
index 0000000000..b3545e54a6
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_attachment_file.xml
@@ -0,0 +1,13 @@
+
+
+
diff --git a/vector/src/main/res/drawable/ic_attachment_file_white_24dp.xml b/vector/src/main/res/drawable/ic_attachment_file_white_24dp.xml
deleted file mode 100644
index 4e6b9458f8..0000000000
--- a/vector/src/main/res/drawable/ic_attachment_file_white_24dp.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/vector/src/main/res/drawable/ic_attachment_gallery.xml b/vector/src/main/res/drawable/ic_attachment_gallery.xml
new file mode 100644
index 0000000000..0f3432544f
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_attachment_gallery.xml
@@ -0,0 +1,12 @@
+
+
+
diff --git a/vector/src/main/res/drawable/ic_attachment_gallery_white_24dp.xml b/vector/src/main/res/drawable/ic_attachment_gallery_white_24dp.xml
deleted file mode 100644
index d4e68f125b..0000000000
--- a/vector/src/main/res/drawable/ic_attachment_gallery_white_24dp.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/vector/src/main/res/drawable/ic_attachment_location.xml b/vector/src/main/res/drawable/ic_attachment_location.xml
new file mode 100644
index 0000000000..c2c8093e1d
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_attachment_location.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/vector/src/main/res/drawable/ic_attachment_poll_white_24dp.xml b/vector/src/main/res/drawable/ic_attachment_poll.xml
similarity index 93%
rename from vector/src/main/res/drawable/ic_attachment_poll_white_24dp.xml
rename to vector/src/main/res/drawable/ic_attachment_poll.xml
index 8cbcc6e47c..320dccb7fc 100644
--- a/vector/src/main/res/drawable/ic_attachment_poll_white_24dp.xml
+++ b/vector/src/main/res/drawable/ic_attachment_poll.xml
@@ -5,6 +5,6 @@
android:viewportHeight="24">
diff --git a/vector/src/main/res/drawable/ic_attachment_sticker.xml b/vector/src/main/res/drawable/ic_attachment_sticker.xml
new file mode 100644
index 0000000000..eb59eaa75d
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_attachment_sticker.xml
@@ -0,0 +1,13 @@
+
+
+
diff --git a/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml b/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml
index 3378878ac6..7e926b860c 100644
--- a/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml
+++ b/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml
@@ -108,9 +108,9 @@
-
+ android:layout_height="@dimen/composer_min_height"
+ android:background="?android:colorBackground">
-
+
+
+ android:layout_marginStart="4dp"
+ android:scrollbars="none"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@id/attachmentCloseButton"
+ app:layout_constraintTop_toTopOf="parent">
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
-
+
-
+
-
+
-
+
-
+
-
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/vector/src/main/res/layout/view_voice_message_recorder.xml b/vector/src/main/res/layout/view_voice_message_recorder.xml
index 53be4f07f6..9f8e58d724 100644
--- a/vector/src/main/res/layout/view_voice_message_recorder.xml
+++ b/vector/src/main/res/layout/view_voice_message_recorder.xml
@@ -34,7 +34,7 @@