Merge branch 'develop' into feature/ons/edit_polls
* develop: (66 commits) toolbar management (#4887) adding changelog entry adding back periodic flag when scheduling automatic background workers Fix enum class warning Split long lines Done by https://github.com/matrix-org/matrix-analytics-events/pull/16 Add new class in analytics plan Fix conditional for Delight issue automation Add missing import in kdoc Update kdoc Enable Delight issue automation Fix an error in string resource (#4997) Changelog Add some unit test for the command parser. Not all commands are covered, could add more tests later. data class. use sealed interface Small cleanup Command parser is not a static object anymore Add changelog Use Throwable.isLimitExceededError extension Do not automatically retry 429 with a too long delay ...
This commit is contained in:
commit
7f97e78ba3
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@ -10,6 +10,8 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
ignore:
|
||||
- dependency-name: "*github-script*"
|
||||
# Updates for Gradle dependencies used in the app
|
||||
- package-ecosystem: gradle
|
||||
directory: "/"
|
||||
|
53
.github/workflows/triage-labelled.yml
vendored
53
.github/workflows/triage-labelled.yml
vendored
@ -100,32 +100,33 @@ jobs:
|
||||
PROJECT_ID: "PN_kwDOAM0swc4AAg6N"
|
||||
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
|
||||
# delight_issues_to_board:
|
||||
# name: Spaces issues to new Delight project board
|
||||
# runs-on: ubuntu-latest
|
||||
# # Skip in forks
|
||||
# if: >
|
||||
# github.repository == 'vector-im/element-android' &&
|
||||
# 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 Delight project board
|
||||
runs-on: ubuntu-latest
|
||||
# Skip in forks
|
||||
if: >
|
||||
github.repository == 'vector-im/element-android' &&
|
||||
(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') ||
|
||||
contains(github.event.issue.labels.*.name, 'Z-IA'))
|
||||
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
|
||||
|
1
changelog.d/3839.bugfix
Normal file
1
changelog.d/3839.bugfix
Normal file
@ -0,0 +1 @@
|
||||
Notification does not take me to the room when another space was last viewed
|
1
changelog.d/4584.feature
Normal file
1
changelog.d/4584.feature
Normal file
@ -0,0 +1 @@
|
||||
Enables the FTUE splash carousel
|
1
changelog.d/4734.misc
Normal file
1
changelog.d/4734.misc
Normal file
@ -0,0 +1 @@
|
||||
Analytics: send more Events
|
3
changelog.d/4884.misc
Normal file
3
changelog.d/4884.misc
Normal file
@ -0,0 +1,3 @@
|
||||
Toolbar management rework. Toolbar title's and subtitle's text appearance now controlled by theme without local overrides. Helper class introduced to
|
||||
help with toolbar configuration. Toolbar title, subtitle and navigation button widgets are removed where it is possible and replaced with built-in
|
||||
toolbar widgets.
|
1
changelog.d/4975.misc
Normal file
1
changelog.d/4975.misc
Normal file
@ -0,0 +1 @@
|
||||
Including onboarding server options in the all screen sanity test suite
|
1
changelog.d/4988.misc
Normal file
1
changelog.d/4988.misc
Normal file
@ -0,0 +1 @@
|
||||
Exclude dependabot upgrade for @github-script@v3
|
1
changelog.d/4991.bugfix
Normal file
1
changelog.d/4991.bugfix
Normal file
@ -0,0 +1 @@
|
||||
EmojiPopupDismissListener not being triggered after dismissing the EmojiPopup
|
1
changelog.d/4995.removal
Normal file
1
changelog.d/4995.removal
Normal file
@ -0,0 +1 @@
|
||||
429 are not automatically retried anymore in case of too long retry delay
|
1
changelog.d/4997.bugfix
Normal file
1
changelog.d/4997.bugfix
Normal file
@ -0,0 +1 @@
|
||||
Fix an error in string resource
|
1
changelog.d/4998.misc
Normal file
1
changelog.d/4998.misc
Normal file
@ -0,0 +1 @@
|
||||
Small iteration on command parser and unit test it.
|
1
changelog.d/5003.bugfix
Normal file
1
changelog.d/5003.bugfix
Normal file
@ -0,0 +1 @@
|
||||
Fixing missing notifications in FDroid variants using `optimised for battery` background sync mode
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.core.utils.compat
|
||||
package im.vector.lib.core.utils.compat
|
||||
|
||||
import android.os.Build
|
||||
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.core.epoxy.charsequence
|
||||
package im.vector.lib.core.utils.epoxy.charsequence
|
||||
|
||||
/**
|
||||
* Wrapper for a CharSequence, which support mutation of the CharSequence, which can happen during rendering
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.core.epoxy.charsequence
|
||||
package im.vector.lib.core.utils.epoxy.charsequence
|
||||
|
||||
/**
|
||||
* Extensions to wrap CharSequence to EpoxyCharSequence
|
@ -42,6 +42,8 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(":library:core-utils")
|
||||
|
||||
implementation libs.androidx.appCompat
|
||||
implementation libs.androidx.core
|
||||
|
||||
@ -57,7 +59,7 @@ dependencies {
|
||||
implementation libs.jetbrains.coroutinesCore
|
||||
implementation libs.jetbrains.coroutinesAndroid
|
||||
|
||||
testImplementation 'org.json:json:20190722'
|
||||
testImplementation 'org.json:json:20211205'
|
||||
testImplementation libs.tests.junit
|
||||
androidTestImplementation libs.androidx.junit
|
||||
androidTestImplementation libs.androidx.espressoCore
|
||||
|
@ -21,6 +21,7 @@ import android.view.View
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Success
|
||||
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||
import me.gujun.android.span.Span
|
||||
import me.gujun.android.span.span
|
||||
|
||||
@ -40,7 +41,7 @@ internal class JSonViewerEpoxyController(private val context: Context) :
|
||||
is Fail -> {
|
||||
valueItem {
|
||||
id("fail")
|
||||
text(async.error.localizedMessage?.toSafeCharSequence())
|
||||
text(async.error.localizedMessage?.toEpoxyCharSequence())
|
||||
}
|
||||
}
|
||||
is Success -> {
|
||||
@ -94,7 +95,7 @@ internal class JSonViewerEpoxyController(private val context: Context) :
|
||||
+"{+${model.keys.size}}"
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}.toSafeCharSequence()
|
||||
}.toEpoxyCharSequence()
|
||||
)
|
||||
itemClickListener(View.OnClickListener { host.itemClicked(model) })
|
||||
}
|
||||
@ -133,7 +134,7 @@ internal class JSonViewerEpoxyController(private val context: Context) :
|
||||
+"[+${model.items.size}]"
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}.toSafeCharSequence()
|
||||
}.toEpoxyCharSequence()
|
||||
)
|
||||
itemClickListener(View.OnClickListener { host.itemClicked(model) })
|
||||
}
|
||||
@ -163,7 +164,7 @@ internal class JSonViewerEpoxyController(private val context: Context) :
|
||||
}
|
||||
}
|
||||
append(host.valueToSpan(model))
|
||||
}.toSafeCharSequence()
|
||||
}.toEpoxyCharSequence()
|
||||
)
|
||||
copyValue(model.stringRes)
|
||||
}
|
||||
@ -233,7 +234,7 @@ internal class JSonViewerEpoxyController(private val context: Context) :
|
||||
span("{".takeIf { isObject } ?: "[") {
|
||||
textColor = host.styleProvider.baseColor
|
||||
}
|
||||
}.toSafeCharSequence()
|
||||
}.toEpoxyCharSequence()
|
||||
)
|
||||
itemClickListener(View.OnClickListener { host.itemClicked(composed) })
|
||||
}
|
||||
@ -253,7 +254,7 @@ internal class JSonViewerEpoxyController(private val context: Context) :
|
||||
span {
|
||||
text = "}".takeIf { isObject } ?: "]"
|
||||
textColor = host.styleProvider.baseColor
|
||||
}.toSafeCharSequence()
|
||||
}.toEpoxyCharSequence()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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 org.billcarsonfr.jsonviewer
|
||||
|
||||
/**
|
||||
* Wrapper for a CharSequence, which support mutation of the CharSequence, which can happen during rendering
|
||||
* TODO Mutualize
|
||||
*/
|
||||
internal class SafeCharSequence(val charSequence: CharSequence) {
|
||||
private val hash = charSequence.toString().hashCode()
|
||||
|
||||
override fun hashCode() = hash
|
||||
override fun equals(other: Any?) = other is SafeCharSequence && other.hash == hash
|
||||
}
|
||||
|
||||
internal fun CharSequence.toSafeCharSequence() = SafeCharSequence(this)
|
@ -19,9 +19,6 @@ package org.billcarsonfr.jsonviewer
|
||||
import android.content.Context
|
||||
import android.util.TypedValue
|
||||
|
||||
/**
|
||||
* TODO Mutualize
|
||||
*/
|
||||
internal object Utils {
|
||||
fun dpToPx(dp: Int, context: Context): Int {
|
||||
return TypedValue.applyDimension(
|
||||
|
@ -28,12 +28,13 @@ import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyHolder
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import com.airbnb.epoxy.EpoxyModelWithHolder
|
||||
import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence
|
||||
|
||||
@EpoxyModelClass(layout = R2.layout.item_jv_base_value)
|
||||
internal abstract class ValueItem : EpoxyModelWithHolder<ValueItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute
|
||||
var text: SafeCharSequence? = null
|
||||
var text: EpoxyCharSequence? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var depth: Int = 0
|
||||
|
@ -1,7 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<gradient
|
||||
android:angle="270"
|
||||
android:endColor="?vctr_system"
|
||||
android:startColor="#000000" />
|
||||
<solid android:color="?android:colorBackground" />
|
||||
</shape>
|
@ -6,10 +6,12 @@
|
||||
<item name="elevation">0dp</item>
|
||||
|
||||
<!-- main text -->
|
||||
<item name="titleTextStyle">@style/Widget.Vector.TextView.ActionBarTitle</item>
|
||||
<item name="titleTextAppearance">@style/TextAppearance.Vector.Widget.ActionBarTitle</item>
|
||||
|
||||
<!-- sub text -->
|
||||
<item name="subtitleTextStyle">@style/Widget.Vector.TextView.ActionBarSubTitle</item>
|
||||
<item name="subtitleTextAppearance">@style/TextAppearance.Vector.Widget.ActionBarSubTitle</item>
|
||||
|
||||
<item name="navigationIconTint">?vctr_content_secondary</item>
|
||||
</style>
|
||||
|
||||
<!-- Default toolbar style -->
|
||||
@ -22,16 +24,18 @@
|
||||
|
||||
<!-- Toolbar text style -->
|
||||
<!-- main text -->
|
||||
<style name="Widget.Vector.TextView.ActionBarTitle" parent="TextAppearance.AppCompat.Widget.ActionBar.Title">
|
||||
<style name="TextAppearance.Vector.Widget.ActionBarTitle" parent="TextAppearance.AppCompat.Widget.ActionBar.Title">
|
||||
<item name="android:textColor">?vctr_content_primary</item>
|
||||
<item name="android:fontFamily">"sans-serif-medium"</item>
|
||||
<item name="android:textSize">20sp</item>
|
||||
<item name="android:fontFamily">sans-serif-medium</item>
|
||||
<item name="fontFamily">sans-serif-medium</item>
|
||||
<item name="android:textSize">18sp</item>
|
||||
</style>
|
||||
|
||||
<!-- sub text -->
|
||||
<style name="Widget.Vector.TextView.ActionBarSubTitle" parent="TextAppearance.AppCompat.Widget.ActionBar.Subtitle">
|
||||
<item name="android:textColor">?vctr_content_primary</item>
|
||||
<item name="android:fontFamily">"sans-serif-medium"</item>
|
||||
<style name="TextAppearance.Vector.Widget.ActionBarSubTitle" parent="TextAppearance.AppCompat.Widget.ActionBar.Subtitle">
|
||||
<item name="android:textColor">?vctr_content_secondary</item>
|
||||
<item name="android:fontFamily">sans-serif</item>
|
||||
<item name="fontFamily">sans-serif</item>
|
||||
<item name="android:textSize">12sp</item>
|
||||
</style>
|
||||
|
||||
|
@ -32,13 +32,18 @@ fun Throwable.is401() =
|
||||
fun Throwable.isTokenError() =
|
||||
this is Failure.ServerError &&
|
||||
(error.code == MatrixError.M_UNKNOWN_TOKEN ||
|
||||
error.code == MatrixError.M_MISSING_TOKEN ||
|
||||
error.code == MatrixError.ORG_MATRIX_EXPIRED_ACCOUNT)
|
||||
error.code == MatrixError.M_MISSING_TOKEN ||
|
||||
error.code == MatrixError.ORG_MATRIX_EXPIRED_ACCOUNT)
|
||||
|
||||
fun Throwable.isLimitExceededError() =
|
||||
this is Failure.ServerError &&
|
||||
httpCode == 429 &&
|
||||
error.code == MatrixError.M_LIMIT_EXCEEDED
|
||||
|
||||
fun Throwable.shouldBeRetried(): Boolean {
|
||||
return this is Failure.NetworkConnection ||
|
||||
this is IOException ||
|
||||
(this is Failure.ServerError && error.code == MatrixError.M_LIMIT_EXCEEDED)
|
||||
this.isLimitExceededError()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,8 +19,9 @@ package org.matrix.android.sdk.internal.network
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.delay
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.MatrixError
|
||||
import org.matrix.android.sdk.api.failure.GlobalError
|
||||
import org.matrix.android.sdk.api.failure.getRetryDelay
|
||||
import org.matrix.android.sdk.api.failure.isLimitExceededError
|
||||
import org.matrix.android.sdk.api.failure.shouldBeRetried
|
||||
import org.matrix.android.sdk.internal.network.ssl.CertUtil
|
||||
import retrofit2.HttpException
|
||||
@ -33,7 +34,8 @@ import java.io.IOException
|
||||
*
|
||||
* @param globalErrorReceiver will be use to notify error such as invalid token error. See [GlobalError]
|
||||
* @param canRetry if set to true, the request will be executed again in case of error, after a delay
|
||||
* @param maxDelayBeforeRetry the max delay to wait before a retry
|
||||
* @param maxDelayBeforeRetry the max delay to wait before a retry. Note that in the case of a 429, if the provided delay exceeds this value, the error will
|
||||
* be propagated as it does not make sense to retry it with a shorter delay.
|
||||
* @param maxRetriesCount the max number of retries
|
||||
* @param requestBlock a suspend lambda to perform the network request
|
||||
*/
|
||||
@ -74,23 +76,26 @@ internal suspend inline fun <DATA> executeRequest(globalErrorReceiver: GlobalErr
|
||||
|
||||
currentRetryCount++
|
||||
|
||||
if (exception is Failure.ServerError &&
|
||||
exception.httpCode == 429 &&
|
||||
exception.error.code == MatrixError.M_LIMIT_EXCEEDED &&
|
||||
currentRetryCount < maxRetriesCount) {
|
||||
if (exception.isLimitExceededError() && currentRetryCount < maxRetriesCount) {
|
||||
// 429, we can retry
|
||||
delay(exception.getRetryDelay(1_000))
|
||||
val retryDelay = exception.getRetryDelay(1_000)
|
||||
if (retryDelay <= maxDelayBeforeRetry) {
|
||||
delay(retryDelay)
|
||||
} else {
|
||||
// delay is too high to be retried, propagate the exception
|
||||
throw exception
|
||||
}
|
||||
} else if (canRetry && currentRetryCount < maxRetriesCount && exception.shouldBeRetried()) {
|
||||
delay(currentDelay)
|
||||
currentDelay = currentDelay.times(2L).coerceAtMost(maxDelayBeforeRetry)
|
||||
// Try again (loop)
|
||||
} else {
|
||||
throw when (exception) {
|
||||
is IOException -> Failure.NetworkConnection(exception)
|
||||
is IOException -> Failure.NetworkConnection(exception)
|
||||
is Failure.ServerError,
|
||||
is Failure.OtherServerError,
|
||||
is CancellationException -> exception
|
||||
else -> Failure.Unknown(exception)
|
||||
is CancellationException -> exception
|
||||
else -> Failure.Unknown(exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,8 +23,8 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.android.sdk.api.auth.data.SessionParams
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.MatrixError
|
||||
import org.matrix.android.sdk.api.failure.getRetryDelay
|
||||
import org.matrix.android.sdk.api.failure.isLimitExceededError
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
@ -145,17 +145,17 @@ internal class EventSenderProcessorCoroutine @Inject constructor(
|
||||
task.execute()
|
||||
} catch (exception: Throwable) {
|
||||
when {
|
||||
exception is IOException || exception is Failure.NetworkConnection -> {
|
||||
exception is IOException || exception is Failure.NetworkConnection -> {
|
||||
canReachServer.set(false)
|
||||
task.markAsFailedOrRetry(exception, 0)
|
||||
}
|
||||
(exception is Failure.ServerError && exception.error.code == MatrixError.M_LIMIT_EXCEEDED) -> {
|
||||
(exception.isLimitExceededError()) -> {
|
||||
task.markAsFailedOrRetry(exception, exception.getRetryDelay(3_000))
|
||||
}
|
||||
exception is CancellationException -> {
|
||||
exception is CancellationException -> {
|
||||
Timber.v("## $task has been cancelled, try next task")
|
||||
}
|
||||
else -> {
|
||||
else -> {
|
||||
Timber.v("## un-retryable error for $task, try next task")
|
||||
// this task is in error, check next one?
|
||||
task.onTaskFailed()
|
||||
|
@ -23,7 +23,7 @@ import org.matrix.android.sdk.api.auth.data.SessionParams
|
||||
import org.matrix.android.sdk.api.auth.data.sessionId
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.MatrixError
|
||||
import org.matrix.android.sdk.api.failure.isLimitExceededError
|
||||
import org.matrix.android.sdk.api.failure.isTokenError
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||
@ -171,7 +171,7 @@ internal class EventSenderProcessorThread @Inject constructor(
|
||||
break@retryLoop
|
||||
} catch (exception: Throwable) {
|
||||
when {
|
||||
exception is IOException || exception is Failure.NetworkConnection -> {
|
||||
exception is IOException || exception is Failure.NetworkConnection -> {
|
||||
canReachServer = false
|
||||
if (task.retryCount.getAndIncrement() >= 3) task.onTaskFailed()
|
||||
while (!canReachServer) {
|
||||
@ -180,7 +180,7 @@ internal class EventSenderProcessorThread @Inject constructor(
|
||||
waitForNetwork()
|
||||
}
|
||||
}
|
||||
(exception is Failure.ServerError && exception.error.code == MatrixError.M_LIMIT_EXCEEDED) -> {
|
||||
(exception.isLimitExceededError()) -> {
|
||||
if (task.retryCount.getAndIncrement() >= 3) task.onTaskFailed()
|
||||
Timber.v("## SendThread retryLoop retryable error for $task reason: ${exception.localizedMessage}")
|
||||
// wait a bit
|
||||
@ -188,17 +188,17 @@ internal class EventSenderProcessorThread @Inject constructor(
|
||||
sleep(3_000)
|
||||
continue@retryLoop
|
||||
}
|
||||
exception.isTokenError() -> {
|
||||
exception.isTokenError() -> {
|
||||
Timber.v("## SendThread retryLoop retryable TOKEN error, interrupt")
|
||||
// we can exit the loop
|
||||
task.onTaskFailed()
|
||||
throw InterruptedException()
|
||||
}
|
||||
exception is CancellationException -> {
|
||||
exception is CancellationException -> {
|
||||
Timber.v("## SendThread task has been cancelled")
|
||||
break@retryLoop
|
||||
}
|
||||
else -> {
|
||||
else -> {
|
||||
Timber.v("## SendThread retryLoop Un-Retryable error, try next task")
|
||||
// this task is in error, check next one?
|
||||
task.onTaskFailed()
|
||||
|
@ -151,6 +151,7 @@ internal class SyncWorker(context: Context, workerParameters: WorkerParameters,
|
||||
sessionId = sessionId,
|
||||
timeout = serverTimeoutInSeconds,
|
||||
delay = delayInSeconds,
|
||||
periodic = true,
|
||||
forceImmediate = forceImmediate
|
||||
)
|
||||
)
|
||||
|
@ -160,7 +160,7 @@ Formatter\.formatShortFileSize===1
|
||||
# android\.text\.TextUtils
|
||||
|
||||
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
|
||||
enum class===119
|
||||
enum class===121
|
||||
|
||||
### Do not import temporary legacy classes
|
||||
import org.matrix.android.sdk.internal.legacy.riot===3
|
||||
|
@ -55,6 +55,10 @@ class UiAllScreensSanityTest {
|
||||
fun allScreensTest() {
|
||||
IdlingPolicies.setMasterPolicyTimeout(120, TimeUnit.SECONDS)
|
||||
|
||||
elementRobot.onboarding {
|
||||
crawl()
|
||||
}
|
||||
|
||||
// Create an account
|
||||
val userId = "UiTest_" + UUID.randomUUID().toString()
|
||||
elementRobot.signUp(userId)
|
||||
|
@ -40,6 +40,10 @@ import timber.log.Timber
|
||||
|
||||
class ElementRobot {
|
||||
|
||||
fun onboarding(block: OnboardingRobot.() -> Unit) {
|
||||
block(OnboardingRobot())
|
||||
}
|
||||
|
||||
fun signUp(userId: String) {
|
||||
val onboardingRobot = OnboardingRobot()
|
||||
onboardingRobot.createAccount(userId = userId)
|
||||
|
@ -18,6 +18,7 @@ package im.vector.app.ui.robot
|
||||
|
||||
import androidx.test.espresso.Espresso.closeSoftKeyboard
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.Espresso.pressBack
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isRoot
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import com.adevinta.android.barista.assertion.BaristaEnabledAssertions.assertDisabled
|
||||
@ -31,6 +32,24 @@ import im.vector.app.waitForView
|
||||
|
||||
class OnboardingRobot {
|
||||
|
||||
fun crawl() {
|
||||
waitUntilViewVisible(withId(R.id.loginSplashSubmit))
|
||||
crawlGetStarted()
|
||||
crawlAlreadyHaveAccount()
|
||||
}
|
||||
|
||||
private fun crawlGetStarted() {
|
||||
clickOn(R.id.loginSplashSubmit)
|
||||
OnboardingServersRobot().crawlSignUp()
|
||||
pressBack()
|
||||
}
|
||||
|
||||
private fun crawlAlreadyHaveAccount() {
|
||||
clickOn(R.id.loginSplashAlreadyHaveAccount)
|
||||
OnboardingServersRobot().crawlSignIn()
|
||||
pressBack()
|
||||
}
|
||||
|
||||
fun createAccount(userId: String, password: String = "password", homeServerUrl: String = "http://10.0.2.2:8080") {
|
||||
initSession(true, userId, password, homeServerUrl)
|
||||
}
|
||||
@ -44,7 +63,7 @@ class OnboardingRobot {
|
||||
password: String,
|
||||
homeServerUrl: String) {
|
||||
waitUntilViewVisible(withId(R.id.loginSplashSubmit))
|
||||
assertDisplayed(R.id.loginSplashSubmit, R.string.login_splash_submit)
|
||||
assertDisplayed(R.id.loginSplashSubmit, R.string.login_splash_create_account)
|
||||
if (createAccount) {
|
||||
clickOn(R.id.loginSplashSubmit)
|
||||
} else {
|
||||
|
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.ui.robot
|
||||
|
||||
import androidx.test.espresso.Espresso
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import com.adevinta.android.barista.assertion.BaristaVisibilityAssertions
|
||||
import com.adevinta.android.barista.interaction.BaristaClickInteractions
|
||||
import com.adevinta.android.barista.interaction.BaristaEditTextInteractions
|
||||
import im.vector.app.R
|
||||
import im.vector.app.espresso.tools.waitUntilViewVisible
|
||||
|
||||
class OnboardingServersRobot {
|
||||
|
||||
fun crawlSignUp() {
|
||||
BaristaVisibilityAssertions.assertDisplayed(R.id.loginServerTitle, R.string.login_server_title)
|
||||
crawlMatrixServer(isSignUp = true)
|
||||
crawlEmsServer()
|
||||
crawlOtherServer(isSignUp = true)
|
||||
crawlSignInWithMatrixId()
|
||||
}
|
||||
|
||||
fun crawlSignIn() {
|
||||
BaristaVisibilityAssertions.assertDisplayed(R.id.loginServerTitle, R.string.login_server_title)
|
||||
crawlMatrixServer(isSignUp = false)
|
||||
crawlEmsServer()
|
||||
crawlOtherServer(isSignUp = false)
|
||||
crawlSignInWithMatrixId()
|
||||
}
|
||||
|
||||
private fun crawlOtherServer(isSignUp: Boolean) {
|
||||
BaristaClickInteractions.clickOn(R.id.loginServerChoiceOther)
|
||||
waitUntilViewVisible(ViewMatchers.withId(R.id.loginServerUrlFormTitle))
|
||||
BaristaEditTextInteractions.writeTo(R.id.loginServerUrlFormHomeServerUrl, "https://chat.mozilla.org")
|
||||
BaristaClickInteractions.clickOn(R.id.loginServerUrlFormSubmit)
|
||||
waitUntilViewVisible(ViewMatchers.withId(R.id.loginSignupSigninTitle))
|
||||
BaristaVisibilityAssertions.assertDisplayed(R.id.loginSignupSigninText, "Connect to chat.mozilla.org")
|
||||
BaristaVisibilityAssertions.assertDisplayed(R.id.loginSignupSigninSubmit, R.string.login_signin_sso)
|
||||
Espresso.pressBack()
|
||||
|
||||
BaristaEditTextInteractions.writeTo(R.id.loginServerUrlFormHomeServerUrl, "https://matrix.org")
|
||||
BaristaClickInteractions.clickOn(R.id.loginServerUrlFormSubmit)
|
||||
assetMatrixSignInOptions(isSignUp)
|
||||
Espresso.pressBack()
|
||||
Espresso.pressBack()
|
||||
}
|
||||
|
||||
private fun crawlEmsServer() {
|
||||
BaristaClickInteractions.clickOn(R.id.loginServerChoiceEms)
|
||||
waitUntilViewVisible(ViewMatchers.withId(R.id.loginServerUrlFormTitle))
|
||||
BaristaVisibilityAssertions.assertDisplayed(R.id.loginServerUrlFormTitle, R.string.login_connect_to_modular)
|
||||
|
||||
BaristaEditTextInteractions.writeTo(R.id.loginServerUrlFormHomeServerUrl, "https://one.ems.host")
|
||||
BaristaClickInteractions.clickOn(R.id.loginServerUrlFormSubmit)
|
||||
|
||||
waitUntilViewVisible(ViewMatchers.withId(R.id.loginSignupSigninTitle))
|
||||
BaristaVisibilityAssertions.assertDisplayed(R.id.loginSignupSigninText, "one.ems.host")
|
||||
BaristaVisibilityAssertions.assertDisplayed(R.id.loginSignupSigninSubmit, R.string.login_signin_sso)
|
||||
Espresso.pressBack()
|
||||
Espresso.pressBack()
|
||||
}
|
||||
|
||||
private fun crawlMatrixServer(isSignUp: Boolean) {
|
||||
BaristaClickInteractions.clickOn(R.id.loginServerChoiceMatrixOrg)
|
||||
assetMatrixSignInOptions(isSignUp)
|
||||
Espresso.pressBack()
|
||||
}
|
||||
|
||||
private fun assetMatrixSignInOptions(isSignUp: Boolean) {
|
||||
waitUntilViewVisible(ViewMatchers.withId(R.id.loginTitle))
|
||||
when (isSignUp) {
|
||||
true -> BaristaVisibilityAssertions.assertDisplayed(R.id.loginTitle, "Sign up to matrix.org")
|
||||
false -> BaristaVisibilityAssertions.assertDisplayed(R.id.loginTitle, "Connect to matrix.org")
|
||||
}
|
||||
}
|
||||
|
||||
private fun crawlSignInWithMatrixId() {
|
||||
BaristaClickInteractions.clickOn(R.id.loginServerIKnowMyIdSubmit)
|
||||
waitUntilViewVisible(ViewMatchers.withId(R.id.loginTitle))
|
||||
BaristaVisibilityAssertions.assertDisplayed(R.id.loginTitle, R.string.login_signin_matrix_id_title)
|
||||
Espresso.pressBack()
|
||||
}
|
||||
}
|
@ -37,7 +37,6 @@ import im.vector.app.features.home.room.detail.timeline.action.MessageActionsBot
|
||||
import im.vector.app.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet
|
||||
import im.vector.app.features.reactions.data.EmojiDataSource
|
||||
import im.vector.app.interactWithSheet
|
||||
import im.vector.app.waitForView
|
||||
import im.vector.app.withRetry
|
||||
import java.lang.Thread.sleep
|
||||
|
||||
@ -127,7 +126,7 @@ class RoomDetailRobot {
|
||||
|
||||
fun openSettings(block: RoomSettingsRobot.() -> Unit) {
|
||||
clickMenu(R.id.timeline_setting)
|
||||
waitForView(withId(R.id.roomProfileAvatarView))
|
||||
waitUntilViewVisible(withId(R.id.roomProfileAvatarView))
|
||||
sleep(1000)
|
||||
block(RoomSettingsRobot())
|
||||
pressBack()
|
||||
|
@ -78,11 +78,17 @@ class AppStateHandler @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun setCurrentSpace(spaceId: String?, session: Session? = null) {
|
||||
fun setCurrentSpace(spaceId: String?, session: Session? = null, persistNow: Boolean = false) {
|
||||
val uSession = session ?: activeSessionHolder.getSafeActiveSession() ?: return
|
||||
if (selectedSpaceDataSource.currentValue?.orNull() is RoomGroupingMethod.BySpace &&
|
||||
spaceId == selectedSpaceDataSource.currentValue?.orNull()?.space()?.roomId) return
|
||||
val spaceSum = spaceId?.let { uSession.getRoomSummary(spaceId) }
|
||||
|
||||
if (persistNow) {
|
||||
uiStateRepository.storeGroupingMethod(true, uSession.sessionId)
|
||||
uiStateRepository.storeSelectedSpace(spaceSum?.roomId, uSession.sessionId)
|
||||
}
|
||||
|
||||
selectedSpaceDataSource.post(Option.just(RoomGroupingMethod.BySpace(spaceSum)))
|
||||
if (spaceId != null) {
|
||||
uSession.coroutineScope.launch(Dispatchers.IO) {
|
||||
|
@ -21,7 +21,7 @@ import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
|
||||
import im.vector.app.core.error.ErrorFormatter
|
||||
import im.vector.app.features.analytics.VectorAnalytics
|
||||
import im.vector.app.features.analytics.AnalyticsTracker
|
||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.navigation.Navigator
|
||||
@ -56,7 +56,7 @@ interface SingletonEntryPoint {
|
||||
|
||||
fun pinLocker(): PinLocker
|
||||
|
||||
fun analytics(): VectorAnalytics
|
||||
fun analyticsTracker(): AnalyticsTracker
|
||||
|
||||
fun webRtcCallManager(): WebRtcCallManager
|
||||
|
||||
|
@ -33,6 +33,7 @@ import im.vector.app.core.error.DefaultErrorFormatter
|
||||
import im.vector.app.core.error.ErrorFormatter
|
||||
import im.vector.app.core.time.Clock
|
||||
import im.vector.app.core.time.DefaultClock
|
||||
import im.vector.app.features.analytics.AnalyticsTracker
|
||||
import im.vector.app.features.analytics.VectorAnalytics
|
||||
import im.vector.app.features.analytics.impl.DefaultVectorAnalytics
|
||||
import im.vector.app.features.invite.AutoAcceptInvites
|
||||
@ -64,6 +65,9 @@ abstract class VectorBindModule {
|
||||
@Binds
|
||||
abstract fun bindVectorAnalytics(analytics: DefaultVectorAnalytics): VectorAnalytics
|
||||
|
||||
@Binds
|
||||
abstract fun bindAnalyticsTracker(analytics: DefaultVectorAnalytics): AnalyticsTracker
|
||||
|
||||
@Binds
|
||||
abstract fun bindErrorFormatter(formatter: DefaultErrorFormatter): ErrorFormatter
|
||||
|
||||
|
@ -26,7 +26,6 @@ import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.ClickListener
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence
|
||||
import im.vector.app.core.epoxy.onClick
|
||||
import im.vector.app.core.extensions.setTextOrHide
|
||||
import im.vector.app.features.displayname.getBestName
|
||||
@ -34,6 +33,7 @@ import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.home.room.detail.timeline.item.BindingOptions
|
||||
import im.vector.app.features.home.room.detail.timeline.tools.findPillsAndProcess
|
||||
import im.vector.app.features.media.ImageContentRenderer
|
||||
import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
|
||||
/**
|
||||
|
@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.MatrixError
|
||||
import org.matrix.android.sdk.api.failure.MatrixIdFailure
|
||||
import org.matrix.android.sdk.api.failure.isInvalidPassword
|
||||
import org.matrix.android.sdk.api.failure.isLimitExceededError
|
||||
import org.matrix.android.sdk.api.session.identity.IdentityServiceError
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.SocketTimeoutException
|
||||
@ -58,53 +59,53 @@ class DefaultErrorFormatter @Inject constructor(
|
||||
}
|
||||
is Failure.ServerError -> {
|
||||
when {
|
||||
throwable.error.code == MatrixError.M_CONSENT_NOT_GIVEN -> {
|
||||
throwable.error.code == MatrixError.M_CONSENT_NOT_GIVEN -> {
|
||||
// Special case for terms and conditions
|
||||
stringProvider.getString(R.string.error_terms_not_accepted)
|
||||
}
|
||||
throwable.isInvalidPassword() -> {
|
||||
throwable.isInvalidPassword() -> {
|
||||
stringProvider.getString(R.string.auth_invalid_login_param)
|
||||
}
|
||||
throwable.error.code == MatrixError.M_USER_IN_USE -> {
|
||||
throwable.error.code == MatrixError.M_USER_IN_USE -> {
|
||||
stringProvider.getString(R.string.login_signup_error_user_in_use)
|
||||
}
|
||||
throwable.error.code == MatrixError.M_BAD_JSON -> {
|
||||
throwable.error.code == MatrixError.M_BAD_JSON -> {
|
||||
stringProvider.getString(R.string.login_error_bad_json)
|
||||
}
|
||||
throwable.error.code == MatrixError.M_NOT_JSON -> {
|
||||
throwable.error.code == MatrixError.M_NOT_JSON -> {
|
||||
stringProvider.getString(R.string.login_error_not_json)
|
||||
}
|
||||
throwable.error.code == MatrixError.M_THREEPID_DENIED -> {
|
||||
throwable.error.code == MatrixError.M_THREEPID_DENIED -> {
|
||||
stringProvider.getString(R.string.login_error_threepid_denied)
|
||||
}
|
||||
throwable.error.code == MatrixError.M_LIMIT_EXCEEDED -> {
|
||||
throwable.isLimitExceededError() -> {
|
||||
limitExceededError(throwable.error)
|
||||
}
|
||||
throwable.error.code == MatrixError.M_TOO_LARGE -> {
|
||||
throwable.error.code == MatrixError.M_TOO_LARGE -> {
|
||||
stringProvider.getString(R.string.error_file_too_big_simple)
|
||||
}
|
||||
throwable.error.code == MatrixError.M_THREEPID_NOT_FOUND -> {
|
||||
throwable.error.code == MatrixError.M_THREEPID_NOT_FOUND -> {
|
||||
stringProvider.getString(R.string.login_reset_password_error_not_found)
|
||||
}
|
||||
throwable.error.code == MatrixError.M_USER_DEACTIVATED -> {
|
||||
throwable.error.code == MatrixError.M_USER_DEACTIVATED -> {
|
||||
stringProvider.getString(R.string.auth_invalid_login_deactivated_account)
|
||||
}
|
||||
throwable.error.code == MatrixError.M_THREEPID_IN_USE &&
|
||||
throwable.error.message == "Email is already in use" -> {
|
||||
throwable.error.message == "Email is already in use" -> {
|
||||
stringProvider.getString(R.string.account_email_already_used_error)
|
||||
}
|
||||
throwable.error.code == MatrixError.M_THREEPID_IN_USE &&
|
||||
throwable.error.message == "MSISDN is already in use" -> {
|
||||
throwable.error.message == "MSISDN is already in use" -> {
|
||||
stringProvider.getString(R.string.account_phone_number_already_used_error)
|
||||
}
|
||||
throwable.error.code == MatrixError.M_THREEPID_AUTH_FAILED -> {
|
||||
throwable.error.code == MatrixError.M_THREEPID_AUTH_FAILED -> {
|
||||
stringProvider.getString(R.string.error_threepid_auth_failed)
|
||||
}
|
||||
throwable.error.code == MatrixError.M_UNKNOWN &&
|
||||
throwable.error.message == "Not allowed to join this room" -> {
|
||||
stringProvider.getString(R.string.room_error_access_unauthorized)
|
||||
}
|
||||
else -> {
|
||||
else -> {
|
||||
throwable.error.message.takeIf { it.isNotEmpty() }
|
||||
?: throwable.error.code.takeIf { it.isNotEmpty() }
|
||||
}
|
||||
|
@ -30,7 +30,8 @@ abstract class SimpleFragmentActivity : VectorBaseActivity<ActivityBinding>() {
|
||||
final override fun getCoordinatorLayout() = views.coordinatorLayout
|
||||
|
||||
override fun initUiAndData() {
|
||||
configureToolbar(views.toolbar)
|
||||
setupToolbar(views.toolbar)
|
||||
.allowBack(true)
|
||||
waitingView = views.waitingView.waitingView
|
||||
}
|
||||
|
||||
|
@ -62,10 +62,13 @@ import im.vector.app.core.extensions.restart
|
||||
import im.vector.app.core.extensions.setTextOrHide
|
||||
import im.vector.app.core.extensions.singletonEntryPoint
|
||||
import im.vector.app.core.extensions.toMvRxBundle
|
||||
import im.vector.app.core.utils.ToolbarConfig
|
||||
import im.vector.app.core.utils.toast
|
||||
import im.vector.app.features.MainActivity
|
||||
import im.vector.app.features.MainActivityArgs
|
||||
import im.vector.app.features.analytics.VectorAnalytics
|
||||
import im.vector.app.features.analytics.AnalyticsTracker
|
||||
import im.vector.app.features.analytics.plan.Screen
|
||||
import im.vector.app.features.analytics.screen.ScreenEvent
|
||||
import im.vector.app.features.configuration.VectorConfiguration
|
||||
import im.vector.app.features.consent.ConsentNotGivenHelper
|
||||
import im.vector.app.features.navigation.Navigator
|
||||
@ -90,6 +93,15 @@ import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), MavericksView {
|
||||
/* ==========================================================================================
|
||||
* Analytics
|
||||
* ========================================================================================== */
|
||||
|
||||
protected var analyticsScreenName: Screen.ScreenName? = null
|
||||
private var screenEvent: ScreenEvent? = null
|
||||
|
||||
protected lateinit var analyticsTracker: AnalyticsTracker
|
||||
|
||||
/* ==========================================================================================
|
||||
* View
|
||||
* ========================================================================================== */
|
||||
@ -115,6 +127,8 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
||||
.launchIn(lifecycleScope)
|
||||
}
|
||||
|
||||
var toolbar: ToolbarConfig? = null
|
||||
|
||||
/* ==========================================================================================
|
||||
* Views
|
||||
* ========================================================================================== */
|
||||
@ -133,7 +147,6 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
||||
private lateinit var sessionListener: SessionListener
|
||||
protected lateinit var bugReporter: BugReporter
|
||||
private lateinit var pinLocker: PinLocker
|
||||
protected lateinit var analytics: VectorAnalytics
|
||||
|
||||
@Inject
|
||||
lateinit var rageShake: RageShake
|
||||
@ -189,7 +202,7 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
||||
configurationViewModel = viewModelProvider.get(ConfigurationViewModel::class.java)
|
||||
bugReporter = singletonEntryPoint.bugReporter()
|
||||
pinLocker = singletonEntryPoint.pinLocker()
|
||||
analytics = singletonEntryPoint.analytics()
|
||||
analyticsTracker = singletonEntryPoint.analyticsTracker()
|
||||
navigator = singletonEntryPoint.navigator()
|
||||
activeSessionHolder = singletonEntryPoint.activeSessionHolder()
|
||||
vectorPreferences = singletonEntryPoint.vectorPreferences()
|
||||
@ -324,7 +337,7 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
Timber.i("onResume Activity ${javaClass.simpleName}")
|
||||
|
||||
screenEvent = analyticsScreenName?.let { ScreenEvent(it) }
|
||||
configurationViewModel.onActivityResumed()
|
||||
|
||||
if (this !is BugReportActivity && vectorPreferences.useRageshake()) {
|
||||
@ -363,6 +376,7 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
screenEvent?.send(analyticsTracker, analyticsScreenName)
|
||||
Timber.i("onPause Activity ${javaClass.simpleName}")
|
||||
|
||||
rageShake.stop()
|
||||
@ -497,18 +511,6 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
||||
*/
|
||||
protected fun isFirstCreation() = savedInstanceState == null
|
||||
|
||||
/**
|
||||
* Configure the Toolbar, with default back button.
|
||||
*/
|
||||
protected fun configureToolbar(toolbar: MaterialToolbar, displayBack: Boolean = true) {
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.let {
|
||||
it.setDisplayShowHomeEnabled(displayBack)
|
||||
it.setDisplayHomeAsUpEnabled(displayBack)
|
||||
it.title = null
|
||||
}
|
||||
}
|
||||
|
||||
// ==============================================================================================
|
||||
// Handle loading view (also called waiting view or spinner view)
|
||||
// ==============================================================================================
|
||||
@ -618,4 +620,13 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
||||
toast(getString(R.string.not_implemented))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets toolbar as actionBar
|
||||
*
|
||||
* @return Instance of [ToolbarConfig] with set of helper methods to configure toolbar
|
||||
* */
|
||||
fun setupToolbar(toolbar: MaterialToolbar) = ToolbarConfig(this, toolbar).also {
|
||||
this.toolbar = it.setup()
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,9 @@ import im.vector.app.core.di.ActivityEntryPoint
|
||||
import im.vector.app.core.extensions.singletonEntryPoint
|
||||
import im.vector.app.core.extensions.toMvRxBundle
|
||||
import im.vector.app.core.utils.DimensionConverter
|
||||
import im.vector.app.features.analytics.VectorAnalytics
|
||||
import im.vector.app.features.analytics.AnalyticsTracker
|
||||
import im.vector.app.features.analytics.plan.Screen
|
||||
import im.vector.app.features.analytics.screen.ScreenEvent
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import reactivecircus.flowbinding.android.view.clicks
|
||||
@ -47,6 +49,14 @@ import timber.log.Timber
|
||||
* Add Mavericks capabilities, handle DI and bindings.
|
||||
*/
|
||||
abstract class VectorBaseBottomSheetDialogFragment<VB : ViewBinding> : BottomSheetDialogFragment(), MavericksView {
|
||||
/* ==========================================================================================
|
||||
* Analytics
|
||||
* ========================================================================================== */
|
||||
|
||||
protected var analyticsScreenName: Screen.ScreenName? = null
|
||||
private var screenEvent: ScreenEvent? = null
|
||||
|
||||
protected lateinit var analyticsTracker: AnalyticsTracker
|
||||
|
||||
/* ==========================================================================================
|
||||
* View
|
||||
@ -84,8 +94,6 @@ abstract class VectorBaseBottomSheetDialogFragment<VB : ViewBinding> : BottomShe
|
||||
|
||||
open val showExpanded = false
|
||||
|
||||
protected lateinit var analytics: VectorAnalytics
|
||||
|
||||
interface ResultListener {
|
||||
fun onBottomSheetResult(resultCode: Int, data: Any?)
|
||||
|
||||
@ -124,13 +132,19 @@ abstract class VectorBaseBottomSheetDialogFragment<VB : ViewBinding> : BottomShe
|
||||
val activityEntryPoint = EntryPointAccessors.fromActivity(vectorBaseActivity, ActivityEntryPoint::class.java)
|
||||
viewModelFactory = activityEntryPoint.viewModelFactory()
|
||||
val singletonEntryPoint = context.singletonEntryPoint()
|
||||
analytics = singletonEntryPoint.analytics()
|
||||
analyticsTracker = singletonEntryPoint.analyticsTracker()
|
||||
super.onAttach(context)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
Timber.i("onResume BottomSheet ${javaClass.simpleName}")
|
||||
screenEvent = analyticsScreenName?.let { ScreenEvent(it) }
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
screenEvent?.send(analyticsTracker)
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
|
@ -42,7 +42,10 @@ import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
|
||||
import im.vector.app.core.error.ErrorFormatter
|
||||
import im.vector.app.core.extensions.singletonEntryPoint
|
||||
import im.vector.app.core.extensions.toMvRxBundle
|
||||
import im.vector.app.features.analytics.VectorAnalytics
|
||||
import im.vector.app.core.utils.ToolbarConfig
|
||||
import im.vector.app.features.analytics.AnalyticsTracker
|
||||
import im.vector.app.features.analytics.plan.Screen
|
||||
import im.vector.app.features.analytics.screen.ScreenEvent
|
||||
import im.vector.app.features.navigation.Navigator
|
||||
import im.vector.lib.ui.styles.dialogs.MaterialProgressDialog
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
@ -51,6 +54,18 @@ import reactivecircus.flowbinding.android.view.clicks
|
||||
import timber.log.Timber
|
||||
|
||||
abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView {
|
||||
/* ==========================================================================================
|
||||
* Analytics
|
||||
* ========================================================================================== */
|
||||
|
||||
protected var analyticsScreenName: Screen.ScreenName? = null
|
||||
private var screenEvent: ScreenEvent? = null
|
||||
|
||||
protected lateinit var analyticsTracker: AnalyticsTracker
|
||||
|
||||
/* ==========================================================================================
|
||||
* Activity
|
||||
* ========================================================================================== */
|
||||
|
||||
protected val vectorBaseActivity: VectorBaseActivity<*> by lazy {
|
||||
activity as VectorBaseActivity<*>
|
||||
@ -61,12 +76,17 @@ abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView
|
||||
* ========================================================================================== */
|
||||
|
||||
protected lateinit var navigator: Navigator
|
||||
protected lateinit var analytics: VectorAnalytics
|
||||
protected lateinit var errorFormatter: ErrorFormatter
|
||||
protected lateinit var unrecognizedCertificateDialog: UnrecognizedCertificateDialog
|
||||
|
||||
private var progress: AlertDialog? = null
|
||||
|
||||
/**
|
||||
* [ToolbarConfig] instance from host activity
|
||||
* */
|
||||
protected var toolbar: ToolbarConfig? = null
|
||||
get() = (activity as? VectorBaseActivity<*>)?.toolbar
|
||||
private set
|
||||
/* ==========================================================================================
|
||||
* View model
|
||||
* ========================================================================================== */
|
||||
@ -98,7 +118,7 @@ abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView
|
||||
val activityEntryPoint = EntryPointAccessors.fromActivity(vectorBaseActivity, ActivityEntryPoint::class.java)
|
||||
navigator = singletonEntryPoint.navigator()
|
||||
errorFormatter = singletonEntryPoint.errorFormatter()
|
||||
analytics = singletonEntryPoint.analytics()
|
||||
analyticsTracker = singletonEntryPoint.analyticsTracker()
|
||||
unrecognizedCertificateDialog = singletonEntryPoint.unrecognizedCertificateDialog()
|
||||
viewModelFactory = activityEntryPoint.viewModelFactory()
|
||||
childFragmentManager.fragmentFactory = activityEntryPoint.fragmentFactory()
|
||||
@ -125,12 +145,14 @@ abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
Timber.i("onResume Fragment ${javaClass.simpleName}")
|
||||
screenEvent = analyticsScreenName?.let { ScreenEvent(it) }
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
Timber.i("onPause Fragment ${javaClass.simpleName}")
|
||||
screenEvent?.send(analyticsTracker)
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@ -213,13 +235,12 @@ abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView
|
||||
* ========================================================================================== */
|
||||
|
||||
/**
|
||||
* Configure the Toolbar.
|
||||
*/
|
||||
protected fun setupToolbar(toolbar: MaterialToolbar) {
|
||||
val parentActivity = vectorBaseActivity
|
||||
if (parentActivity is ToolbarConfigurable) {
|
||||
parentActivity.configure(toolbar)
|
||||
}
|
||||
* Sets toolbar as actionBar for current activity
|
||||
*
|
||||
* @return Instance of [ToolbarConfig] with set of helper methods to configure toolbar
|
||||
* */
|
||||
protected fun setupToolbar(toolbar: MaterialToolbar): ToolbarConfig {
|
||||
return vectorBaseActivity.setupToolbar(toolbar)
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
|
@ -24,10 +24,10 @@ import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.ClickListener
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence
|
||||
import im.vector.app.core.epoxy.onClick
|
||||
import im.vector.app.core.extensions.setTextOrHide
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence
|
||||
|
||||
/**
|
||||
* A generic list item.
|
||||
|
@ -28,9 +28,9 @@ import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.ClickListener
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence
|
||||
import im.vector.app.core.epoxy.onClick
|
||||
import im.vector.app.core.extensions.setTextOrHide
|
||||
import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence
|
||||
|
||||
/**
|
||||
* A generic list item.
|
||||
|
@ -28,10 +28,10 @@ import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.ClickListener
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence
|
||||
import im.vector.app.core.epoxy.onClick
|
||||
import im.vector.app.core.extensions.setTextOrHide
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence
|
||||
|
||||
/**
|
||||
* A generic list item with a rounded corner background and an optional icon
|
||||
|
@ -27,10 +27,10 @@ import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.ClickListener
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence
|
||||
import im.vector.app.core.epoxy.onClick
|
||||
import im.vector.app.core.extensions.setTextOrHide
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence
|
||||
|
||||
/**
|
||||
* A generic list item.
|
||||
|
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (c) 2022 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.utils
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import im.vector.app.R
|
||||
|
||||
/**
|
||||
* Helper class to configure toolbar.
|
||||
* Wraps [MaterialToolbar] providing set of methods to configure it
|
||||
*/
|
||||
class ToolbarConfig(val activity: AppCompatActivity, val toolbar: MaterialToolbar) {
|
||||
private var customBackResId: Int? = null
|
||||
|
||||
fun setup() = apply {
|
||||
activity.setSupportActionBar(toolbar)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegating property for [toolbar.title]
|
||||
* */
|
||||
var title: CharSequence? by toolbar::title
|
||||
|
||||
/**
|
||||
* Delegating property for [toolbar.subtitle]
|
||||
* */
|
||||
var subtitle: CharSequence? by toolbar::subtitle
|
||||
|
||||
/**
|
||||
* Sets toolbar's title text
|
||||
* */
|
||||
fun setTitle(title: CharSequence?) = apply { toolbar.title = title }
|
||||
|
||||
/**
|
||||
* Sets toolbar's title text using provided string resource
|
||||
* */
|
||||
fun setTitle(@StringRes titleRes: Int) = apply { toolbar.setTitle(titleRes) }
|
||||
|
||||
/**
|
||||
* Sets toolbar's subtitle text
|
||||
* */
|
||||
fun setSubtitle(subtitle: String?) = apply { toolbar.subtitle = subtitle }
|
||||
|
||||
/**
|
||||
* Sets toolbar's title text using provided string resource
|
||||
* */
|
||||
fun setSubtitle(@StringRes subtitleRes: Int) = apply { toolbar.subtitle = activity.getString(subtitleRes) }
|
||||
|
||||
/**
|
||||
* Enables/disables navigate back button
|
||||
*
|
||||
* @param isAllowed defines if back button is enabled. Default [true]
|
||||
* @param useCross defines if cross icon should be used instead of arrow. Default [false]
|
||||
* */
|
||||
fun allowBack(isAllowed: Boolean = true, useCross: Boolean = false) = apply {
|
||||
activity.supportActionBar?.let {
|
||||
it.setDisplayShowHomeEnabled(isAllowed)
|
||||
it.setDisplayHomeAsUpEnabled(isAllowed)
|
||||
if (isAllowed && useCross) {
|
||||
val navResId = customBackResId ?: R.drawable.ic_x_18dp
|
||||
toolbar.navigationIcon = AppCompatResources.getDrawable(activity, navResId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -35,6 +35,6 @@ interface VectorFeatures {
|
||||
class DefaultVectorFeatures : VectorFeatures {
|
||||
override fun onboardingVariant(): VectorFeatures.OnboardingVariant = BuildConfig.ONBOARDING_VARIANT
|
||||
override fun isOnboardingAlreadyHaveAccountSplashEnabled() = true
|
||||
override fun isOnboardingSplashCarouselEnabled() = false
|
||||
override fun isOnboardingSplashCarouselEnabled() = true
|
||||
override fun isOnboardingUseCaseEnabled() = false
|
||||
}
|
||||
|
@ -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,19 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.core.platform
|
||||
package im.vector.app.features.analytics
|
||||
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
|
||||
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
|
||||
|
||||
interface ToolbarConfigurable {
|
||||
interface AnalyticsTracker {
|
||||
/**
|
||||
* Capture an Event
|
||||
*/
|
||||
fun capture(event: VectorAnalyticsEvent)
|
||||
|
||||
fun configure(toolbar: MaterialToolbar)
|
||||
/**
|
||||
* Track a displayed screen
|
||||
*/
|
||||
fun screen(screen: VectorAnalyticsScreen)
|
||||
}
|
@ -17,8 +17,8 @@
|
||||
package im.vector.app.features.analytics
|
||||
|
||||
import im.vector.app.core.time.Clock
|
||||
import im.vector.app.core.utils.compat.removeIfCompat
|
||||
import im.vector.app.features.analytics.plan.Error
|
||||
import im.vector.lib.core.utils.compat.removeIfCompat
|
||||
import im.vector.lib.core.utils.flow.tickerFlow
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@ -49,7 +49,7 @@ private const val CHECK_INTERVAL = 2_000L
|
||||
*/
|
||||
@Singleton
|
||||
class DecryptionFailureTracker @Inject constructor(
|
||||
private val vectorAnalytics: VectorAnalytics,
|
||||
private val analyticsTracker: AnalyticsTracker,
|
||||
private val clock: Clock
|
||||
) {
|
||||
|
||||
@ -136,7 +136,7 @@ class DecryptionFailureTracker @Inject constructor(
|
||||
// for now we ignore events already reported even if displayed again?
|
||||
.filter { alreadyReported.contains(it).not() }
|
||||
.forEach { failedEventId ->
|
||||
vectorAnalytics.capture(Error(failedEventId, Error.Domain.E2EE, aggregation.key))
|
||||
analyticsTracker.capture(Error(failedEventId, Error.Domain.E2EE, aggregation.key))
|
||||
alreadyReported.add(failedEventId)
|
||||
}
|
||||
}
|
||||
|
@ -16,11 +16,9 @@
|
||||
|
||||
package im.vector.app.features.analytics
|
||||
|
||||
import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
|
||||
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface VectorAnalytics {
|
||||
interface VectorAnalytics : AnalyticsTracker {
|
||||
/**
|
||||
* Return a Flow of Boolean, true if the user has given their consent
|
||||
*/
|
||||
@ -60,14 +58,4 @@ interface VectorAnalytics {
|
||||
* To be called when application is started
|
||||
*/
|
||||
fun init()
|
||||
|
||||
/**
|
||||
* Capture an Event
|
||||
*/
|
||||
fun capture(event: VectorAnalyticsEvent)
|
||||
|
||||
/**
|
||||
* Track a displayed screen
|
||||
*/
|
||||
fun screen(screen: VectorAnalyticsScreen)
|
||||
}
|
||||
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.analytics.extensions
|
||||
|
||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
|
||||
|
||||
fun Int?.toAnalyticsRoomSize(): JoinedRoom.RoomSize {
|
||||
return when (this) {
|
||||
null,
|
||||
2 -> JoinedRoom.RoomSize.Two
|
||||
in 3..10 -> JoinedRoom.RoomSize.ThreeToTen
|
||||
in 11..100 -> JoinedRoom.RoomSize.ElevenToOneHundred
|
||||
in 101..1000 -> JoinedRoom.RoomSize.OneHundredAndOneToAThousand
|
||||
else -> JoinedRoom.RoomSize.MoreThanAThousand
|
||||
}
|
||||
}
|
||||
|
||||
fun RoomSummary?.toAnalyticsJoinedRoom(): JoinedRoom {
|
||||
return JoinedRoom(
|
||||
isDM = this?.isDirect.orFalse(),
|
||||
roomSize = this?.joinedMembersCount?.toAnalyticsRoomSize() ?: JoinedRoom.RoomSize.Two
|
||||
)
|
||||
}
|
||||
|
||||
fun PublicRoom.toAnalyticsJoinedRoom(): JoinedRoom {
|
||||
return JoinedRoom(
|
||||
isDM = false,
|
||||
roomSize = numJoinedMembers.toAnalyticsRoomSize()
|
||||
)
|
||||
}
|
@ -25,22 +25,22 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
|
||||
* Triggered when a call has ended.
|
||||
*/
|
||||
data class CallEnded(
|
||||
/**
|
||||
* The duration of the call in milliseconds.
|
||||
*/
|
||||
val durationMs: Int,
|
||||
/**
|
||||
* Whether its a video call or not.
|
||||
*/
|
||||
val isVideo: Boolean,
|
||||
/**
|
||||
* Number of participants in the call.
|
||||
*/
|
||||
val numParticipants: Int,
|
||||
/**
|
||||
* Whether this user placed it.
|
||||
*/
|
||||
val placed: Boolean,
|
||||
/**
|
||||
* The duration of the call in milliseconds.
|
||||
*/
|
||||
val durationMs: Int,
|
||||
/**
|
||||
* Whether its a video call or not.
|
||||
*/
|
||||
val isVideo: Boolean,
|
||||
/**
|
||||
* Number of participants in the call.
|
||||
*/
|
||||
val numParticipants: Int,
|
||||
/**
|
||||
* Whether this user placed it.
|
||||
*/
|
||||
val placed: Boolean,
|
||||
) : VectorAnalyticsEvent {
|
||||
|
||||
override fun getName() = "CallEnded"
|
||||
|
@ -25,18 +25,18 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
|
||||
* Triggered when an error occurred in a call.
|
||||
*/
|
||||
data class CallError(
|
||||
/**
|
||||
* Whether its a video call or not.
|
||||
*/
|
||||
val isVideo: Boolean,
|
||||
/**
|
||||
* Number of participants in the call.
|
||||
*/
|
||||
val numParticipants: Int,
|
||||
/**
|
||||
* Whether this user placed it.
|
||||
*/
|
||||
val placed: Boolean,
|
||||
/**
|
||||
* Whether its a video call or not.
|
||||
*/
|
||||
val isVideo: Boolean,
|
||||
/**
|
||||
* Number of participants in the call.
|
||||
*/
|
||||
val numParticipants: Int,
|
||||
/**
|
||||
* Whether this user placed it.
|
||||
*/
|
||||
val placed: Boolean,
|
||||
) : VectorAnalyticsEvent {
|
||||
|
||||
override fun getName() = "CallError"
|
||||
|
@ -25,18 +25,18 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
|
||||
* Triggered when a call is started.
|
||||
*/
|
||||
data class CallStarted(
|
||||
/**
|
||||
* Whether its a video call or not.
|
||||
*/
|
||||
val isVideo: Boolean,
|
||||
/**
|
||||
* Number of participants in the call.
|
||||
*/
|
||||
val numParticipants: Int,
|
||||
/**
|
||||
* Whether this user placed it.
|
||||
*/
|
||||
val placed: Boolean,
|
||||
/**
|
||||
* Whether its a video call or not.
|
||||
*/
|
||||
val isVideo: Boolean,
|
||||
/**
|
||||
* Number of participants in the call.
|
||||
*/
|
||||
val numParticipants: Int,
|
||||
/**
|
||||
* Whether this user placed it.
|
||||
*/
|
||||
val placed: Boolean,
|
||||
) : VectorAnalyticsEvent {
|
||||
|
||||
override fun getName() = "CallStarted"
|
||||
|
@ -25,14 +25,14 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
|
||||
* Triggered when the user clicks/taps on a UI element.
|
||||
*/
|
||||
data class Click(
|
||||
/**
|
||||
* The index of the element, if its in a list of elements.
|
||||
*/
|
||||
val index: Int? = null,
|
||||
/**
|
||||
* The unique name of this element.
|
||||
*/
|
||||
val name: Name,
|
||||
/**
|
||||
* The index of the element, if its in a list of elements.
|
||||
*/
|
||||
val index: Int? = null,
|
||||
/**
|
||||
* The unique name of this element.
|
||||
*/
|
||||
val name: Name,
|
||||
) : VectorAnalyticsEvent {
|
||||
|
||||
enum class Name {
|
||||
|
@ -25,10 +25,10 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
|
||||
* Triggered when the user creates a room.
|
||||
*/
|
||||
data class CreatedRoom(
|
||||
/**
|
||||
* Whether the room is a DM.
|
||||
*/
|
||||
val isDM: Boolean,
|
||||
/**
|
||||
* Whether the room is a DM.
|
||||
*/
|
||||
val isDM: Boolean,
|
||||
) : VectorAnalyticsEvent {
|
||||
|
||||
override fun getName() = "CreatedRoom"
|
||||
|
@ -25,12 +25,12 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
|
||||
* Triggered when an error occurred
|
||||
*/
|
||||
data class Error(
|
||||
/**
|
||||
* Context - client defined, can be used for debugging
|
||||
*/
|
||||
val context: String? = null,
|
||||
val domain: Domain,
|
||||
val name: Name,
|
||||
/**
|
||||
* Context - client defined, can be used for debugging
|
||||
*/
|
||||
val context: String? = null,
|
||||
val domain: Domain,
|
||||
val name: Name,
|
||||
) : VectorAnalyticsEvent {
|
||||
|
||||
enum class Domain {
|
||||
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.analytics.plan
|
||||
|
||||
import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
|
||||
|
||||
// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT
|
||||
// https://github.com/matrix-org/matrix-analytics-events/
|
||||
|
||||
/**
|
||||
* The user properties to apply when identifying
|
||||
*/
|
||||
data class Identity(
|
||||
/**
|
||||
* The selected messaging use case during the onboarding flow.
|
||||
*/
|
||||
val ftueUseCaseSelection: FtueUseCaseSelection? = null,
|
||||
) : VectorAnalyticsEvent {
|
||||
|
||||
enum class FtueUseCaseSelection {
|
||||
/**
|
||||
* The third option, Communities.
|
||||
*/
|
||||
CommunityMessaging,
|
||||
|
||||
/**
|
||||
* The first option, Friends and family.
|
||||
*/
|
||||
PersonalMessaging,
|
||||
|
||||
/**
|
||||
* The footer option to skip the question.
|
||||
*/
|
||||
Skip,
|
||||
|
||||
/**
|
||||
* The second option, Teams.
|
||||
*/
|
||||
WorkMessaging,
|
||||
}
|
||||
|
||||
override fun getName() = "Identity"
|
||||
|
||||
override fun getProperties(): Map<String, Any>? {
|
||||
return mutableMapOf<String, Any>().apply {
|
||||
ftueUseCaseSelection?.let { put("ftueUseCaseSelection", it.name) }
|
||||
}.takeIf { it.isNotEmpty() }
|
||||
}
|
||||
}
|
@ -25,14 +25,14 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
|
||||
* Triggered when the user joins a room.
|
||||
*/
|
||||
data class JoinedRoom(
|
||||
/**
|
||||
* Whether the room is a DM.
|
||||
*/
|
||||
val isDM: Boolean,
|
||||
/**
|
||||
* The size of the room.
|
||||
*/
|
||||
val roomSize: RoomSize,
|
||||
/**
|
||||
* Whether the room is a DM.
|
||||
*/
|
||||
val isDM: Boolean,
|
||||
/**
|
||||
* The size of the room.
|
||||
*/
|
||||
val roomSize: RoomSize,
|
||||
) : VectorAnalyticsEvent {
|
||||
|
||||
enum class RoomSize {
|
||||
|
@ -25,22 +25,23 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
|
||||
* Triggered after timing an operation in the app.
|
||||
*/
|
||||
data class PerformanceTimer(
|
||||
/**
|
||||
* Client defined, can be used for debugging.
|
||||
*/
|
||||
val context: String? = null,
|
||||
/**
|
||||
* Client defined, an optional value to indicate how many items were handled during the operation.
|
||||
*/
|
||||
val itemCount: Int? = null,
|
||||
/**
|
||||
* The timer that is being reported.
|
||||
*/
|
||||
val name: Name,
|
||||
/**
|
||||
* The time reported by the timer in milliseconds.
|
||||
*/
|
||||
val timeMs: Int,
|
||||
/**
|
||||
* Client defined, can be used for debugging.
|
||||
*/
|
||||
val context: String? = null,
|
||||
/**
|
||||
* Client defined, an optional value to indicate how many items were
|
||||
* handled during the operation.
|
||||
*/
|
||||
val itemCount: Int? = null,
|
||||
/**
|
||||
* The timer that is being reported.
|
||||
*/
|
||||
val name: Name,
|
||||
/**
|
||||
* The time reported by the timer in milliseconds.
|
||||
*/
|
||||
val timeMs: Int,
|
||||
) : VectorAnalyticsEvent {
|
||||
|
||||
enum class Name {
|
||||
@ -55,7 +56,8 @@ data class PerformanceTimer(
|
||||
InitialSyncRequest,
|
||||
|
||||
/**
|
||||
* The time taken to display an event in the timeline that was opened from a notification.
|
||||
* The time taken to display an event in the timeline that was opened
|
||||
* from a notification.
|
||||
*/
|
||||
NotificationsOpenEvent,
|
||||
|
||||
@ -65,7 +67,8 @@ data class PerformanceTimer(
|
||||
StartupIncrementalSync,
|
||||
|
||||
/**
|
||||
* The duration of an initial /sync request during startup (if the store has been wiped).
|
||||
* The duration of an initial /sync request during startup (if the store
|
||||
* has been wiped).
|
||||
*/
|
||||
StartupInitialSync,
|
||||
|
||||
@ -80,7 +83,8 @@ data class PerformanceTimer(
|
||||
StartupStorePreload,
|
||||
|
||||
/**
|
||||
* The time to load all data from the store (including StartupStorePreload time).
|
||||
* The time to load all data from the store (including
|
||||
* StartupStorePreload time).
|
||||
*/
|
||||
StartupStoreReady,
|
||||
}
|
||||
|
@ -25,28 +25,221 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
|
||||
* Triggered when the user changed screen
|
||||
*/
|
||||
data class Screen(
|
||||
/**
|
||||
* How long the screen was displayed for in milliseconds.
|
||||
*/
|
||||
val durationMs: Int? = null,
|
||||
val screenName: ScreenName,
|
||||
/**
|
||||
* How long the screen was displayed for in milliseconds.
|
||||
*/
|
||||
val durationMs: Int? = null,
|
||||
val screenName: ScreenName,
|
||||
) : VectorAnalyticsScreen {
|
||||
|
||||
enum class ScreenName {
|
||||
/**
|
||||
* The screen shown to create a new (non-direct) room.
|
||||
*/
|
||||
CreateRoom,
|
||||
|
||||
/**
|
||||
* The confirmation screen shown before deactivating an account.
|
||||
*/
|
||||
DeactivateAccount,
|
||||
|
||||
/**
|
||||
* The form for the forgot password use case
|
||||
*/
|
||||
ForgotPassword,
|
||||
|
||||
/**
|
||||
* Legacy: The screen that shows information about a specific group.
|
||||
*/
|
||||
Group,
|
||||
|
||||
/**
|
||||
* The Home tab on iOS | possibly the same on Android? | The Home space
|
||||
* on Web?
|
||||
*/
|
||||
Home,
|
||||
|
||||
/**
|
||||
* The screen that displays the login flow (when the user already has an
|
||||
* account).
|
||||
*/
|
||||
Login,
|
||||
|
||||
/**
|
||||
* The screen that displays the user's breadcrumbs.
|
||||
*/
|
||||
MobileBreadcrumbs,
|
||||
|
||||
/**
|
||||
* The tab on mobile that displays the dialpad.
|
||||
*/
|
||||
MobileDialpad,
|
||||
|
||||
/**
|
||||
* The Favourites tab on mobile that lists your favourite people/rooms.
|
||||
*/
|
||||
MobileFavourites,
|
||||
|
||||
/**
|
||||
* The screen shown to share a link to download the app.
|
||||
*/
|
||||
MobileInviteFriends,
|
||||
|
||||
/**
|
||||
* The People tab on mobile that lists all the DM rooms you have joined.
|
||||
*/
|
||||
MobilePeople,
|
||||
|
||||
/**
|
||||
* The Rooms tab on mobile that lists all the (non-direct) rooms you've
|
||||
* joined.
|
||||
*/
|
||||
MobileRooms,
|
||||
|
||||
/**
|
||||
* The Files tab shown in the global search screen on Mobile.
|
||||
*/
|
||||
MobileSearchFiles,
|
||||
|
||||
/**
|
||||
* The Messages tab shown in the global search screen on Mobile.
|
||||
*/
|
||||
MobileSearchMessages,
|
||||
|
||||
/**
|
||||
* The People tab shown in the global search screen on Mobile.
|
||||
*/
|
||||
MobileSearchPeople,
|
||||
|
||||
/**
|
||||
* The Rooms tab shown in the global search screen on Mobile.
|
||||
*/
|
||||
MobileSearchRooms,
|
||||
|
||||
/**
|
||||
* The sidebar shown on mobile with spaces, settings etc.
|
||||
*/
|
||||
MobileSidebar,
|
||||
|
||||
/**
|
||||
* The screen shown to select which room directory you'd like to use.
|
||||
*/
|
||||
MobileSwitchDirectory,
|
||||
|
||||
/**
|
||||
* Legacy: The screen that shows all groups/communities you have joined.
|
||||
*/
|
||||
MyGroups,
|
||||
|
||||
/**
|
||||
* The screen that displays the registration flow (when the user wants
|
||||
* to create an account)
|
||||
*/
|
||||
Register,
|
||||
|
||||
/**
|
||||
* The screen that displays the messages and events received in a room.
|
||||
*/
|
||||
Room,
|
||||
|
||||
/**
|
||||
* The screen shown when tapping the name of a room from the Room
|
||||
* screen.
|
||||
*/
|
||||
RoomDetails,
|
||||
|
||||
/**
|
||||
* The screen that lists public rooms for you to discover.
|
||||
*/
|
||||
RoomDirectory,
|
||||
|
||||
/**
|
||||
* The screen that lists all the user's rooms and let them filter the
|
||||
* rooms.
|
||||
*/
|
||||
RoomFilter,
|
||||
|
||||
/**
|
||||
* The screen that displays the list of members that are part of a room.
|
||||
*/
|
||||
RoomMembers,
|
||||
|
||||
/**
|
||||
* The notifications settings screen shown from the Room Details screen.
|
||||
*/
|
||||
RoomNotifications,
|
||||
|
||||
/**
|
||||
* The screen that allows you to search for messages/files in a specific
|
||||
* room.
|
||||
*/
|
||||
RoomSearch,
|
||||
|
||||
/**
|
||||
* The settings screen shown from the Room Details screen.
|
||||
*/
|
||||
RoomSettings,
|
||||
|
||||
/**
|
||||
* The screen that allows you to see all of the files sent in a specific
|
||||
* room.
|
||||
*/
|
||||
RoomUploads,
|
||||
|
||||
/**
|
||||
* The global settings screen shown in the app.
|
||||
*/
|
||||
Settings,
|
||||
|
||||
/**
|
||||
* The settings screen to change the default notification options.
|
||||
*/
|
||||
SettingsDefaultNotifications,
|
||||
|
||||
/**
|
||||
* The settings screen to manage notification mentions and keywords.
|
||||
*/
|
||||
SettingsMentionsAndKeywords,
|
||||
|
||||
/**
|
||||
* The global security settings screen.
|
||||
*/
|
||||
SettingsSecurity,
|
||||
|
||||
/**
|
||||
* The screen shown to create a new direct room.
|
||||
*/
|
||||
StartChat,
|
||||
|
||||
/**
|
||||
* A screen that shows information about a room member.
|
||||
*/
|
||||
User,
|
||||
|
||||
/**
|
||||
* ?
|
||||
*/
|
||||
WebCompleteSecurity,
|
||||
|
||||
/**
|
||||
* ?
|
||||
*/
|
||||
WebE2ESetup,
|
||||
WebForgotPassword,
|
||||
|
||||
/**
|
||||
* ?
|
||||
*/
|
||||
WebLoading,
|
||||
WebLogin,
|
||||
WebRegister,
|
||||
|
||||
/**
|
||||
* ?
|
||||
*/
|
||||
WebSoftLogout,
|
||||
WebWelcome,
|
||||
|
||||
/**
|
||||
* The splash screen.
|
||||
*/
|
||||
Welcome,
|
||||
}
|
||||
|
||||
override fun getName() = screenName.name
|
||||
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.analytics.plan
|
||||
|
||||
import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
|
||||
|
||||
// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT
|
||||
// https://github.com/matrix-org/matrix-analytics-events/
|
||||
|
||||
/**
|
||||
* Triggered when the user becomes unauthenticated without actually clicking
|
||||
* sign out(E.g. Due to expiry of an access token without a way to refresh).
|
||||
*/
|
||||
data class UnauthenticatedError(
|
||||
/**
|
||||
* The error code as defined in matrix spec. The source of this error is
|
||||
* from the homeserver.
|
||||
*/
|
||||
val errorCode: ErrorCode,
|
||||
/**
|
||||
* The reason for the error. The source of this error is from the
|
||||
* homeserver, the reason can vary and is subject to change so there is
|
||||
* no enum of possible values.
|
||||
*/
|
||||
val errorReason: String,
|
||||
/**
|
||||
* Whether the auth mechanism is refresh-token-based.
|
||||
*/
|
||||
val refreshTokenAuth: Boolean,
|
||||
/**
|
||||
* Whether a soft logout or hard logout was triggered.
|
||||
*/
|
||||
val softLogout: Boolean,
|
||||
) : VectorAnalyticsEvent {
|
||||
|
||||
enum class ErrorCode {
|
||||
M_FORBIDDEN,
|
||||
M_UNKNOWN,
|
||||
M_UNKNOWN_TOKEN,
|
||||
}
|
||||
|
||||
override fun getName() = "UnauthenticatedError"
|
||||
|
||||
override fun getProperties(): Map<String, Any>? {
|
||||
return mutableMapOf<String, Any>().apply {
|
||||
put("errorCode", errorCode.name)
|
||||
put("errorReason", errorReason)
|
||||
put("refreshTokenAuth", refreshTokenAuth)
|
||||
put("softLogout", softLogout)
|
||||
}.takeIf { it.isNotEmpty() }
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.analytics.screen
|
||||
|
||||
import android.os.SystemClock
|
||||
import im.vector.app.features.analytics.AnalyticsTracker
|
||||
import im.vector.app.features.analytics.plan.Screen
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Track a screen display. Unique usage.
|
||||
*/
|
||||
class ScreenEvent(val screenName: Screen.ScreenName) {
|
||||
private val startTime = SystemClock.elapsedRealtime()
|
||||
|
||||
// Protection to avoid multiple sending
|
||||
private var isSent = false
|
||||
|
||||
/**
|
||||
* @param screenNameOverride can be used to override the screen name passed in constructor parameter
|
||||
*/
|
||||
fun send(analyticsTracker: AnalyticsTracker,
|
||||
screenNameOverride: Screen.ScreenName? = null) {
|
||||
if (isSent) {
|
||||
Timber.w("Event $screenName Already sent!")
|
||||
return
|
||||
}
|
||||
isSent = true
|
||||
analyticsTracker.screen(
|
||||
Screen(
|
||||
screenName = screenNameOverride ?: screenName,
|
||||
durationMs = (SystemClock.elapsedRealtime() - startTime).toInt()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
@ -19,17 +19,15 @@ package im.vector.app.features.attachments.preview
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.core.extensions.addFragment
|
||||
import im.vector.app.core.platform.ToolbarConfigurable
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
import im.vector.app.databinding.ActivitySimpleBinding
|
||||
import im.vector.app.features.themes.ActivityOtherThemes
|
||||
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AttachmentsPreviewActivity : VectorBaseActivity<ActivitySimpleBinding>(), ToolbarConfigurable {
|
||||
class AttachmentsPreviewActivity : VectorBaseActivity<ActivitySimpleBinding>() {
|
||||
|
||||
companion object {
|
||||
private const val EXTRA_FRAGMENT_ARGS = "EXTRA_FRAGMENT_ARGS"
|
||||
@ -72,8 +70,4 @@ class AttachmentsPreviewActivity : VectorBaseActivity<ActivitySimpleBinding>(),
|
||||
setResult(RESULT_OK, resultIntent)
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun configure(toolbar: MaterialToolbar) {
|
||||
configureToolbar(toolbar)
|
||||
}
|
||||
}
|
||||
|
@ -126,7 +126,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
||||
if (savedInstanceState != null) {
|
||||
(supportFragmentManager.findFragmentByTag(FRAGMENT_DIAL_PAD_TAG) as? CallDialPadBottomSheet)?.callback = dialPadCallback
|
||||
}
|
||||
setSupportActionBar(views.callToolbar)
|
||||
setupToolbar(views.callToolbar)
|
||||
configureCallViews()
|
||||
|
||||
callViewModel.onEach {
|
||||
@ -257,18 +257,18 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
||||
views.fullscreenRenderer.isVisible = false
|
||||
views.pipRendererWrapper.isVisible = false
|
||||
views.callInfoGroup.isVisible = true
|
||||
views.callToolbar.setSubtitle(R.string.call_ringing)
|
||||
toolbar?.setSubtitle(R.string.call_ringing)
|
||||
configureCallInfo(state)
|
||||
}
|
||||
is CallState.Answering -> {
|
||||
views.fullscreenRenderer.isVisible = false
|
||||
views.pipRendererWrapper.isVisible = false
|
||||
views.callInfoGroup.isVisible = true
|
||||
views.callToolbar.setSubtitle(R.string.call_connecting)
|
||||
toolbar?.setSubtitle(R.string.call_connecting)
|
||||
configureCallInfo(state)
|
||||
}
|
||||
is CallState.Connected -> {
|
||||
views.callToolbar.subtitle = state.formattedDuration
|
||||
toolbar?.subtitle = state.formattedDuration
|
||||
if (callState.iceConnectionState == MxPeerConnectionState.CONNECTED) {
|
||||
if (state.isLocalOnHold || state.isRemoteOnHold) {
|
||||
views.smallIsHeldIcon.isVisible = true
|
||||
@ -280,11 +280,11 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
||||
views.callActionText.setText(R.string.call_resume_action)
|
||||
views.callActionText.isVisible = true
|
||||
views.callActionText.setOnClickListener { callViewModel.handle(VectorCallViewActions.ToggleHoldResume) }
|
||||
views.callToolbar.setSubtitle(R.string.call_held_by_you)
|
||||
toolbar?.setSubtitle(R.string.call_held_by_you)
|
||||
} else {
|
||||
views.callActionText.isInvisible = true
|
||||
state.callInfo?.opponentUserItem?.let {
|
||||
views.callToolbar.subtitle = getString(R.string.call_held_by_user, it.getBestName())
|
||||
toolbar?.subtitle = getString(R.string.call_held_by_user, it.getBestName())
|
||||
}
|
||||
}
|
||||
} else if (state.transferee !is VectorCallViewState.TransfereeState.NoTransferee) {
|
||||
@ -316,14 +316,14 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
||||
views.pipRendererWrapper.isVisible = false
|
||||
views.callInfoGroup.isVisible = true
|
||||
configureCallInfo(state)
|
||||
views.callToolbar.setSubtitle(R.string.call_connecting)
|
||||
toolbar?.setSubtitle(R.string.call_connecting)
|
||||
}
|
||||
}
|
||||
is CallState.Ended -> {
|
||||
views.fullscreenRenderer.isVisible = false
|
||||
views.pipRendererWrapper.isVisible = false
|
||||
views.callInfoGroup.isVisible = true
|
||||
views.callToolbar.setSubtitle(R.string.call_ended)
|
||||
toolbar?.setSubtitle(R.string.call_ended)
|
||||
configureCallInfo(state)
|
||||
}
|
||||
else -> {
|
||||
@ -410,7 +410,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
||||
avatarRenderer.renderBlur(it, views.bgCallView, sampling = 20, rounded = false, colorFilter = colorFilter, addPlaceholder = false)
|
||||
if (state.transferee is VectorCallViewState.TransfereeState.NoTransferee) {
|
||||
views.participantNameText.setTextOrHide(null)
|
||||
views.callToolbar.title = if (state.isVideoCall) {
|
||||
toolbar?.title = if (state.isVideoCall) {
|
||||
getString(R.string.video_call_with_participant, it.getBestName())
|
||||
} else {
|
||||
getString(R.string.audio_call_with_participant, it.getBestName())
|
||||
|
@ -17,6 +17,7 @@
|
||||
package im.vector.app.features.call.dialpad
|
||||
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.os.Bundle
|
||||
import android.telephony.PhoneNumberFormattingTextWatcher
|
||||
@ -37,6 +38,10 @@ import androidx.fragment.app.Fragment
|
||||
import com.android.dialer.dialpadview.DialpadView
|
||||
import com.android.dialer.dialpadview.DigitsEditText
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.singletonEntryPoint
|
||||
import im.vector.app.features.analytics.AnalyticsTracker
|
||||
import im.vector.app.features.analytics.plan.Screen
|
||||
import im.vector.app.features.analytics.screen.ScreenEvent
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
|
||||
class DialPadFragment : Fragment(), TextWatcher {
|
||||
@ -53,6 +58,25 @@ class DialPadFragment : Fragment(), TextWatcher {
|
||||
private var enableDelete = true
|
||||
private var enableFabOk = true
|
||||
|
||||
private lateinit var analyticsTracker: AnalyticsTracker
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
val singletonEntryPoint = context.singletonEntryPoint()
|
||||
analyticsTracker = singletonEntryPoint.analyticsTracker()
|
||||
}
|
||||
|
||||
private var screenEvent: ScreenEvent? = null
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
screenEvent = ScreenEvent(Screen.ScreenName.MobileDialpad)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
screenEvent?.send(analyticsTracker)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
@ -70,7 +70,8 @@ class CallTransferActivity : VectorBaseActivity<ActivityCallTransferBinding>() {
|
||||
CallTransferPagerAdapter.DIAL_PAD_INDEX -> tab.text = getString(R.string.call_dial_pad_title)
|
||||
}
|
||||
}.attach()
|
||||
configureToolbar(views.callTransferToolbar)
|
||||
setupToolbar(views.callTransferToolbar)
|
||||
.allowBack()
|
||||
views.callTransferToolbar.title = getString(R.string.call_transfer_title)
|
||||
setupConnectAction()
|
||||
}
|
||||
|
@ -270,6 +270,10 @@ class WebRtcCall(
|
||||
}
|
||||
}
|
||||
|
||||
fun durationMillis(): Int {
|
||||
return timer.elapsedTime().toInt()
|
||||
}
|
||||
|
||||
fun formattedDuration(): String {
|
||||
return formatDuration(
|
||||
Duration.ofMillis(timer.elapsedTime())
|
||||
|
@ -22,6 +22,9 @@ import androidx.lifecycle.LifecycleOwner
|
||||
import im.vector.app.ActiveSessionDataSource
|
||||
import im.vector.app.BuildConfig
|
||||
import im.vector.app.core.services.CallService
|
||||
import im.vector.app.features.analytics.AnalyticsTracker
|
||||
import im.vector.app.features.analytics.plan.CallEnded
|
||||
import im.vector.app.features.analytics.plan.CallStarted
|
||||
import im.vector.app.features.call.VectorCallActivity
|
||||
import im.vector.app.features.call.audio.CallAudioManager
|
||||
import im.vector.app.features.call.lookup.CallProtocolsChecker
|
||||
@ -68,7 +71,8 @@ private val loggerTag = LoggerTag("WebRtcCallManager", LoggerTag.VOIP)
|
||||
@Singleton
|
||||
class WebRtcCallManager @Inject constructor(
|
||||
private val context: Context,
|
||||
private val activeSessionDataSource: ActiveSessionDataSource
|
||||
private val activeSessionDataSource: ActiveSessionDataSource,
|
||||
private val analyticsTracker: AnalyticsTracker
|
||||
) : CallListener,
|
||||
DefaultLifecycleObserver {
|
||||
|
||||
@ -237,6 +241,7 @@ class WebRtcCallManager @Inject constructor(
|
||||
val currentCall = getCurrentCall().takeIf { it != call }
|
||||
currentCall?.updateRemoteOnHold(onHold = true)
|
||||
audioManager.setMode(if (call.mxCall.isVideoCall) CallAudioManager.Mode.VIDEO_CALL else CallAudioManager.Mode.AUDIO_CALL)
|
||||
call.trackCallStarted()
|
||||
this.currentCall.setAndNotify(call)
|
||||
}
|
||||
|
||||
@ -245,6 +250,7 @@ class WebRtcCallManager @Inject constructor(
|
||||
val webRtcCall = callsByCallId.remove(callId) ?: return Unit.also {
|
||||
Timber.tag(loggerTag.value).v("On call ended for unknown call $callId")
|
||||
}
|
||||
webRtcCall.trackCallEnded()
|
||||
CallService.onCallTerminated(context, callId, endCallReason, rejected)
|
||||
callsByRoomId[webRtcCall.signalingRoomId]?.remove(webRtcCall)
|
||||
callsByRoomId[webRtcCall.nativeRoomId]?.remove(webRtcCall)
|
||||
@ -443,4 +449,28 @@ class WebRtcCallManager @Inject constructor(
|
||||
}
|
||||
call.onCallAssertedIdentityReceived(callAssertedIdentityContent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Analytics
|
||||
*/
|
||||
private fun WebRtcCall.trackCallStarted() {
|
||||
analyticsTracker.capture(
|
||||
CallStarted(
|
||||
isVideo = mxCall.isVideoCall,
|
||||
numParticipants = 2,
|
||||
placed = mxCall.isOutgoing
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun WebRtcCall.trackCallEnded() {
|
||||
analyticsTracker.capture(
|
||||
CallEnded(
|
||||
durationMs = durationMillis(),
|
||||
isVideo = mxCall.isVideoCall,
|
||||
numParticipants = 2,
|
||||
placed = mxCall.isOutgoing
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -23,8 +23,9 @@ import org.matrix.android.sdk.api.MatrixPatterns
|
||||
import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl
|
||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
object CommandParser {
|
||||
class CommandParser @Inject constructor() {
|
||||
|
||||
/**
|
||||
* Convert the text message into a Slash command.
|
||||
@ -34,11 +35,9 @@ object CommandParser {
|
||||
*/
|
||||
fun parseSlashCommand(textMessage: CharSequence): ParsedCommand {
|
||||
// check if it has the Slash marker
|
||||
if (!textMessage.startsWith("/")) {
|
||||
return ParsedCommand.ErrorNotACommand
|
||||
return if (!textMessage.startsWith("/")) {
|
||||
ParsedCommand.ErrorNotACommand
|
||||
} else {
|
||||
Timber.v("parseSlashCommand")
|
||||
|
||||
// "/" only
|
||||
if (textMessage.length == 1) {
|
||||
return ParsedCommand.ErrorEmptySlashCommand
|
||||
@ -52,7 +51,7 @@ object CommandParser {
|
||||
val messageParts = try {
|
||||
textMessage.split("\\s+".toRegex()).dropLastWhile { it.isEmpty() }
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## manageSlashCommand() : split failed")
|
||||
Timber.e(e, "## parseSlashCommand() : split failed")
|
||||
null
|
||||
}
|
||||
|
||||
@ -64,7 +63,7 @@ object CommandParser {
|
||||
val slashCommand = messageParts.first()
|
||||
val message = textMessage.substring(slashCommand.length).trim()
|
||||
|
||||
return when {
|
||||
when {
|
||||
Command.PLAIN.matches(slashCommand) -> {
|
||||
if (message.isNotEmpty()) {
|
||||
ParsedCommand.SendPlainText(message = message)
|
||||
|
@ -22,51 +22,51 @@ import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
/**
|
||||
* Represent a parsed command
|
||||
*/
|
||||
sealed class ParsedCommand {
|
||||
sealed interface ParsedCommand {
|
||||
// This is not a Slash command
|
||||
object ErrorNotACommand : ParsedCommand()
|
||||
object ErrorNotACommand : ParsedCommand
|
||||
|
||||
object ErrorEmptySlashCommand : ParsedCommand()
|
||||
object ErrorEmptySlashCommand : ParsedCommand
|
||||
|
||||
// Unknown/Unsupported slash command
|
||||
class ErrorUnknownSlashCommand(val slashCommand: String) : ParsedCommand()
|
||||
data class ErrorUnknownSlashCommand(val slashCommand: String) : ParsedCommand
|
||||
|
||||
// A slash command is detected, but there is an error
|
||||
class ErrorSyntax(val command: Command) : ParsedCommand()
|
||||
data class ErrorSyntax(val command: Command) : ParsedCommand
|
||||
|
||||
// Valid commands:
|
||||
|
||||
class SendPlainText(val message: CharSequence) : ParsedCommand()
|
||||
class SendEmote(val message: CharSequence) : ParsedCommand()
|
||||
class SendRainbow(val message: CharSequence) : ParsedCommand()
|
||||
class SendRainbowEmote(val message: CharSequence) : ParsedCommand()
|
||||
class BanUser(val userId: String, val reason: String?) : ParsedCommand()
|
||||
class UnbanUser(val userId: String, val reason: String?) : ParsedCommand()
|
||||
class IgnoreUser(val userId: String) : ParsedCommand()
|
||||
class UnignoreUser(val userId: String) : ParsedCommand()
|
||||
class SetUserPowerLevel(val userId: String, val powerLevel: Int?) : ParsedCommand()
|
||||
class ChangeRoomName(val name: String) : ParsedCommand()
|
||||
class Invite(val userId: String, val reason: String?) : ParsedCommand()
|
||||
class Invite3Pid(val threePid: ThreePid) : ParsedCommand()
|
||||
class JoinRoom(val roomAlias: String, val reason: String?) : ParsedCommand()
|
||||
class PartRoom(val roomAlias: String?) : ParsedCommand()
|
||||
class ChangeTopic(val topic: String) : ParsedCommand()
|
||||
class RemoveUser(val userId: String, val reason: String?) : ParsedCommand()
|
||||
class ChangeDisplayName(val displayName: String) : ParsedCommand()
|
||||
class ChangeDisplayNameForRoom(val displayName: String) : ParsedCommand()
|
||||
class ChangeRoomAvatar(val url: String) : ParsedCommand()
|
||||
class ChangeAvatarForRoom(val url: String) : ParsedCommand()
|
||||
class SetMarkdown(val enable: Boolean) : ParsedCommand()
|
||||
object ClearScalarToken : ParsedCommand()
|
||||
class SendSpoiler(val message: String) : ParsedCommand()
|
||||
class SendShrug(val message: CharSequence) : ParsedCommand()
|
||||
class SendLenny(val message: CharSequence) : ParsedCommand()
|
||||
object DiscardSession : ParsedCommand()
|
||||
class ShowUser(val userId: String) : ParsedCommand()
|
||||
class SendChatEffect(val chatEffect: ChatEffect, val message: String) : ParsedCommand()
|
||||
class CreateSpace(val name: String, val invitees: List<String>) : ParsedCommand()
|
||||
class AddToSpace(val spaceId: String) : ParsedCommand()
|
||||
class JoinSpace(val spaceIdOrAlias: String) : ParsedCommand()
|
||||
class LeaveRoom(val roomId: String) : ParsedCommand()
|
||||
class UpgradeRoom(val newVersion: String) : ParsedCommand()
|
||||
data class SendPlainText(val message: CharSequence) : ParsedCommand
|
||||
data class SendEmote(val message: CharSequence) : ParsedCommand
|
||||
data class SendRainbow(val message: CharSequence) : ParsedCommand
|
||||
data class SendRainbowEmote(val message: CharSequence) : ParsedCommand
|
||||
data class BanUser(val userId: String, val reason: String?) : ParsedCommand
|
||||
data class UnbanUser(val userId: String, val reason: String?) : ParsedCommand
|
||||
data class IgnoreUser(val userId: String) : ParsedCommand
|
||||
data class UnignoreUser(val userId: String) : ParsedCommand
|
||||
data class SetUserPowerLevel(val userId: String, val powerLevel: Int?) : ParsedCommand
|
||||
data class ChangeRoomName(val name: String) : ParsedCommand
|
||||
data class Invite(val userId: String, val reason: String?) : ParsedCommand
|
||||
data class Invite3Pid(val threePid: ThreePid) : ParsedCommand
|
||||
data class JoinRoom(val roomAlias: String, val reason: String?) : ParsedCommand
|
||||
data class PartRoom(val roomAlias: String?) : ParsedCommand
|
||||
data class ChangeTopic(val topic: String) : ParsedCommand
|
||||
data class RemoveUser(val userId: String, val reason: String?) : ParsedCommand
|
||||
data class ChangeDisplayName(val displayName: String) : ParsedCommand
|
||||
data class ChangeDisplayNameForRoom(val displayName: String) : ParsedCommand
|
||||
data class ChangeRoomAvatar(val url: String) : ParsedCommand
|
||||
data class ChangeAvatarForRoom(val url: String) : ParsedCommand
|
||||
data class SetMarkdown(val enable: Boolean) : ParsedCommand
|
||||
object ClearScalarToken : ParsedCommand
|
||||
data class SendSpoiler(val message: String) : ParsedCommand
|
||||
data class SendShrug(val message: CharSequence) : ParsedCommand
|
||||
data class SendLenny(val message: CharSequence) : ParsedCommand
|
||||
object DiscardSession : ParsedCommand
|
||||
data class ShowUser(val userId: String) : ParsedCommand
|
||||
data class SendChatEffect(val chatEffect: ChatEffect, val message: String) : ParsedCommand
|
||||
data class CreateSpace(val name: String, val invitees: List<String>) : ParsedCommand
|
||||
data class AddToSpace(val spaceId: String) : ParsedCommand
|
||||
data class JoinSpace(val spaceIdOrAlias: String) : ParsedCommand
|
||||
data class LeaveRoom(val roomId: String) : ParsedCommand
|
||||
data class UpgradeRoom(val newVersion: String) : ParsedCommand
|
||||
}
|
||||
|
@ -67,7 +67,8 @@ class ContactsBookFragment @Inject constructor(
|
||||
setupFilterView()
|
||||
setupConsentView()
|
||||
setupOnlyBoundContactsView()
|
||||
setupCloseView()
|
||||
setupToolbar(views.phoneBookToolbar)
|
||||
.allowBack(useCross = true)
|
||||
contactsBookViewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is ContactsBookViewEvents.Failure -> showFailure(it.throwable)
|
||||
@ -119,12 +120,6 @@ class ContactsBookFragment @Inject constructor(
|
||||
views.phoneBookRecyclerView.configureWith(contactsBookController)
|
||||
}
|
||||
|
||||
private fun setupCloseView() {
|
||||
views.phoneBookClose.debouncedClicks {
|
||||
sharedActionViewModel.post(UserListSharedAction.GoBack)
|
||||
}
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(contactsBookViewModel) { state ->
|
||||
views.phoneBookSearchForMatrixContacts.isVisible = state.filteredMappedContacts.isNotEmpty() && state.identityServerUrl != null && !state.userConsent
|
||||
views.phoneBookOnlyBoundContacts.isVisible = state.isBoundRetrieved
|
||||
|
@ -42,6 +42,7 @@ import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
|
||||
import im.vector.app.core.utils.checkPermissions
|
||||
import im.vector.app.core.utils.onPermissionDeniedSnackbar
|
||||
import im.vector.app.core.utils.registerForPermissionsResult
|
||||
import im.vector.app.features.analytics.plan.Screen
|
||||
import im.vector.app.features.contactsbook.ContactsBookFragment
|
||||
import im.vector.app.features.userdirectory.UserListFragment
|
||||
import im.vector.app.features.userdirectory.UserListFragmentArgs
|
||||
@ -63,6 +64,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
analyticsScreenName = Screen.ScreenName.StartChat
|
||||
views.toolbar.visibility = View.GONE
|
||||
|
||||
sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java)
|
||||
|
@ -64,11 +64,8 @@ class CreateDirectRoomByQrCodeFragment @Inject constructor() : VectorBaseFragmen
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
setupToolbar(views.qrScannerToolbar)
|
||||
|
||||
views.qrScannerClose.debouncedClicks {
|
||||
requireActivity().onBackPressed()
|
||||
}
|
||||
views.qrScannerTitle.text = getString(R.string.add_by_qr_code)
|
||||
.setTitle(R.string.add_by_qr_code)
|
||||
.allowBack(useCross = true)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
@ -22,13 +22,13 @@ import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence
|
||||
import im.vector.app.core.epoxy.errorWithRetryItem
|
||||
import im.vector.app.core.epoxy.loadingItem
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.ui.list.ItemStyle
|
||||
import im.vector.app.core.ui.list.genericItem
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust
|
||||
|
@ -20,14 +20,14 @@ import androidx.core.text.toSpannable
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.bottomSheetDividerItem
|
||||
import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence
|
||||
import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.utils.colorizeMatchingText
|
||||
import im.vector.app.features.crypto.verification.VerificationBottomSheetViewState
|
||||
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationActionItem
|
||||
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
|
||||
import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence
|
||||
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||
import javax.inject.Inject
|
||||
|
||||
class VerificationCancelController @Inject constructor(
|
||||
|
@ -19,13 +19,13 @@ package im.vector.app.features.crypto.verification.cancel
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.bottomSheetDividerItem
|
||||
import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.crypto.verification.VerificationBottomSheetViewState
|
||||
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationActionItem
|
||||
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
|
||||
import im.vector.app.features.html.EventHtmlRenderer
|
||||
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||
import javax.inject.Inject
|
||||
|
||||
class VerificationNotMeController @Inject constructor(
|
||||
|
@ -19,12 +19,12 @@ package im.vector.app.features.crypto.verification.choose
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.bottomSheetDividerItem
|
||||
import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationActionItem
|
||||
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
|
||||
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationQrCodeItem
|
||||
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||
import javax.inject.Inject
|
||||
|
||||
class VerificationChooseMethodController @Inject constructor(
|
||||
|
@ -19,13 +19,13 @@ package im.vector.app.features.crypto.verification.conclusion
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.bottomSheetDividerItem
|
||||
import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationActionItem
|
||||
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationBigImageItem
|
||||
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
|
||||
import im.vector.app.features.html.EventHtmlRenderer
|
||||
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -21,7 +21,6 @@ import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Success
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.bottomSheetDividerItem
|
||||
import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence
|
||||
import im.vector.app.core.epoxy.errorWithRetryItem
|
||||
import im.vector.app.core.error.ErrorFormatter
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
@ -32,6 +31,7 @@ import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationE
|
||||
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
|
||||
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationWaitingItem
|
||||
import im.vector.app.features.displayname.getBestName
|
||||
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||
import javax.inject.Inject
|
||||
|
||||
class VerificationEmojiCodeController @Inject constructor(
|
||||
|
@ -22,7 +22,7 @@ import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence
|
||||
import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence
|
||||
|
||||
/**
|
||||
* A action for bottom sheet.
|
||||
|
@ -18,12 +18,12 @@ package im.vector.app.features.crypto.verification.qrconfirmation
|
||||
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationBigImageItem
|
||||
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
|
||||
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationWaitingItem
|
||||
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -19,7 +19,6 @@ package im.vector.app.features.crypto.verification.qrconfirmation
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.bottomSheetDividerItem
|
||||
import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.crypto.verification.VerificationBottomSheetViewState
|
||||
@ -27,6 +26,7 @@ import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationA
|
||||
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationBigImageItem
|
||||
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
|
||||
import im.vector.app.features.displayname.getBestName
|
||||
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -23,7 +23,6 @@ import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.bottomSheetDividerItem
|
||||
import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.utils.colorizeMatchingText
|
||||
@ -33,6 +32,7 @@ import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationA
|
||||
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
|
||||
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationWaitingItem
|
||||
import im.vector.app.features.displayname.getBestName
|
||||
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||
import javax.inject.Inject
|
||||
|
||||
class VerificationRequestController @Inject constructor(
|
||||
|
@ -18,11 +18,11 @@ package im.vector.app.features.devtools
|
||||
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.ui.list.genericFooterItem
|
||||
import im.vector.app.features.form.formEditTextItem
|
||||
import im.vector.app.features.form.formMultiLineEditTextItem
|
||||
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||
import javax.inject.Inject
|
||||
|
||||
class RoomDevToolSendFormController @Inject constructor(
|
||||
|
@ -18,11 +18,11 @@ package im.vector.app.features.devtools
|
||||
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence
|
||||
import im.vector.app.core.epoxy.noResultItem
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.ui.list.genericItem
|
||||
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||
import me.gujun.android.span.span
|
||||
import org.json.JSONObject
|
||||
import javax.inject.Inject
|
||||
@ -37,7 +37,7 @@ class RoomStateListController @Inject constructor(
|
||||
override fun buildModels(data: RoomDevToolViewState?) {
|
||||
val host = this
|
||||
when (data?.displayMode) {
|
||||
RoomDevToolViewState.Mode.StateEventList -> {
|
||||
RoomDevToolViewState.Mode.StateEventList -> {
|
||||
val stateEventsGroups = data.stateEvents.invoke().orEmpty().groupBy { it.getClearType() }
|
||||
|
||||
if (stateEventsGroups.isEmpty()) {
|
||||
|
@ -24,6 +24,7 @@ import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
@ -32,7 +33,6 @@ import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.Mavericks
|
||||
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.AppStateHandler
|
||||
@ -42,13 +42,14 @@ import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
import im.vector.app.core.extensions.registerStartForActivityResult
|
||||
import im.vector.app.core.extensions.replaceFragment
|
||||
import im.vector.app.core.platform.ToolbarConfigurable
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
import im.vector.app.core.pushers.PushersManager
|
||||
import im.vector.app.databinding.ActivityHomeBinding
|
||||
import im.vector.app.features.MainActivity
|
||||
import im.vector.app.features.MainActivityArgs
|
||||
import im.vector.app.features.analytics.accountdata.AnalyticsAccountDataViewModel
|
||||
import im.vector.app.features.analytics.plan.Screen
|
||||
import im.vector.app.features.analytics.screen.ScreenEvent
|
||||
import im.vector.app.features.disclaimer.showDisclaimerDialog
|
||||
import im.vector.app.features.matrixto.MatrixToBottomSheet
|
||||
import im.vector.app.features.navigation.Navigator
|
||||
@ -96,7 +97,6 @@ data class HomeActivityArgs(
|
||||
@AndroidEntryPoint
|
||||
class HomeActivity :
|
||||
VectorBaseActivity<ActivityHomeBinding>(),
|
||||
ToolbarConfigurable,
|
||||
NavigationInterceptor,
|
||||
SpaceInviteBottomSheet.InteractionListener,
|
||||
MatrixToBottomSheet.InteractionListener {
|
||||
@ -104,6 +104,7 @@ class HomeActivity :
|
||||
private lateinit var sharedActionViewModel: HomeSharedActionViewModel
|
||||
|
||||
private val homeActivityViewModel: HomeActivityViewModel by viewModel()
|
||||
|
||||
@Suppress("UNUSED")
|
||||
private val analyticsAccountDataViewModel: AnalyticsAccountDataViewModel by viewModel()
|
||||
@Suppress("UNUSED")
|
||||
@ -164,6 +165,16 @@ class HomeActivity :
|
||||
}
|
||||
|
||||
private val drawerListener = object : DrawerLayout.SimpleDrawerListener() {
|
||||
private var drawerScreenEvent: ScreenEvent? = null
|
||||
override fun onDrawerOpened(drawerView: View) {
|
||||
drawerScreenEvent = ScreenEvent(Screen.ScreenName.MobileSidebar)
|
||||
}
|
||||
|
||||
override fun onDrawerClosed(drawerView: View) {
|
||||
drawerScreenEvent?.send(analyticsTracker)
|
||||
drawerScreenEvent = null
|
||||
}
|
||||
|
||||
override fun onDrawerStateChanged(newState: Int) {
|
||||
hideKeyboard()
|
||||
}
|
||||
@ -175,6 +186,7 @@ class HomeActivity :
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
analyticsScreenName = Screen.ScreenName.Home
|
||||
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, false)
|
||||
FcmHelper.ensureFcmTokenIsRetrieved(this, pushManager, vectorPreferences.areNotificationEnabledForDevice())
|
||||
sharedActionViewModel = viewModelProvider.get(HomeSharedActionViewModel::class.java)
|
||||
@ -478,10 +490,6 @@ class HomeActivity :
|
||||
serverBackupStatusViewModel.refreshRemoteStateIfNeeded()
|
||||
}
|
||||
|
||||
override fun configure(toolbar: MaterialToolbar) {
|
||||
configureToolbar(toolbar, false)
|
||||
}
|
||||
|
||||
override fun getMenuRes() = R.menu.home
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
|
||||
|
@ -33,7 +33,6 @@ import im.vector.app.R
|
||||
import im.vector.app.RoomGroupingMethod
|
||||
import im.vector.app.core.extensions.commitTransaction
|
||||
import im.vector.app.core.extensions.toMvRxBundle
|
||||
import im.vector.app.core.platform.ToolbarConfigurable
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
@ -314,11 +313,9 @@ class HomeDetailFragment @Inject constructor(
|
||||
}
|
||||
|
||||
private fun setupToolbar() {
|
||||
val parentActivity = vectorBaseActivity
|
||||
if (parentActivity is ToolbarConfigurable) {
|
||||
parentActivity.configure(views.groupToolbar)
|
||||
}
|
||||
views.groupToolbar.title = ""
|
||||
setupToolbar(views.groupToolbar)
|
||||
.setTitle(null)
|
||||
|
||||
views.groupToolbarAvatarImageView.debouncedClicks {
|
||||
sharedActionViewModel.post(HomeActivitySharedAction.OpenDrawer)
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import im.vector.app.core.extensions.replaceChildFragment
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.core.utils.startSharePlainTextIntent
|
||||
import im.vector.app.databinding.FragmentHomeDrawerBinding
|
||||
import im.vector.app.features.analytics.plan.Screen
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import im.vector.app.features.settings.VectorSettingsActivity
|
||||
import im.vector.app.features.spaces.SpaceListFragment
|
||||
@ -97,6 +98,7 @@ class HomeDrawerFragment @Inject constructor(
|
||||
|
||||
views.homeDrawerInviteFriendButton.debouncedClicks {
|
||||
session.permalinkService().createPermalink(sharedActionViewModel.session.myUserId)?.let { permalink ->
|
||||
analyticsTracker.screen(Screen(screenName = Screen.ScreenName.MobileInviteFriends))
|
||||
val text = getString(R.string.invite_friends_text, permalink)
|
||||
|
||||
startSharePlainTextIntent(
|
||||
|
@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
@ -27,15 +28,15 @@ import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.Mavericks
|
||||
import com.airbnb.mvrx.viewModel
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.core.extensions.endKeepScreenOn
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
import im.vector.app.core.extensions.keepScreenOn
|
||||
import im.vector.app.core.extensions.replaceFragment
|
||||
import im.vector.app.core.platform.ToolbarConfigurable
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
import im.vector.app.databinding.ActivityRoomDetailBinding
|
||||
import im.vector.app.features.analytics.plan.Screen
|
||||
import im.vector.app.features.analytics.screen.ScreenEvent
|
||||
import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker
|
||||
import im.vector.app.features.matrixto.MatrixToBottomSheet
|
||||
@ -50,7 +51,6 @@ import javax.inject.Inject
|
||||
@AndroidEntryPoint
|
||||
class RoomDetailActivity :
|
||||
VectorBaseActivity<ActivityRoomDetailBinding>(),
|
||||
ToolbarConfigurable,
|
||||
MatrixToBottomSheet.InteractionListener {
|
||||
|
||||
override fun getBinding(): ActivityRoomDetailBinding {
|
||||
@ -156,11 +156,17 @@ class RoomDetailActivity :
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun configure(toolbar: MaterialToolbar) {
|
||||
configureToolbar(toolbar)
|
||||
}
|
||||
|
||||
private val drawerListener = object : DrawerLayout.SimpleDrawerListener() {
|
||||
private var drawerScreenEvent: ScreenEvent? = null
|
||||
override fun onDrawerOpened(drawerView: View) {
|
||||
drawerScreenEvent = ScreenEvent(Screen.ScreenName.MobileBreadcrumbs)
|
||||
}
|
||||
|
||||
override fun onDrawerClosed(drawerView: View) {
|
||||
drawerScreenEvent?.send(analyticsTracker)
|
||||
drawerScreenEvent = null
|
||||
}
|
||||
|
||||
override fun onDrawerStateChanged(newState: Int) {
|
||||
hideKeyboard()
|
||||
|
||||
|
@ -116,6 +116,8 @@ import im.vector.app.core.utils.startInstallFromSourceIntent
|
||||
import im.vector.app.core.utils.toast
|
||||
import im.vector.app.databinding.DialogReportContentBinding
|
||||
import im.vector.app.databinding.FragmentRoomDetailBinding
|
||||
import im.vector.app.features.analytics.plan.Click
|
||||
import im.vector.app.features.analytics.plan.Screen
|
||||
import im.vector.app.features.attachments.AttachmentTypeSelectorView
|
||||
import im.vector.app.features.attachments.AttachmentsHelper
|
||||
import im.vector.app.features.attachments.ContactAttachment
|
||||
@ -239,7 +241,8 @@ data class RoomDetailArgs(
|
||||
val roomId: String,
|
||||
val eventId: String? = null,
|
||||
val sharedData: SharedData? = null,
|
||||
val openShareSpaceForId: String? = null
|
||||
val openShareSpaceForId: String? = null,
|
||||
val switchToParentSpace: Boolean = false
|
||||
) : Parcelable
|
||||
|
||||
class RoomDetailFragment @Inject constructor(
|
||||
@ -337,6 +340,7 @@ class RoomDetailFragment @Inject constructor(
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
analyticsScreenName = Screen.ScreenName.Room
|
||||
setFragmentResultListener(MigrateRoomBottomSheet.REQUEST_KEY) { _, bundle ->
|
||||
bundle.getString(MigrateRoomBottomSheet.BUNDLE_KEY_REPLACEMENT_ROOM)?.let { replacementRoomId ->
|
||||
roomDetailViewModel.handle(RoomDetailAction.RoomUpgradeSuccess(replacementRoomId))
|
||||
@ -363,6 +367,7 @@ class RoomDetailFragment @Inject constructor(
|
||||
keyboardStateUtils = KeyboardStateUtils(requireActivity())
|
||||
lazyLoadedViews.bind(views)
|
||||
setupToolbar(views.roomToolbar)
|
||||
.allowBack()
|
||||
setupRecyclerView()
|
||||
setupComposer()
|
||||
setupNotificationView()
|
||||
@ -677,7 +682,7 @@ class RoomDetailFragment @Inject constructor(
|
||||
*/
|
||||
private fun EmojiPopup.Builder.setOnEmojiPopupDismissListenerLifecycleAware(action: () -> Unit): EmojiPopup.Builder {
|
||||
return setOnEmojiPopupDismissListener {
|
||||
if (lifecycle.currentState == Lifecycle.State.STARTED) {
|
||||
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
|
||||
action()
|
||||
}
|
||||
}
|
||||
@ -1397,6 +1402,7 @@ class RoomDetailFragment @Inject constructor(
|
||||
return
|
||||
}
|
||||
if (text.isNotBlank()) {
|
||||
analyticsTracker.capture(Click(name = Click.Name.SendMessageButton))
|
||||
// We collapse ASAP, if not there will be a slight annoying delay
|
||||
views.composerLayout.collapse(true)
|
||||
lockSendButton = true
|
||||
@ -1511,7 +1517,7 @@ class RoomDetailFragment @Inject constructor(
|
||||
views.roomToolbarSubtitleView.apply {
|
||||
setTextOrHide(subtitle)
|
||||
if (typingMessage.isNullOrBlank()) {
|
||||
setTextColor(colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
|
||||
setTextColor(colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary))
|
||||
setTypeface(null, Typeface.NORMAL)
|
||||
} else {
|
||||
setTextColor(colorProvider.getColorFromAttribute(R.attr.colorPrimary))
|
||||
|
@ -28,6 +28,7 @@ import com.airbnb.mvrx.Uninitialized
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.AppStateHandler
|
||||
import im.vector.app.BuildConfig
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||
@ -37,7 +38,9 @@ import im.vector.app.core.mvrx.runCatchingToAsync
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.utils.BehaviorDataSource
|
||||
import im.vector.app.features.analytics.AnalyticsTracker
|
||||
import im.vector.app.features.analytics.DecryptionFailureTracker
|
||||
import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom
|
||||
import im.vector.app.features.call.conference.ConferenceEvent
|
||||
import im.vector.app.features.call.conference.JitsiActiveConferenceHolder
|
||||
import im.vector.app.features.call.conference.JitsiService
|
||||
@ -54,6 +57,7 @@ import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
|
||||
import im.vector.app.features.session.coroutineScope
|
||||
import im.vector.app.features.settings.VectorDataStore
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import im.vector.app.space
|
||||
import im.vector.lib.core.utils.flow.chunk
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
@ -112,9 +116,11 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
private val chatEffectManager: ChatEffectManager,
|
||||
private val directRoomHelper: DirectRoomHelper,
|
||||
private val jitsiService: JitsiService,
|
||||
private val analyticsTracker: AnalyticsTracker,
|
||||
private val activeConferenceHolder: JitsiActiveConferenceHolder,
|
||||
private val decryptionFailureTracker: DecryptionFailureTracker,
|
||||
timelineFactory: TimelineFactory
|
||||
timelineFactory: TimelineFactory,
|
||||
appStateHandler: AppStateHandler
|
||||
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState),
|
||||
Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener {
|
||||
|
||||
@ -179,6 +185,24 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
if (OutboundSessionKeySharingStrategy.WhenEnteringRoom == BuildConfig.outboundSessionKeySharingStrategy && room.isEncrypted()) {
|
||||
prepareForEncryption()
|
||||
}
|
||||
|
||||
if (initialState.switchToParentSpace) {
|
||||
// We are coming from a notification, try to switch to the most relevant space
|
||||
// so that when hitting back the room will appear in the list
|
||||
appStateHandler.getCurrentRoomGroupingMethod()?.space().let { currentSpace ->
|
||||
val currentRoomSummary = room.roomSummary() ?: return@let
|
||||
// nothing we are good
|
||||
if (currentSpace == null || !currentRoomSummary.flattenParentIds.contains(currentSpace.roomId)) {
|
||||
// take first one or switch to home
|
||||
appStateHandler.setCurrentSpace(
|
||||
currentRoomSummary
|
||||
.flattenParentIds.firstOrNull { it.isNotBlank() },
|
||||
// force persist, because if not on resume the AppStateHandler will resume
|
||||
// the current space from what was persisted on enter background
|
||||
persistNow = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeDataStore() {
|
||||
@ -709,7 +733,10 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
|
||||
private fun handleAcceptInvite() {
|
||||
viewModelScope.launch {
|
||||
tryOrNull { room.join() }
|
||||
tryOrNull {
|
||||
room.join()
|
||||
analyticsTracker.capture(room.roomSummary().toAnalyticsJoinedRoom())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,14 +66,16 @@ data class RoomDetailViewState(
|
||||
val isAllowedToStartWebRTCCall: Boolean = true,
|
||||
val isAllowedToSetupEncryption: Boolean = true,
|
||||
val hasFailedSending: Boolean = false,
|
||||
val jitsiState: JitsiState = JitsiState()
|
||||
val jitsiState: JitsiState = JitsiState(),
|
||||
val switchToParentSpace: Boolean = false
|
||||
) : MavericksState {
|
||||
|
||||
constructor(args: RoomDetailArgs) : this(
|
||||
roomId = args.roomId,
|
||||
eventId = args.eventId,
|
||||
// Also highlight the target event, if any
|
||||
highlightedEventId = args.eventId
|
||||
highlightedEventId = args.eventId,
|
||||
switchToParentSpace = args.switchToParentSpace
|
||||
)
|
||||
|
||||
fun isWebRTCCallOptionAvailable() = (asyncRoomSummary.invoke()?.joinedMembersCount ?: 0) <= 2
|
||||
|
@ -26,6 +26,8 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.analytics.AnalyticsTracker
|
||||
import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom
|
||||
import im.vector.app.features.attachments.toContentAttachmentData
|
||||
import im.vector.app.features.command.CommandParser
|
||||
import im.vector.app.features.command.ParsedCommand
|
||||
@ -66,8 +68,10 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||
private val session: Session,
|
||||
private val stringProvider: StringProvider,
|
||||
private val vectorPreferences: VectorPreferences,
|
||||
private val commandParser: CommandParser,
|
||||
private val rainbowGenerator: RainbowGenerator,
|
||||
private val voiceMessageHelper: VoiceMessageHelper,
|
||||
private val analyticsTracker: AnalyticsTracker,
|
||||
private val voicePlayerHelper: VoicePlayerHelper
|
||||
) : VectorViewModel<MessageComposerViewState, MessageComposerAction, MessageComposerViewEvents>(initialState) {
|
||||
|
||||
@ -183,7 +187,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||
withState { state ->
|
||||
when (state.sendMode) {
|
||||
is SendMode.Regular -> {
|
||||
when (val slashCommandResult = CommandParser.parseSlashCommand(action.text)) {
|
||||
when (val slashCommandResult = commandParser.parseSlashCommand(action.text)) {
|
||||
is ParsedCommand.ErrorNotACommand -> {
|
||||
// Send the text message to the room
|
||||
room.sendTextMessage(action.text, autoMarkdown = action.autoMarkdown)
|
||||
@ -520,6 +524,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
||||
return@launch
|
||||
}
|
||||
session.getRoomSummary(command.roomAlias)
|
||||
?.also { analyticsTracker.capture(it.toAnalyticsJoinedRoom()) }
|
||||
?.roomId
|
||||
?.let {
|
||||
_viewEvents.post(MessageComposerViewEvents.JoinRoomCommandSuccess(it))
|
||||
|
@ -40,7 +40,8 @@ class SearchActivity : VectorBaseActivity<ActivitySearchBinding>() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
configureToolbar(views.searchToolbar)
|
||||
setupToolbar(views.searchToolbar)
|
||||
.allowBack()
|
||||
}
|
||||
|
||||
override fun initUiAndData() {
|
||||
|
@ -26,12 +26,12 @@ import com.airbnb.epoxy.VisibilityState
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.date.DateFormatKind
|
||||
import im.vector.app.core.date.VectorDateFormatter
|
||||
import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence
|
||||
import im.vector.app.core.epoxy.loadingItem
|
||||
import im.vector.app.core.epoxy.noResultItem
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.ui.list.GenericHeaderItem_
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.events.model.Content
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
|
@ -24,11 +24,11 @@ import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.ClickListener
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence
|
||||
import im.vector.app.core.epoxy.onClick
|
||||
import im.vector.app.core.extensions.setTextOrHide
|
||||
import im.vector.app.features.displayname.getBestName
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_search_result)
|
||||
|
@ -27,7 +27,6 @@ import im.vector.app.core.epoxy.bottomsheet.bottomSheetActionItem
|
||||
import im.vector.app.core.epoxy.bottomsheet.bottomSheetMessagePreviewItem
|
||||
import im.vector.app.core.epoxy.bottomsheet.bottomSheetQuickReactionsItem
|
||||
import im.vector.app.core.epoxy.bottomsheet.bottomSheetSendStateItem
|
||||
import im.vector.app.core.epoxy.charsequence.toEpoxyCharSequence
|
||||
import im.vector.app.core.error.ErrorFormatter
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.utils.DimensionConverter
|
||||
@ -40,6 +39,7 @@ import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovement
|
||||
import im.vector.app.features.home.room.detail.timeline.tools.linkify
|
||||
import im.vector.app.features.html.SpanUtils
|
||||
import im.vector.app.features.media.ImageContentRenderer
|
||||
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user