Merge remote-tracking branch 'origin/develop' into feature/eric/space-list-modal

# Conflicts:
#	vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt
This commit is contained in:
ericdecanini 2022-08-05 17:10:36 +02:00
commit 41d859dc5b
115 changed files with 1090 additions and 386 deletions

View file

@ -7,10 +7,8 @@ on:
# Enrich gradle.properties for CI/CD
env:
CI_GRADLE_ARG_PROPERTIES: >
-Porg.gradle.jvmargs=-Xmx4g
-Porg.gradle.parallel=false
--no-daemon
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options="-Xmx2560m" -Dkotlin.incremental=false
CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 2 --no-daemon
jobs:
debug:
@ -36,7 +34,7 @@ jobs:
restore-keys: |
${{ runner.os }}-gradle-
- name: Assemble ${{ matrix.target }} debug apk
run: ./gradlew assemble${{ matrix.target }}Debug $CI_GRADLE_ARG_PROPERTIES --stacktrace
run: ./gradlew assemble${{ matrix.target }}Debug $CI_GRADLE_ARG_PROPERTIES
- name: Upload ${{ matrix.target }} debug APKs
uses: actions/upload-artifact@v3
with:
@ -61,7 +59,7 @@ jobs:
restore-keys: |
${{ runner.os }}-gradle-
- name: Assemble GPlay unsigned apk
run: ./gradlew clean assembleGplayRelease $CI_GRADLE_ARG_PROPERTIES --stacktrace
run: ./gradlew clean assembleGplayRelease $CI_GRADLE_ARG_PROPERTIES
- name: Upload Gplay unsigned APKs
uses: actions/upload-artifact@v3
with:

View file

@ -6,10 +6,8 @@ on:
- cron: "0 4 * * *"
env:
CI_GRADLE_ARG_PROPERTIES: >
-Porg.gradle.jvmargs=-Xmx4g
-Porg.gradle.parallel=false
--no-daemon
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options="-Xmx2560m" -Dkotlin.incremental=false
CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 2 --no-daemon
jobs:
nightly:
@ -40,7 +38,7 @@ jobs:
yes n | towncrier build --version nightly
- name: Build and upload Gplay Nightly APK
run: |
./gradlew assembleGplayNightly appDistributionUploadGplayNightly $CI_GRADLE_ARG_PROPERTIES --stacktrace
./gradlew assembleGplayNightly appDistributionUploadGplayNightly $CI_GRADLE_ARG_PROPERTIES
env:
ELEMENT_ANDROID_NIGHTLY_KEYID: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYID }}
ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD }}

View file

@ -10,10 +10,8 @@ on:
# Enrich gradle.properties for CI/CD
env:
CI_GRADLE_ARG_PROPERTIES: >
-Porg.gradle.jvmargs=-Xmx4g
-Porg.gradle.parallel=false
--no-daemon
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options="-Xmx2560m" -Dkotlin.incremental=false
CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 2 --no-daemon
jobs:

View file

@ -7,10 +7,8 @@ on:
# Enrich gradle.properties for CI/CD
env:
CI_GRADLE_ARG_PROPERTIES: >
-Porg.gradle.jvmargs=-Xmx4g
-Porg.gradle.parallel=false
--no-daemon
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options="-Xmx2560m" -Dkotlin.incremental=false
CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 2 --no-daemon
jobs:
check:
@ -51,8 +49,8 @@ jobs:
- name: Run lint
# Not always, if ktlint or detekt fail, avoid running the long lint check.
run: |
./gradlew lintGplayRelease --stacktrace $CI_GRADLE_ARG_PROPERTIES
./gradlew lintFdroidRelease --stacktrace $CI_GRADLE_ARG_PROPERTIES
./gradlew lintGplayRelease $CI_GRADLE_ARG_PROPERTIES
./gradlew lintFdroidRelease $CI_GRADLE_ARG_PROPERTIES
- name: Upload reports
if: always()
uses: actions/upload-artifact@v3

View file

@ -7,10 +7,8 @@ on:
# Enrich gradle.properties for CI/CD
env:
CI_GRADLE_ARG_PROPERTIES: >
-Porg.gradle.jvmargs=-Xmx4g
-Porg.gradle.parallel=false
--no-daemon
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options="-Xmx2560m" -Dkotlin.incremental=false
CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 2 --no-daemon
jobs:
tests:
@ -51,9 +49,9 @@ jobs:
disable-animations: true
emulator-build: 7425822
script: |
./gradlew unitTestsWithCoverage --stacktrace $CI_GRADLE_ARG_PROPERTIES
./gradlew instrumentationTestsWithCoverage --stacktrace $CI_GRADLE_ARG_PROPERTIES
./gradlew generateCoverageReport --stacktrace $CI_GRADLE_ARG_PROPERTIES
./gradlew unitTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES
./gradlew instrumentationTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES
./gradlew generateCoverageReport $CI_GRADLE_ARG_PROPERTIES
# NB: continue-on-error marks steps.tests.conclusion = 'success' but leaves stes.tests.outcome = 'failure'
- name: Run all the codecoverage tests at once (retry if emulator failed)
uses: reactivecircus/android-emulator-runner@v2
@ -67,9 +65,9 @@ jobs:
disable-animations: true
emulator-build: 7425822
script: |
./gradlew unitTestsWithCoverage --stacktrace $CI_GRADLE_ARG_PROPERTIES
./gradlew instrumentationTestsWithCoverage --stacktrace $CI_GRADLE_ARG_PROPERTIES
./gradlew generateCoverageReport --stacktrace $CI_GRADLE_ARG_PROPERTIES
./gradlew unitTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES
./gradlew instrumentationTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES
./gradlew generateCoverageReport $CI_GRADLE_ARG_PROPERTIES
- run: ./gradlew sonarqube $CI_GRADLE_ARG_PROPERTIES
if: always() # we may have failed a previous step and retried, that's OK
env:
@ -114,5 +112,5 @@ jobs:
# restore-keys: |
# ${{ runner.os }}-gradle-
# - name: Build Android Tests
# run: ./gradlew clean assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES --stacktrace
# run: ./gradlew clean assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES

1
changelog.d/6406.misc Normal file
View file

@ -0,0 +1 @@
[Modularization] Provides abstraction to avoids direct usages of BuildConfig

1
changelog.d/6653.misc Normal file
View file

@ -0,0 +1 @@
[Location share] Update minimum sending period to 5 seconds for a live

1
changelog.d/6678.misc Normal file
View file

@ -0,0 +1 @@
[Timeline] Memory leak in audio message playback tracker

1
changelog.d/6680.misc Normal file
View file

@ -0,0 +1 @@
[FTUE] Memory leak on FtueAuthSplashCarouselFragment

1
changelog.d/6711.feature Normal file
View file

@ -0,0 +1 @@
[Location Share] Render fallback UI when map fails to load

1
changelog.d/6737.bugfix Normal file
View file

@ -0,0 +1 @@
Fixes onboarding login/account creation errors showing after navigation

1
changelog.d/6739.misc Normal file
View file

@ -0,0 +1 @@
Link directly to DCO docs from danger message.

View file

@ -48,7 +48,7 @@ mv towncrier.toml towncrier.toml.bak
sed 's/CHANGES\.md/CHANGES_NIGHTLY\.md/' towncrier.toml.bak > towncrier.toml
rm towncrier.toml.bak
yes n | towncrier --version nightly
./gradlew assembleGplayNightly appDistributionUploadGplayNightly $CI_GRADLE_ARG_PROPERTIES --stacktrace
./gradlew assembleGplayNightly appDistributionUploadGplayNightly $CI_GRADLE_ARG_PROPERTIES
```
Then you can reset the change on the codebase.

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MapLoadingErrorView">
<attr name="mapErrorDescription" format="string" />
</declare-styleable>
</resources>

View file

@ -199,7 +199,7 @@ dependencies {
implementation libs.apache.commonsImaging
// Phone number https://github.com/google/libphonenumber
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.52'
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.53'
testImplementation libs.tests.junit
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281

View file

@ -83,7 +83,7 @@ if (requiresSignOff) {
const hasPRBodySignOff = pr.body.includes(signOff)
const hasCommitSignOff = danger.git.commits.every(commit => commit.message.includes(signOff))
if (!hasPRBodySignOff && !hasCommitSignOff) {
fail("Please add a sign-off to either the PR description or to the commits themselves.")
fail("Please add a sign-off to either the PR description or to the commits themselves. See instructions [here](https://matrix-org.github.io/synapse/latest/development/contributing_guide.html#sign-off).")
}
}

View file

@ -0,0 +1,48 @@
/*
* 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.config
/**
* The types of analytics Element currently supports.
*/
sealed interface Analytics {
/**
* Disables the analytics integrations.
*/
object Disabled : Analytics
/**
* Analytics integration via PostHog.
*/
data class PostHog(
/**
* The PostHog instance url.
*/
val postHogHost: String,
/**
* The PostHog instance API key.
*/
val postHogApiKey: String,
/**
* A URL to more information about the analytics collection.
*/
val policyLink: String,
) : Analytics
}

View file

@ -36,4 +36,57 @@ object Config {
* - Changing the value from `true` to `false` will force the app to return to the background sync / Firebase Push.
*/
const val ALLOW_EXTERNAL_UNIFIED_PUSH_DISTRIBUTORS = true
const val ENABLE_LOCATION_SHARING = true
const val LOCATION_MAP_TILER_KEY = "fU3vlMsMn4Jb6dnEIFsx"
/**
* The maximum length of voice messages in milliseconds.
*/
const val VOICE_MESSAGE_LIMIT_MS = 120_000L
/**
* The strategy for sharing device keys.
*/
val KEY_SHARING_STRATEGY = KeySharingStrategy.WhenTyping
/**
* The onboarding flow.
*/
val ONBOARDING_VARIANT = OnboardingVariant.FTUE_AUTH
/**
* If set, MSC3086 asserted identity messages sent on VoIP calls will cause the call to appear in the room corresponding to the asserted identity.
* This *must* only be set in trusted environments.
*/
const val HANDLE_CALL_ASSERTED_IDENTITY_EVENTS = false
const val LOW_PRIVACY_LOG_ENABLE = false
const val ENABLE_STRICT_MODE_LOGS = false
/**
* The analytics configuration to use for the Debug build type.
* Can be disabled by providing Analytics.Disabled
*/
val DEBUG_ANALYTICS_CONFIG = Analytics.PostHog(
postHogHost = "https://posthog.element.dev",
postHogApiKey = "phc_VtA1L35nw3aeAtHIx1ayrGdzGkss7k1xINeXcoIQzXN",
policyLink = "https://element.io/cookie-policy",
)
/**
* The analytics configuration to use for the Release build type.
* Can be disabled by providing Analytics.Disabled
*/
val RELEASE_ANALYTICS_CONFIG = Analytics.PostHog(
postHogHost = "https://posthog.hss.element.io",
postHogApiKey = "phc_Jzsm6DTm6V2705zeU5dcNvQDlonOR68XvX2sh1sEOHO",
policyLink = "https://element.io/cookie-policy",
)
/**
* The analytics configuration to use for the Nightly build type.
* Can be disabled by providing Analytics.Disabled
*/
val NIGHTLY_ANALYTICS_CONFIG = RELEASE_ANALYTICS_CONFIG
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021 New Vector Ltd
* 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.
@ -16,17 +16,20 @@
package im.vector.app.config
import im.vector.app.BuildConfig
import im.vector.app.features.analytics.AnalyticsConfig
enum class KeySharingStrategy {
/**
* Keys will be sent for the first time when the first message is sent.
* This is handled by the Matrix SDK so there's no need to do it in Vector.
*/
WhenSendingEvent,
private val allowedPackageList = listOf(
"im.vector.app",
"im.vector.app.nightly",
)
/**
* Keys will be sent for the first time when the timeline displayed.
*/
WhenEnteringRoom,
val analyticsConfig: AnalyticsConfig = object : AnalyticsConfig {
override val isEnabled = BuildConfig.APPLICATION_ID in allowedPackageList
override val postHogHost = "https://posthog.hss.element.io"
override val postHogApiKey = "phc_Jzsm6DTm6V2705zeU5dcNvQDlonOR68XvX2sh1sEOHO"
override val policyLink = "https://element.io/cookie-policy"
/**
* Keys will be sent for the first time when a typing started.
*/
WhenTyping
}

View file

@ -0,0 +1,23 @@
/*
* 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.config
enum class OnboardingVariant {
LEGACY,
LOGIN_2,
FTUE_AUTH
}

View file

@ -156,19 +156,6 @@ android {
buildConfigField "String", "GIT_BRANCH_NAME", "\"${gitBranchName()}\""
buildConfigField "String", "BUILD_NUMBER", "\"${buildNumber}\""
buildConfigField "im.vector.app.features.VectorFeatures.OnboardingVariant", "ONBOARDING_VARIANT", "im.vector.app.features.VectorFeatures.OnboardingVariant.FTUE_AUTH"
buildConfigField "im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy", "outboundSessionKeySharingStrategy", "im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy.WhenTyping"
buildConfigField "Long", "VOICE_MESSAGE_DURATION_LIMIT_MS", "120_000L"
// If set, MSC3086 asserted identity messages sent on VoIP calls will cause the call to appear in the room corresponding to the asserted identity.
// This *must* only be set in trusted environments.
buildConfigField "Boolean", "handleCallAssertedIdentityEvents", "false"
buildConfigField "Boolean", "enableLocationSharing", "true"
buildConfigField "String", "mapTilerKey", "\"fU3vlMsMn4Jb6dnEIFsx\""
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
// Keep abiFilter for the universalApk
@ -250,10 +237,6 @@ android {
resValue "string", "app_name", "Element dbg"
resValue "color", "launcher_background", "#0DBD8B"
buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false"
// Set to true if you want to enable strict mode in debug
buildConfigField "boolean", "ENABLE_STRICT_MODE_LOGS", "false"
signingConfig signingConfigs.debug
if (project.hasProperty("coverage")) {
@ -265,10 +248,6 @@ android {
resValue "string", "app_name", "Element"
resValue "color", "launcher_background", "#0DBD8B"
buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false"
buildConfigField "boolean", "ENABLE_STRICT_MODE_LOGS", "false"
// When updating this block, please also update the same block in the `nightly` buildType below
postprocessing {
removeUnusedCode true
removeUnusedResources true
@ -329,7 +308,6 @@ android {
versionName "${versionMajor}.${versionMinor}.${versionPatch}${getGplayVersionSuffix()}"
resValue "bool", "isGplay", "true"
buildConfigField "boolean", "ALLOW_FCM_USE", "true"
buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"G\""
buildConfigField "String", "FLAVOR_DESCRIPTION", "\"GooglePlay\""
}
@ -340,7 +318,6 @@ android {
versionName "${versionMajor}.${versionMinor}.${versionPatch}${getFdroidVersionSuffix()}"
resValue "bool", "isGplay", "false"
buildConfigField "boolean", "ALLOW_FCM_USE", "false"
buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"F\""
buildConfigField "String", "FLAVOR_DESCRIPTION", "\"FDroid\""
}
@ -438,7 +415,7 @@ dependencies {
implementation 'com.facebook.stetho:stetho:1.6.0'
// Phone number https://github.com/google/libphonenumber
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.52'
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.53'
// FlowBinding
implementation libs.github.flowBinding

View file

@ -70,6 +70,11 @@ class DebugFeaturesStateFactory @Inject constructor(
key = DebugFeatureKeys.allowExternalUnifiedPushDistributors,
factory = VectorFeatures::allowExternalUnifiedPushDistributors
),
createBooleanFeature(
label = "Enable Live Location Sharing",
key = DebugFeatureKeys.liveLocationSharing,
factory = VectorFeatures::isLocationSharingEnabled
),
createBooleanFeature(
label = "Force usage of OpusEncoder library",
key = DebugFeatureKeys.forceUsageOfOpusEncoder,

View file

@ -24,6 +24,7 @@ import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import im.vector.app.config.OnboardingVariant
import im.vector.app.features.DefaultVectorFeatures
import im.vector.app.features.VectorFeatures
import kotlinx.coroutines.flow.first
@ -39,8 +40,8 @@ class DebugVectorFeatures(
private val dataStore = context.dataStore
override fun onboardingVariant(): VectorFeatures.OnboardingVariant {
return readPreferences().getEnum<VectorFeatures.OnboardingVariant>() ?: vectorFeatures.onboardingVariant()
override fun onboardingVariant(): OnboardingVariant {
return readPreferences().getEnum<OnboardingVariant>() ?: vectorFeatures.onboardingVariant()
}
override fun isOnboardingAlreadyHaveAccountSplashEnabled(): Boolean = read(DebugFeatureKeys.onboardingAlreadyHaveAnAccount)
@ -66,6 +67,9 @@ class DebugVectorFeatures(
override fun isScreenSharingEnabled(): Boolean = read(DebugFeatureKeys.screenSharing)
?: vectorFeatures.isScreenSharingEnabled()
override fun isLocationSharingEnabled(): Boolean = read(DebugFeatureKeys.liveLocationSharing)
?: vectorFeatures.isLocationSharingEnabled()
override fun forceUsageOfOpusEncoder(): Boolean = read(DebugFeatureKeys.forceUsageOfOpusEncoder)
?: vectorFeatures.forceUsageOfOpusEncoder()

View file

@ -40,7 +40,9 @@ import com.mapbox.mapboxsdk.Mapbox
import com.vanniktech.emoji.EmojiManager
import com.vanniktech.emoji.google.GoogleEmojiProvider
import dagger.hilt.android.HiltAndroidApp
import im.vector.app.config.Config
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.resources.BuildMeta
import im.vector.app.features.analytics.VectorAnalytics
import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.configuration.VectorConfiguration
@ -99,6 +101,7 @@ class VectorApplication :
@Inject lateinit var flipperProxy: FlipperProxy
@Inject lateinit var matrix: Matrix
@Inject lateinit var fcmHelper: FcmHelper
@Inject lateinit var buildMeta: BuildMeta
// font thread handler
private var fontThreadHandler: Handler? = null
@ -127,12 +130,12 @@ class VectorApplication :
.filterIsInstance(JitsiMeetDefaultLogHandler::class.java)
.forEach { Timber.uproot(it) }
if (BuildConfig.DEBUG) {
if (buildMeta.isDebug) {
Timber.plant(Timber.DebugTree())
}
Timber.plant(vectorFileLogger)
if (BuildConfig.DEBUG) {
if (buildMeta.isDebug) {
Stetho.initializeWithDefaults(this)
}
logInfo()
@ -148,7 +151,7 @@ class VectorApplication :
R.array.com_google_android_gms_fonts_certs
)
FontsContractCompat.requestFont(this, fontRequest, emojiCompatFontProvider, getFontThreadHandler())
VectorLocale.init(this)
VectorLocale.init(this, buildMeta)
ThemeUtils.init(this)
vectorConfiguration.applyToApplicationContext()
@ -196,7 +199,7 @@ class VectorApplication :
}
private fun enableStrictModeIfNeeded() {
if (BuildConfig.ENABLE_STRICT_MODE_LOGS) {
if (Config.ENABLE_STRICT_MODE_LOGS) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectAll()

View file

@ -0,0 +1,80 @@
/*
* 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.di
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import im.vector.app.BuildConfig
import im.vector.app.config.Analytics
import im.vector.app.config.Config
import im.vector.app.config.KeySharingStrategy
import im.vector.app.features.analytics.AnalyticsConfig
import im.vector.app.features.call.webrtc.VoipConfig
import im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy
import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageConfig
import im.vector.app.features.location.LocationSharingConfig
import im.vector.app.features.raw.wellknown.CryptoConfig
@InstallIn(SingletonComponent::class)
@Module
object ConfigurationModule {
@Provides
fun providesAnalyticsConfig(): AnalyticsConfig {
val config: Analytics = when (BuildConfig.BUILD_TYPE) {
"debug" -> Config.DEBUG_ANALYTICS_CONFIG
"nightly" -> Config.NIGHTLY_ANALYTICS_CONFIG
"release" -> Config.RELEASE_ANALYTICS_CONFIG
else -> throw IllegalStateException("Unhandled build type: ${BuildConfig.BUILD_TYPE}")
}
return when (config) {
Analytics.Disabled -> AnalyticsConfig(isEnabled = false, "", "", "")
is Analytics.PostHog -> AnalyticsConfig(
isEnabled = true,
postHogHost = config.postHogHost,
postHogApiKey = config.postHogApiKey,
policyLink = config.policyLink
)
}
}
@Provides
fun providesVoiceMessageConfig() = VoiceMessageConfig(
lengthLimitMs = Config.VOICE_MESSAGE_LIMIT_MS
)
@Provides
fun providesCryptoConfig() = CryptoConfig(
fallbackKeySharingStrategy = when (Config.KEY_SHARING_STRATEGY) {
KeySharingStrategy.WhenSendingEvent -> OutboundSessionKeySharingStrategy.WhenSendingEvent
KeySharingStrategy.WhenEnteringRoom -> OutboundSessionKeySharingStrategy.WhenSendingEvent
KeySharingStrategy.WhenTyping -> OutboundSessionKeySharingStrategy.WhenSendingEvent
}
)
@Provides
fun providesLocationSharingConfig() = LocationSharingConfig(
mapTilerKey = Config.LOCATION_MAP_TILER_KEY,
)
@Provides
fun providesVoipConfig() = VoipConfig(
handleCallAssertedIdentityEvents = Config.HANDLE_CALL_ASSERTED_IDENTITY_EVENTS
)
}

View file

@ -64,8 +64,8 @@ import im.vector.app.features.home.room.detail.search.SearchFragment
import im.vector.app.features.home.room.list.RoomListFragment
import im.vector.app.features.home.room.list.home.HomeRoomListFragment
import im.vector.app.features.home.room.threads.list.views.ThreadListFragment
import im.vector.app.features.location.LocationPreviewFragment
import im.vector.app.features.location.LocationSharingFragment
import im.vector.app.features.location.preview.LocationPreviewFragment
import im.vector.app.features.login.LoginCaptchaFragment
import im.vector.app.features.login.LoginFragment
import im.vector.app.features.login.LoginGenericTextInputFormFragment

View file

@ -56,6 +56,7 @@ import im.vector.app.features.homeserver.HomeServerCapabilitiesViewModel
import im.vector.app.features.invite.InviteUsersToRoomViewModel
import im.vector.app.features.location.LocationSharingViewModel
import im.vector.app.features.location.live.map.LiveLocationMapViewModel
import im.vector.app.features.location.preview.LocationPreviewViewModel
import im.vector.app.features.login.LoginViewModel
import im.vector.app.features.login2.LoginViewModel2
import im.vector.app.features.login2.created.AccountCreatedViewModel
@ -605,6 +606,11 @@ interface MavericksViewModelModule {
@MavericksViewModelKey(LocationSharingViewModel::class)
fun createLocationSharingViewModelFactory(factory: LocationSharingViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
@Binds
@IntoMap
@MavericksViewModelKey(LocationPreviewViewModel::class)
fun createLocationPreviewViewModelFactory(factory: LocationPreviewViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
@Binds
@IntoMap
@MavericksViewModelKey(VectorAttachmentViewerViewModel::class)

View file

@ -33,7 +33,7 @@ import im.vector.app.EmojiCompatWrapper
import im.vector.app.EmojiSpanify
import im.vector.app.SpaceStateHandler
import im.vector.app.SpaceStateHandlerImpl
import im.vector.app.config.analyticsConfig
import im.vector.app.config.Config
import im.vector.app.core.dispatchers.CoroutineDispatchers
import im.vector.app.core.error.DefaultErrorFormatter
import im.vector.app.core.error.ErrorFormatter
@ -42,7 +42,6 @@ import im.vector.app.core.time.Clock
import im.vector.app.core.time.DefaultClock
import im.vector.app.core.utils.AndroidSystemSettingsProvider
import im.vector.app.core.utils.SystemSettingsProvider
import im.vector.app.features.analytics.AnalyticsConfig
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.VectorAnalytics
import im.vector.app.features.analytics.impl.DefaultVectorAnalytics
@ -205,17 +204,23 @@ object VectorStaticModule {
return GlobalScope
}
@Provides
fun providesAnalyticsConfig(): AnalyticsConfig {
return analyticsConfig
}
@Provides
fun providesPhoneNumberUtil(): PhoneNumberUtil = PhoneNumberUtil.getInstance()
@Provides
@Singleton
fun providesBuildMeta() = BuildMeta()
fun providesBuildMeta() = BuildMeta(
isDebug = BuildConfig.DEBUG,
applicationId = BuildConfig.APPLICATION_ID,
lowPrivacyLoggingEnabled = Config.LOW_PRIVACY_LOG_ENABLE,
versionName = BuildConfig.VERSION_NAME,
gitRevision = BuildConfig.GIT_REVISION,
gitRevisionDate = BuildConfig.GIT_REVISION_DATE,
gitBranchName = BuildConfig.GIT_BRANCH_NAME,
buildNumber = BuildConfig.BUILD_NUMBER,
flavorDescription = BuildConfig.FLAVOR_DESCRIPTION,
flavorShortDescription = BuildConfig.SHORT_FLAVOR_DESCRIPTION,
)
@Provides
@Singleton

View file

@ -37,7 +37,7 @@ import androidx.datastore.preferences.core.Preferences
import dagger.hilt.EntryPoints
import im.vector.app.core.datastore.dataStoreProvider
import im.vector.app.core.di.SingletonEntryPoint
import im.vector.app.core.resources.BuildMeta
import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider
import java.io.OutputStream
import kotlin.math.roundToInt
@ -93,9 +93,9 @@ fun Context.safeOpenOutputStream(uri: Uri): OutputStream? {
*/
@Suppress("deprecation")
@SuppressLint("NewApi") // false positive
fun Context.inferNoConnectivity(buildMeta: BuildMeta): Boolean {
fun Context.inferNoConnectivity(sdkIntProvider: BuildVersionSdkIntProvider): Boolean {
val connectivityManager = getSystemService<ConnectivityManager>()!!
return if (buildMeta.sdkInt > Build.VERSION_CODES.M) {
return if (sdkIntProvider.get() > Build.VERSION_CODES.M) {
val networkCapabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
when {
networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true -> false

View file

@ -78,10 +78,14 @@ fun TextInputLayout.setOnImeDoneListener(action: () -> Unit) {
}
}
fun TextInputLayout.setOnFocusLostListener(action: () -> Unit) {
/**
* Set a listener for when the input has lost focus, such as moving to the another input field.
* The listener is only called when the view is in a resumed state to avoid triggers when exiting a screen.
*/
fun TextInputLayout.setOnFocusLostListener(lifecycleOwner: LifecycleOwner, action: () -> Unit) {
editText().setOnFocusChangeListener { _, hasFocus ->
when (hasFocus) {
false -> action()
false -> lifecycleOwner.lifecycleScope.launchWhenResumed { action() }
else -> {
// do nothing
}

View file

@ -54,7 +54,6 @@ import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.EntryPointAccessors
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.ActivityEntryPoint
@ -68,6 +67,7 @@ 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.resources.BuildMeta
import im.vector.app.core.utils.AndroidSystemSettingsProvider
import im.vector.app.core.utils.ToolbarConfig
import im.vector.app.core.utils.toast
@ -157,11 +157,9 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
protected lateinit var bugReporter: BugReporter
private lateinit var pinLocker: PinLocker
@Inject
lateinit var rageShake: RageShake
@Inject
lateinit var fontScalePreferences: FontScalePreferences
@Inject lateinit var rageShake: RageShake
@Inject lateinit var buildMeta: BuildMeta
@Inject lateinit var fontScalePreferences: FontScalePreferences
@Inject
lateinit var vectorFeatures: VectorFeatures
@ -422,7 +420,7 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
}
DebugReceiver
.getIntentFilter(this)
.takeIf { BuildConfig.DEBUG }
.takeIf { buildMeta.isDebug }
?.let {
debugReceiver = DebugReceiver()
registerReceiver(debugReceiver, it)

View file

@ -25,14 +25,14 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.BuildConfig
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.network.WifiDetector
import im.vector.app.core.pushers.model.PushData
import im.vector.app.core.resources.BuildMeta
import im.vector.app.core.services.GuardServiceStarter
import im.vector.app.features.notifications.NotifiableEventResolver
import im.vector.app.features.notifications.NotificationActionIds
import im.vector.app.features.notifications.NotificationDrawerManager
import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.settings.BackgroundSyncMode
import im.vector.app.features.settings.VectorDataStore
import im.vector.app.features.settings.VectorPreferences
@ -68,6 +68,8 @@ class VectorMessagingReceiver : MessagingReceiver() {
@Inject lateinit var unifiedPushHelper: UnifiedPushHelper
@Inject lateinit var unifiedPushStore: UnifiedPushStore
@Inject lateinit var pushParser: PushParser
@Inject lateinit var actionIds: NotificationActionIds
@Inject lateinit var buildMeta: BuildMeta
private val coroutineScope = CoroutineScope(SupervisorJob())
@ -87,7 +89,7 @@ class VectorMessagingReceiver : MessagingReceiver() {
Timber.tag(loggerTag.value).d("## onMessage() received")
val sMessage = String(message)
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
if (buildMeta.lowPrivacyLoggingEnabled) {
Timber.tag(loggerTag.value).d("## onMessage() $sMessage")
}
@ -100,7 +102,7 @@ class VectorMessagingReceiver : MessagingReceiver() {
// Diagnostic Push
if (pushData.eventId == PushersManager.TEST_EVENT_ID) {
val intent = Intent(NotificationUtils.PUSH_ACTION)
val intent = Intent(actionIds.push)
LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
return
}
@ -171,7 +173,7 @@ class VectorMessagingReceiver : MessagingReceiver() {
*/
private suspend fun onMessageReceivedInternal(pushData: PushData) {
try {
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
if (buildMeta.lowPrivacyLoggingEnabled) {
Timber.tag(loggerTag.value).d("## onMessageReceivedInternal() : $pushData")
} else {
Timber.tag(loggerTag.value).d("## onMessageReceivedInternal()")

View file

@ -16,8 +16,15 @@
package im.vector.app.core.resources
import android.os.Build
data class BuildMeta(
val sdkInt: Int = Build.VERSION.SDK_INT
val isDebug: Boolean,
val applicationId: String,
val lowPrivacyLoggingEnabled: Boolean,
val versionName: String,
val gitRevision: String,
val gitRevisionDate: String,
val gitBranchName: String,
val buildNumber: String,
val flavorDescription: String,
val flavorShortDescription: String,
)

View file

@ -39,7 +39,6 @@ import androidx.browser.customtabs.CustomTabsSession
import androidx.core.app.ShareCompat
import androidx.core.content.FileProvider
import androidx.core.content.getSystemService
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.themes.ThemeUtils
@ -182,7 +181,7 @@ fun openUri(activity: Activity, uri: String) {
*/
fun openMedia(activity: Activity, savedMediaPath: String, mimeType: String) {
val file = File(savedMediaPath)
val uri = FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + ".fileProvider", file)
val uri = FileProvider.getUriForFile(activity, activity.packageName + ".fileProvider", file)
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, mimeType)
@ -214,7 +213,7 @@ fun openLocation(activity: Activity, latitude: Double, longitude: Double) {
fun shareMedia(context: Context, file: File, mediaMimeType: String?) {
val mediaUri = try {
FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileProvider", file)
FileProvider.getUriForFile(context, context.packageName + ".fileProvider", file)
} catch (e: Exception) {
Timber.e(e, "onMediaAction Selected File cannot be shared")
return
@ -376,7 +375,7 @@ private fun addToGallery(savedFile: File, mediaMimeType: String?, context: Conte
/**
* Open the play store to the provided application Id, default to this app.
*/
fun openPlayStore(activity: Activity, appId: String = BuildConfig.APPLICATION_ID) {
fun openPlayStore(activity: Activity, appId: String) {
try {
activity.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=$appId")))
} catch (activityNotFoundException: ActivityNotFoundException) {

View file

@ -16,8 +16,8 @@
package im.vector.app.features
import im.vector.app.BuildConfig
import im.vector.app.config.Config
import im.vector.app.config.OnboardingVariant
interface VectorFeatures {
@ -30,19 +30,14 @@ interface VectorFeatures {
fun isOnboardingCombinedLoginEnabled(): Boolean
fun allowExternalUnifiedPushDistributors(): Boolean
fun isScreenSharingEnabled(): Boolean
fun isLocationSharingEnabled(): Boolean
fun forceUsageOfOpusEncoder(): Boolean
fun shouldStartDmOnFirstMessage(): Boolean
fun isNewAppLayoutEnabled(): Boolean
enum class OnboardingVariant {
LEGACY,
LOGIN_2,
FTUE_AUTH
}
}
class DefaultVectorFeatures : VectorFeatures {
override fun onboardingVariant(): VectorFeatures.OnboardingVariant = BuildConfig.ONBOARDING_VARIANT
override fun onboardingVariant() = Config.ONBOARDING_VARIANT
override fun isOnboardingAlreadyHaveAccountSplashEnabled() = true
override fun isOnboardingSplashCarouselEnabled() = true
override fun isOnboardingUseCaseEnabled() = true
@ -51,6 +46,7 @@ class DefaultVectorFeatures : VectorFeatures {
override fun isOnboardingCombinedLoginEnabled() = true
override fun allowExternalUnifiedPushDistributors(): Boolean = Config.ALLOW_EXTERNAL_UNIFIED_PUSH_DISTRIBUTORS
override fun isScreenSharingEnabled(): Boolean = true
override fun isLocationSharingEnabled() = Config.ENABLE_LOCATION_SHARING
override fun forceUsageOfOpusEncoder(): Boolean = false
override fun shouldStartDmOnFirstMessage(): Boolean = false
override fun isNewAppLayoutEnabled(): Boolean = false

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021 New Vector Ltd
* 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.
@ -16,9 +16,9 @@
package im.vector.app.features.analytics
interface AnalyticsConfig {
val isEnabled: Boolean
val postHogHost: String
val postHogApiKey: String
val policyLink: String
}
data class AnalyticsConfig(
val isEnabled: Boolean,
val postHogHost: String,
val postHogApiKey: String,
val policyLink: String,
)

View file

@ -18,11 +18,15 @@ package im.vector.app.features.analytics.impl
import android.content.Context
import com.posthog.android.PostHog
import im.vector.app.BuildConfig
import im.vector.app.config.analyticsConfig
import im.vector.app.core.resources.BuildMeta
import im.vector.app.features.analytics.AnalyticsConfig
import javax.inject.Inject
class PostHogFactory @Inject constructor(private val context: Context) {
class PostHogFactory @Inject constructor(
private val context: Context,
private val analyticsConfig: AnalyticsConfig,
private val buildMeta: BuildMeta,
) {
fun createPosthog(): PostHog {
return PostHog.Builder(context, analyticsConfig.postHogApiKey, analyticsConfig.postHogHost)
@ -43,7 +47,7 @@ class PostHogFactory @Inject constructor(private val context: Context) {
}
private fun getLogLevel(): PostHog.LogLevel {
return if (BuildConfig.DEBUG) {
return if (buildMeta.isDebug) {
PostHog.LogLevel.DEBUG
} else {
PostHog.LogLevel.INFO

View file

@ -22,17 +22,17 @@ import android.view.View
import android.view.ViewGroup
import com.airbnb.mvrx.activityViewModel
import im.vector.app.R
import im.vector.app.config.analyticsConfig
import im.vector.app.core.extensions.setTextWithColoredPart
import im.vector.app.core.platform.OnBackPressed
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.openUrlInChromeCustomTab
import im.vector.app.databinding.FragmentAnalyticsOptinBinding
import im.vector.app.features.analytics.AnalyticsConfig
import javax.inject.Inject
class AnalyticsOptInFragment @Inject constructor() :
VectorBaseFragment<FragmentAnalyticsOptinBinding>(),
OnBackPressed {
class AnalyticsOptInFragment @Inject constructor(
private val analyticsConfig: AnalyticsConfig,
) : VectorBaseFragment<FragmentAnalyticsOptinBinding>(), OnBackPressed {
// Share the view model with the Activity so that the Activity
// can decide what to do when the data has been saved

View file

@ -23,9 +23,9 @@ import android.os.Bundle
import androidx.activity.result.ActivityResultLauncher
import im.vector.app.core.dialogs.PhotoOrVideoDialog
import im.vector.app.core.platform.Restorable
import im.vector.app.core.resources.BuildMeta
import im.vector.app.features.settings.VectorPreferences
import im.vector.lib.multipicker.MultiPicker
import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
import timber.log.Timber
@ -35,15 +35,14 @@ private const val PENDING_TYPE_KEY = "PENDING_TYPE_KEY"
/**
* This class helps to handle attachments by providing simple methods.
*/
class AttachmentsHelper(val context: Context, val callback: Callback) : Restorable {
class AttachmentsHelper(
val context: Context,
val callback: Callback,
private val buildMeta: BuildMeta,
) : Restorable {
interface Callback {
fun onContactAttachmentReady(contactAttachment: ContactAttachment) {
if (BuildConfig.LOG_PRIVATE_DATA) {
Timber.v("On contact attachment ready: $contactAttachment")
}
}
fun onContactAttachmentReady(contactAttachment: ContactAttachment)
fun onContentAttachmentsReady(attachments: List<ContentAttachmentData>)
}
@ -144,6 +143,9 @@ class AttachmentsHelper(val context: Context, val callback: Callback) : Restorab
.firstOrNull()
?.toContactAttachment()
?.let {
if (buildMeta.lowPrivacyLoggingEnabled) {
Timber.v("On contact attachment ready: $it")
}
callback.onContactAttachmentReady(it)
}
}

View file

@ -0,0 +1,21 @@
/*
* 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.features.call.webrtc
data class VoipConfig(
val handleCallAssertedIdentityEvents: Boolean
)

View file

@ -20,7 +20,6 @@ import android.content.Context
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import im.vector.app.ActiveSessionDataSource
import im.vector.app.BuildConfig
import im.vector.app.core.pushers.UnifiedPushHelper
import im.vector.app.core.services.CallAndroidService
import im.vector.app.features.analytics.AnalyticsTracker
@ -74,6 +73,7 @@ class WebRtcCallManager @Inject constructor(
private val activeSessionDataSource: ActiveSessionDataSource,
private val analyticsTracker: AnalyticsTracker,
private val unifiedPushHelper: UnifiedPushHelper,
private val voipConfig: VoipConfig,
) : CallListener,
DefaultLifecycleObserver {
@ -444,7 +444,7 @@ class WebRtcCallManager @Inject constructor(
}
override fun onCallAssertedIdentityReceived(callAssertedIdentityContent: CallAssertedIdentityContent) {
if (!BuildConfig.handleCallAssertedIdentityEvents) {
if (!voipConfig.handleCallAssertedIdentityEvents) {
return
}
val call = callsByCallId[callAssertedIdentityContent.callId]

View file

@ -22,11 +22,11 @@ import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.config.analyticsConfig
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.analytics.AnalyticsConfig
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.extensions.toAnalyticsType
import im.vector.app.features.analytics.plan.Signup
@ -80,7 +80,8 @@ class HomeActivityViewModel @AssistedInject constructor(
private val analyticsStore: AnalyticsStore,
private val lightweightSettingsStorage: LightweightSettingsStorage,
private val vectorPreferences: VectorPreferences,
private val analyticsTracker: AnalyticsTracker
private val analyticsTracker: AnalyticsTracker,
private val analyticsConfig: AnalyticsConfig,
) : VectorViewModel<HomeActivityViewState, HomeActivityViewActions, HomeActivityViewEvents>(initialState) {
@AssistedFactory

View file

@ -41,7 +41,6 @@ import im.vector.app.core.ui.views.CurrentCallsView
import im.vector.app.core.ui.views.CurrentCallsViewPresenter
import im.vector.app.core.ui.views.KeysBackupBanner
import im.vector.app.databinding.FragmentHomeDetailBinding
import im.vector.app.features.VectorFeatures
import im.vector.app.features.call.SharedKnownCallsViewModel
import im.vector.app.features.call.VectorCallActivity
import im.vector.app.features.call.dialpad.DialPadFragment
@ -49,7 +48,6 @@ import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.home.room.list.RoomListFragment
import im.vector.app.features.home.room.list.RoomListParams
import im.vector.app.features.home.room.list.UnreadCounterBadgeView
import im.vector.app.features.home.room.list.home.HomeRoomListFragment
import im.vector.app.features.popup.PopupAlertManager
import im.vector.app.features.popup.VerificationVectorAlert
import im.vector.app.features.settings.VectorLocale
@ -69,7 +67,6 @@ class HomeDetailFragment @Inject constructor(
private val callManager: WebRtcCallManager,
private val vectorPreferences: VectorPreferences,
private val spaceStateHandler: SpaceStateHandler,
private val vectorFeatures: VectorFeatures,
) : VectorBaseFragment<FragmentHomeDetailBinding>(),
KeysBackupBanner.Delegate,
CurrentCallsView.Callback,
@ -355,12 +352,8 @@ class HomeDetailFragment @Inject constructor(
if (fragmentToShow == null) {
when (tab) {
is HomeTab.RoomList -> {
if (vectorFeatures.isNewAppLayoutEnabled()) {
add(R.id.roomListContainer, HomeRoomListFragment::class.java, null, fragmentTag)
} else {
val params = RoomListParams(tab.displayMode)
add(R.id.roomListContainer, RoomListFragment::class.java, params.toMvRxBundle(), fragmentTag)
}
val params = RoomListParams(tab.displayMode)
add(R.id.roomListContainer, RoomListFragment::class.java, params.toMvRxBundle(), fragmentTag)
}
is HomeTab.DialPad -> {
add(R.id.roomListContainer, createDialPadFragment(), fragmentTag)

View file

@ -23,11 +23,11 @@ import android.view.ViewGroup
import androidx.core.app.ActivityOptionsCompat
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.extensions.observeK
import im.vector.app.core.extensions.replaceChildFragment
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.BuildMeta
import im.vector.app.core.utils.startSharePlainTextIntent
import im.vector.app.databinding.FragmentHomeDrawerBinding
import im.vector.app.features.analytics.plan.MobileScreen
@ -43,7 +43,8 @@ import javax.inject.Inject
class HomeDrawerFragment @Inject constructor(
private val session: Session,
private val vectorPreferences: VectorPreferences,
private val avatarRenderer: AvatarRenderer
private val avatarRenderer: AvatarRenderer,
private val buildMeta: BuildMeta,
) : VectorBaseFragment<FragmentHomeDrawerBinding>() {
private lateinit var sharedActionViewModel: HomeSharedActionViewModel
@ -112,7 +113,7 @@ class HomeDrawerFragment @Inject constructor(
}
// Debug menu
views.homeDrawerHeaderDebugView.isVisible = BuildConfig.DEBUG && vectorPreferences.developerMode()
views.homeDrawerHeaderDebugView.isVisible = buildMeta.isDebug && vectorPreferences.developerMode()
views.homeDrawerHeaderDebugView.debouncedClicks {
sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer)
navigator.openDebug(requireActivity())

View file

@ -31,7 +31,6 @@ import com.google.android.material.badge.BadgeDrawable
import im.vector.app.R
import im.vector.app.SpaceStateHandler
import im.vector.app.core.extensions.commitTransaction
import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.core.platform.OnBackPressed
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.platform.VectorBaseFragment

View file

@ -24,8 +24,8 @@ import androidx.annotation.WorkerThread
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
import im.vector.app.BuildConfig
import im.vector.app.core.glide.GlideApp
import im.vector.app.core.resources.BuildMeta
import im.vector.app.core.utils.DimensionConverter
import im.vector.app.features.MainActivity
import org.matrix.android.sdk.api.session.room.model.RoomSummary
@ -35,13 +35,15 @@ import javax.inject.Inject
private val useAdaptiveIcon = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
private const val adaptiveIconSizeDp = 108
private const val adaptiveIconOuterSidesDp = 18
private const val directShareCategory = BuildConfig.APPLICATION_ID + ".SHORTCUT_SHARE"
class ShortcutCreator @Inject constructor(
private val context: Context,
private val avatarRenderer: AvatarRenderer,
private val dimensionConverter: DimensionConverter
private val dimensionConverter: DimensionConverter,
buildMeta: BuildMeta,
) {
private val directShareCategory = buildMeta.applicationId + ".SHORTCUT_SHARE"
private val adaptiveIconSize = dimensionConverter.dpToPx(adaptiveIconSizeDp)
private val adaptiveIconOuterSides = dimensionConverter.dpToPx(adaptiveIconOuterSidesDp)
private val iconSize by lazy {

View file

@ -67,7 +67,6 @@ import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.vanniktech.emoji.EmojiPopup
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.animations.play
import im.vector.app.core.dialogs.ConfirmationDialogBuilder
@ -92,6 +91,7 @@ import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.platform.VectorMenuProvider
import im.vector.app.core.platform.lifecycleAwareLazy
import im.vector.app.core.platform.showOptimizedSnackbar
import im.vector.app.core.resources.BuildMeta
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.UserPreferencesProvider
import im.vector.app.core.time.Clock
@ -125,6 +125,7 @@ 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.FragmentTimelineBinding
import im.vector.app.features.VectorFeatures
import im.vector.app.features.analytics.extensions.toAnalyticsInteraction
import im.vector.app.features.analytics.plan.Interaction
import im.vector.app.features.analytics.plan.MobileScreen
@ -275,7 +276,9 @@ class TimelineFragment @Inject constructor(
private val callManager: WebRtcCallManager,
private val audioMessagePlaybackTracker: AudioMessagePlaybackTracker,
private val shareIntentHandler: ShareIntentHandler,
private val clock: Clock
private val clock: Clock,
private val vectorFeatures: VectorFeatures,
private val buildMeta: BuildMeta,
) :
VectorBaseFragment<FragmentTimelineBinding>(),
TimelineEventController.Callback,
@ -372,7 +375,7 @@ class TimelineFragment @Inject constructor(
sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java)
sharedActivityActionViewModel = activityViewModelProvider.get(RoomDetailSharedActionViewModel::class.java)
knownCallsViewModel = activityViewModelProvider.get(SharedKnownCallsViewModel::class.java)
attachmentsHelper = AttachmentsHelper(requireContext(), this).register()
attachmentsHelper = AttachmentsHelper(requireContext(), this, buildMeta).register()
callActionsHandler = StartCallActionsHandler(
roomId = timelineArgs.roomId,
fragment = this,
@ -962,7 +965,7 @@ class TimelineFragment @Inject constructor(
}
override fun onDestroyView() {
audioMessagePlaybackTracker.makeAllPlaybacksIdle()
messageComposerViewModel.endAllVoiceActions()
lazyLoadedViews.unBind()
timelineEventController.callback = null
timelineEventController.removeModelBuildListener(modelBuildListener)
@ -1095,7 +1098,7 @@ class TimelineFragment @Inject constructor(
else -> state.isAllowedToManageWidgets
}
menu.findItem(R.id.video_call).icon?.alpha = if (callButtonsEnabled) 0xFF else 0x40
menu.findItem(R.id.voice_call).icon?.alpha = if (callButtonsEnabled || state.hasActiveElementCallWidget()) 0xFF else 0x40
menu.findItem(R.id.voice_call).icon?.alpha = if (callButtonsEnabled || state.hasActiveElementCallWidget()) 0xFF else 0x40
val matrixAppsMenuItem = menu.findItem(R.id.open_matrix_apps)
val widgetsCount = state.activeRoomWidgets.invoke()?.size ?: 0
@ -1559,7 +1562,7 @@ class TimelineFragment @Inject constructor(
attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@TimelineFragment)
attachmentTypeSelector.setAttachmentVisibility(
AttachmentTypeSelectorView.Type.LOCATION,
BuildConfig.enableLocationSharing
vectorFeatures.isLocationSharingEnabled(),
)
attachmentTypeSelector.setAttachmentVisibility(
AttachmentTypeSelectorView.Type.POLL, !isThreadTimeLine()
@ -2646,7 +2649,6 @@ class TimelineFragment @Inject constructor(
}
override fun onContactAttachmentReady(contactAttachment: ContactAttachment) {
super.onContactAttachmentReady(contactAttachment)
val formattedContact = contactAttachment.toHumanReadable()
messageComposerViewModel.handle(MessageComposerAction.SendMessage(formattedContact, false))
}

View file

@ -33,6 +33,7 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.mvrx.runCatchingToAsync
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.BuildMeta
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.BehaviorDataSource
import im.vector.app.features.analytics.AnalyticsTracker
@ -56,6 +57,7 @@ import im.vector.app.features.location.live.StopLiveLocationShareUseCase
import im.vector.app.features.location.live.tracking.LocationSharingServiceConnection
import im.vector.app.features.notifications.NotificationDrawerManager
import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
import im.vector.app.features.raw.wellknown.CryptoConfig
import im.vector.app.features.raw.wellknown.getOutboundSessionKeySharingStrategyOrDefault
import im.vector.app.features.raw.wellknown.withElementWellKnown
import im.vector.app.features.session.coroutineScope
@ -137,6 +139,8 @@ class TimelineViewModel @AssistedInject constructor(
private val locationSharingServiceConnection: LocationSharingServiceConnection,
private val stopLiveLocationShareUseCase: StopLiveLocationShareUseCase,
private val redactLiveLocationShareEventUseCase: RedactLiveLocationShareEventUseCase,
private val cryptoConfig: CryptoConfig,
buildMeta: BuildMeta,
timelineFactory: TimelineFactory,
spaceStateHandler: SpaceStateHandler,
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState),
@ -150,7 +154,7 @@ class TimelineViewModel @AssistedInject constructor(
val timeline = timelineFactory.createTimeline(viewModelScope, room, eventId, initialState.rootThreadEventId)
// Same lifecycle than the ViewModel (survive to screen rotation)
val previewUrlRetriever = PreviewUrlRetriever(session, viewModelScope)
val previewUrlRetriever = PreviewUrlRetriever(session, viewModelScope, buildMeta)
// Slot to keep a pending action during permission request
var pendingAction: RoomDetailAction? = null
@ -205,7 +209,7 @@ class TimelineViewModel @AssistedInject constructor(
// Ensure to share the outbound session keys with all members
if (room.roomCryptoService().isEncrypted()) {
rawService.withElementWellKnown(viewModelScope, session.sessionParams) {
val strategy = it.getOutboundSessionKeySharingStrategyOrDefault()
val strategy = it.getOutboundSessionKeySharingStrategyOrDefault(cryptoConfig.fallbackKeySharingStrategy)
if (strategy == OutboundSessionKeySharingStrategy.WhenEnteringRoom) {
prepareForEncryption()
}
@ -688,7 +692,7 @@ class TimelineViewModel @AssistedInject constructor(
// Ensure outbound session keys
if (room.roomCryptoService().isEncrypted()) {
rawService.withElementWellKnown(viewModelScope, session.sessionParams) {
val strategy = it.getOutboundSessionKeySharingStrategyOrDefault()
val strategy = it.getOutboundSessionKeySharingStrategyOrDefault(cryptoConfig.fallbackKeySharingStrategy)
if (strategy == OutboundSessionKeySharingStrategy.WhenTyping && action.focused) {
// Should we add some rate limit here, or do it only once per model lifecycle?
prepareForEncryption()

View file

@ -20,7 +20,7 @@ import android.content.Context
import android.media.AudioAttributes
import android.media.MediaPlayer
import androidx.core.content.FileProvider
import im.vector.app.BuildConfig
import im.vector.app.core.resources.BuildMeta
import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker
import im.vector.app.features.voice.VoiceFailure
import im.vector.app.features.voice.VoiceRecorder
@ -43,6 +43,7 @@ import javax.inject.Inject
class AudioMessageHelper @Inject constructor(
private val context: Context,
private val playbackTracker: AudioMessagePlaybackTracker,
private val buildMeta: BuildMeta,
voiceRecorderProvider: VoiceRecorderProvider
) {
private var mediaPlayer: MediaPlayer? = null
@ -88,7 +89,7 @@ class AudioMessageHelper @Inject constructor(
try {
voiceMessageFile?.let {
val outputFileUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileProvider", it, "Voice message.${it.extension}")
val outputFileUri = FileProvider.getUriForFile(context, buildMeta.applicationId + ".fileProvider", it, "Voice message.${it.extension}")
return outputFileUri
.toMultiPickerAudioType(context)
?.apply {

View file

@ -41,7 +41,6 @@ sealed class MessageComposerAction : VectorViewModelAction {
object PauseRecordingVoiceMessage : MessageComposerAction()
data class PlayOrPauseVoicePlayback(val eventId: String, val messageAudioContent: MessageAudioContent) : MessageComposerAction()
object PlayOrPauseRecordingPlayback : MessageComposerAction()
data class EndAllVoiceActions(val deleteRecord: Boolean = true) : MessageComposerAction()
data class VoiceWaveformTouchedUp(val eventId: String, val duration: Int, val percentage: Float) : MessageComposerAction()
data class VoiceWaveformMovedTo(val eventId: String, val duration: Int, val percentage: Float) : MessageComposerAction()
data class AudioSeekBarMovedTo(val eventId: String, val duration: Int, val percentage: Float) : MessageComposerAction()

View file

@ -107,7 +107,6 @@ class MessageComposerViewModel @AssistedInject constructor(
is MessageComposerAction.PlayOrPauseVoicePlayback -> handlePlayOrPauseVoicePlayback(action)
MessageComposerAction.PauseRecordingVoiceMessage -> handlePauseRecordingVoiceMessage()
MessageComposerAction.PlayOrPauseRecordingPlayback -> handlePlayOrPauseRecordingPlayback()
is MessageComposerAction.EndAllVoiceActions -> handleEndAllVoiceActions(action.deleteRecord)
is MessageComposerAction.InitializeVoiceRecorder -> handleInitializeVoiceRecorder(action.attachmentData)
is MessageComposerAction.OnEntersBackground -> handleEntersBackground(action.composerText)
is MessageComposerAction.VoiceWaveformTouchedUp -> handleVoiceWaveformTouchedUp(action)
@ -887,7 +886,7 @@ class MessageComposerViewModel @AssistedInject constructor(
audioMessageHelper.startOrPauseRecordingPlayback()
}
private fun handleEndAllVoiceActions(deleteRecord: Boolean) {
fun endAllVoiceActions(deleteRecord: Boolean = true) {
audioMessageHelper.clearTracker()
audioMessageHelper.stopAllVoiceActions(deleteRecord)
}

View file

@ -0,0 +1,21 @@
/*
* 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.features.home.room.detail.composer.voice
data class VoiceMessageConfig(
val lengthLimitMs: Long
)

View file

@ -21,7 +21,6 @@ import android.util.AttributeSet
import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.hardware.vibrate
import im.vector.app.core.time.Clock
@ -57,6 +56,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
}
@Inject lateinit var clock: Clock
@Inject lateinit var voiceMessageConfig: VoiceMessageConfig
// We need to define views as lateinit var to be able to check if initialized for the bug fix on api 21 and 22.
@Suppress("UNNECESSARY_LATEINIT")
@ -202,7 +202,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
private fun onRecordingTick(isLocked: Boolean, milliseconds: Long) {
voiceMessageViews.renderRecordingTimer(isLocked, milliseconds / 1_000)
val timeDiffToRecordingLimit = BuildConfig.VOICE_MESSAGE_DURATION_LIMIT_MS - milliseconds
val timeDiffToRecordingLimit = voiceMessageConfig.lengthLimitMs - milliseconds
if (timeDiffToRecordingLimit <= 0) {
post {
callback.onRecordingLimitReached()

View file

@ -36,6 +36,8 @@ import im.vector.app.core.utils.DimensionConverter
import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
import im.vector.app.features.home.room.detail.timeline.style.granularRoundedCorners
import im.vector.app.features.location.MapLoadingErrorView
import im.vector.app.features.location.MapLoadingErrorViewState
abstract class AbsMessageLocationItem<H : AbsMessageLocationItem.Holder>(
@LayoutRes layoutId: Int = R.layout.item_timeline_event_base
@ -86,8 +88,10 @@ abstract class AbsMessageLocationItem<H : AbsMessageLocationItem.Holder>(
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
holder.staticMapPinImageView.setImageResource(R.drawable.ic_location_pin_failed)
holder.staticMapErrorTextView.isVisible = true
holder.staticMapPinImageView.setImageDrawable(null)
holder.staticMapLoadingErrorView.isVisible = true
val mapErrorViewState = MapLoadingErrorViewState(imageCornerTransformation)
holder.staticMapLoadingErrorView.render(mapErrorViewState)
holder.staticMapCopyrightTextView.isVisible = false
return false
}
@ -103,7 +107,7 @@ abstract class AbsMessageLocationItem<H : AbsMessageLocationItem.Holder>(
// we are not using Glide since it does not display it correctly when there is no user photo
holder.staticMapPinImageView.setImageDrawable(pinDrawable)
}
holder.staticMapErrorTextView.isVisible = false
holder.staticMapLoadingErrorView.isVisible = false
holder.staticMapCopyrightTextView.isVisible = true
return false
}
@ -115,7 +119,7 @@ abstract class AbsMessageLocationItem<H : AbsMessageLocationItem.Holder>(
abstract class Holder(@IdRes stubId: Int) : AbsMessageItem.Holder(stubId) {
val staticMapImageView by bind<ImageView>(R.id.staticMapImageView)
val staticMapPinImageView by bind<ImageView>(R.id.staticMapPinImageView)
val staticMapErrorTextView by bind<TextView>(R.id.staticMapErrorTextView)
val staticMapLoadingErrorView by bind<MapLoadingErrorView>(R.id.staticMapLoadingError)
val staticMapCopyrightTextView by bind<TextView>(R.id.staticMapCopyrightTextView)
}
}

View file

@ -16,7 +16,7 @@
package im.vector.app.features.home.room.detail.timeline.url
import im.vector.app.BuildConfig
import im.vector.app.core.resources.BuildMeta
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -27,7 +27,8 @@ import org.matrix.android.sdk.api.session.room.timeline.getLatestEventId
class PreviewUrlRetriever(
session: Session,
private val coroutineScope: CoroutineScope
private val coroutineScope: CoroutineScope,
private val buildMeta: BuildMeta,
) {
private val mediaService = session.mediaService()
@ -77,7 +78,7 @@ class PreviewUrlRetriever(
mediaService.getPreviewUrl(
url = urlToRetrieve,
timestamp = null,
cacheStrategy = if (BuildConfig.DEBUG) CacheStrategy.NoCache else CacheStrategy.TtlCache(CACHE_VALIDITY, false)
cacheStrategy = if (buildMeta.isDebug) CacheStrategy.NoCache else CacheStrategy.TtlCache(CACHE_VALIDITY, false)
)
}.fold(
{

View file

@ -22,5 +22,3 @@ const val DEFAULT_PIN_ID = "DEFAULT_PIN_ID"
const val INITIAL_MAP_ZOOM_IN_PREVIEW = 15.0
const val INITIAL_MAP_ZOOM_IN_TIMELINE = 17.0
const val MIN_TIME_TO_UPDATE_LOCATION_MILLIS = 2 * 1_000L // every 2 seconds
const val MIN_DISTANCE_TO_UPDATE_LOCATION_METERS = 10f

View file

@ -25,4 +25,5 @@ sealed class LocationSharingAction : VectorViewModelAction {
object ZoomToUserLocation : LocationSharingAction()
object LiveLocationSharingRequested : LocationSharingAction()
data class StartLiveLocationSharing(val durationMillis: Long) : LocationSharingAction()
object ShowMapLoadingError : LocationSharingAction()
}

View file

@ -23,6 +23,7 @@ import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.extensions.addFragment
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivityLocationSharingBinding
import im.vector.app.features.location.preview.LocationPreviewFragment
import kotlinx.parcelize.Parcelize
@Parcelize

View file

@ -0,0 +1,21 @@
/*
* 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.features.location
data class LocationSharingConfig(
val mapTilerKey: String,
)

View file

@ -24,6 +24,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.fragment.app.setFragmentResultListener
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.fragmentViewModel
@ -69,6 +70,7 @@ class LocationSharingFragment @Inject constructor(
private var mapView: WeakReference<MapView>? = null
private var hasRenderedUserAvatar = false
private var mapLoadingErrorListener: MapView.OnDidFailLoadingMapListener? = null
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLocationSharingBinding {
return FragmentLocationSharingBinding.inflate(inflater, container, false)
@ -87,6 +89,9 @@ class LocationSharingFragment @Inject constructor(
super.onViewCreated(view, savedInstanceState)
mapView = WeakReference(views.mapView)
mapLoadingErrorListener = MapView.OnDidFailLoadingMapListener {
viewModel.handle(LocationSharingAction.ShowMapLoadingError)
}.also { views.mapView.addOnDidFailLoadingMapListener(it) }
views.mapView.onCreate(savedInstanceState)
lifecycleScope.launchWhenCreated {
@ -112,6 +117,12 @@ class LocationSharingFragment @Inject constructor(
}
}
override fun onDestroyView() {
mapLoadingErrorListener?.let { mapView?.get()?.removeOnDidFailLoadingMapListener(it) }
mapLoadingErrorListener = null
super.onDestroyView()
}
override fun onResume() {
super.onResume()
views.mapView.onResume()
@ -256,20 +267,27 @@ class LocationSharingFragment @Inject constructor(
}
private fun updateMap(state: LocationSharingViewState) {
// first, update the options view
val options: Set<LocationSharingOption> = when (state.areTargetAndUserLocationEqual) {
true -> setOf(LocationSharingOption.USER_CURRENT, LocationSharingOption.USER_LIVE)
false -> setOf(LocationSharingOption.PINNED)
else -> emptySet()
}
views.shareLocationOptionsPicker.render(options)
if (state.loadingMapHasFailed) {
views.shareLocationOptionsPicker.render(emptySet())
views.shareLocationMapLoadingError.isVisible = true
} else {
// first, update the options view
val options: Set<LocationSharingOption> = when (state.areTargetAndUserLocationEqual) {
true -> setOf(LocationSharingOption.USER_CURRENT, LocationSharingOption.USER_LIVE)
false -> setOf(LocationSharingOption.PINNED)
else -> emptySet()
}
views.shareLocationOptionsPicker.render(options)
// then, update the map using the height of the options view after it has been rendered
views.shareLocationOptionsPicker.post {
val mapState = state
.toMapState()
.copy(logoMarginBottom = views.shareLocationOptionsPicker.height)
views.mapView.render(mapState)
// then, update the map using the height of the options view after it has been rendered
views.shareLocationOptionsPicker.post {
val mapState = state
.toMapState()
.copy(logoMarginBottom = views.shareLocationOptionsPicker.height)
views.mapView.render(mapState)
}
views.shareLocationMapLoadingError.isGone = true
}
}

View file

@ -152,6 +152,7 @@ class LocationSharingViewModel @AssistedInject constructor(
LocationSharingAction.ZoomToUserLocation -> handleZoomToUserLocationAction()
LocationSharingAction.LiveLocationSharingRequested -> handleLiveLocationSharingRequestedAction()
is LocationSharingAction.StartLiveLocationSharing -> handleStartLiveLocationSharingAction(action.durationMillis)
LocationSharingAction.ShowMapLoadingError -> handleShowMapLoadingError()
}
}
@ -211,6 +212,10 @@ class LocationSharingViewModel @AssistedInject constructor(
)
}
private fun handleShowMapLoadingError() {
setState { copy(loadingMapHasFailed = true) }
}
private fun onLocationUpdate(locationData: LocationData) {
Timber.d("onLocationUpdate()")
setState {

View file

@ -36,6 +36,7 @@ data class LocationSharingViewState(
val lastKnownUserLocation: LocationData? = null,
val locationTargetDrawable: Drawable? = null,
val canShareLiveLocation: Boolean = false,
val loadingMapHasFailed: Boolean = false
) : MavericksState {
constructor(locationSharingArgs: LocationSharingArgs) : this(

View file

@ -24,8 +24,8 @@ import androidx.annotation.RequiresPermission
import androidx.annotation.VisibleForTesting
import androidx.core.content.getSystemService
import androidx.core.location.LocationListenerCompat
import im.vector.app.BuildConfig
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.resources.BuildMeta
import im.vector.app.features.session.coroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
@ -36,11 +36,16 @@ import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.time.Duration.Companion.seconds
@VisibleForTesting
const val MIN_DISTANCE_TO_UPDATE_LOCATION_METERS = 10f
@Singleton
class LocationTracker @Inject constructor(
context: Context,
private val activeSessionHolder: ActiveSessionHolder
private val activeSessionHolder: ActiveSessionHolder,
private val buildMeta: BuildMeta,
) : LocationListenerCompat {
private val locationManager = context.getSystemService<LocationManager>()
@ -61,14 +66,25 @@ class LocationTracker @Inject constructor(
@VisibleForTesting
var hasLocationFromGPSProvider = false
private var firstLocationHandled = false
private val _locations = MutableSharedFlow<Location>(replay = 1)
@VisibleForTesting
val minDurationToUpdateLocationMillis = 5.seconds.inWholeMilliseconds
/**
* SharedFlow to collect location updates.
*/
val locations = _locations.asSharedFlow()
.onEach { Timber.d("new location emitted") }
.debounce(MIN_TIME_TO_UPDATE_LOCATION_MILLIS)
.debounce {
if (firstLocationHandled) {
minDurationToUpdateLocationMillis
} else {
firstLocationHandled = true
0
}
}
.onEach { Timber.d("new location emitted after debounce") }
.map { it.toLocationData() }
@ -95,7 +111,7 @@ class LocationTracker @Inject constructor(
locationManager.requestLocationUpdates(
provider,
MIN_TIME_TO_UPDATE_LOCATION_MILLIS,
minDurationToUpdateLocationMillis,
MIN_DISTANCE_TO_UPDATE_LOCATION_METERS,
this
)
@ -104,7 +120,7 @@ class LocationTracker @Inject constructor(
}
.maxByOrNull { location -> location.time }
?.let { latestKnownLocation ->
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
if (buildMeta.lowPrivacyLoggingEnabled) {
Timber.d("lastKnownLocation: $latestKnownLocation")
} else {
Timber.d("lastKnownLocation: ${latestKnownLocation.provider}")
@ -162,7 +178,7 @@ class LocationTracker @Inject constructor(
}
override fun onLocationChanged(location: Location) {
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
if (buildMeta.lowPrivacyLoggingEnabled) {
Timber.d("onLocationChanged: $location")
} else {
Timber.d("onLocationChanged: ${location.provider}")
@ -196,7 +212,7 @@ class LocationTracker @Inject constructor(
private fun notifyLocation(location: Location) {
activeSessionHolder.getSafeActiveSession()?.coroutineScope?.launch {
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
if (buildMeta.lowPrivacyLoggingEnabled) {
Timber.d("notify location: $location")
} else {
Timber.d("notify location: ${location.provider}")

View file

@ -0,0 +1,69 @@
/*
* 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.features.location
import android.content.Context
import android.content.res.TypedArray
import android.graphics.drawable.ColorDrawable
import android.util.AttributeSet
import android.view.LayoutInflater
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.res.use
import im.vector.app.R
import im.vector.app.core.glide.GlideApp
import im.vector.app.databinding.ViewMapLoadingErrorBinding
import im.vector.app.features.themes.ThemeUtils
/**
* Custom view to display an error when map fails to load.
*/
class MapLoadingErrorView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
private val binding = ViewMapLoadingErrorBinding.inflate(
LayoutInflater.from(context),
this
)
init {
context.obtainStyledAttributes(
attrs,
R.styleable.MapLoadingErrorView,
0,
0
).use {
setErrorDescription(it)
}
}
private fun setErrorDescription(typedArray: TypedArray) {
val description = typedArray.getString(R.styleable.MapLoadingErrorView_mapErrorDescription)
if (description.isNullOrEmpty()) {
binding.mapLoadingErrorDescription.setText(R.string.location_share_loading_map_error)
} else {
binding.mapLoadingErrorDescription.text = description
}
}
fun render(mapLoadingErrorViewState: MapLoadingErrorViewState) {
GlideApp.with(binding.mapLoadingErrorBackground)
.load(ColorDrawable(ThemeUtils.getColor(context, R.attr.vctr_system)))
.transform(mapLoadingErrorViewState.backgroundTransformation)
.into(binding.mapLoadingErrorBackground)
}
}

View file

@ -0,0 +1,21 @@
/*
* 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.features.location
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
data class MapLoadingErrorViewState(val backgroundTransformation: BitmapTransformation)

View file

@ -16,7 +16,6 @@
package im.vector.app.features.location
import im.vector.app.BuildConfig
import im.vector.app.features.raw.wellknown.getElementWellknown
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.raw.RawService
@ -25,9 +24,10 @@ import javax.inject.Inject
class UrlMapProvider @Inject constructor(
private val session: Session,
private val rawService: RawService
private val rawService: RawService,
locationSharingConfig: LocationSharingConfig,
) {
private val keyParam = "?key=${BuildConfig.mapTilerKey}"
private val keyParam = "?key=${locationSharingConfig.mapTilerKey}"
private val fallbackMapUrl = buildString {
append(MAP_BASE_URL)

View file

@ -22,4 +22,5 @@ sealed class LiveLocationMapAction : VectorViewModelAction {
data class AddMapSymbol(val key: String, val value: Long) : LiveLocationMapAction()
data class RemoveMapSymbol(val key: String) : LiveLocationMapAction()
object StopSharing : LiveLocationMapAction()
object ShowMapLoadingError : LiveLocationMapAction()
}

View file

@ -71,11 +71,13 @@ class LiveLocationMapViewFragment @Inject constructor() : VectorBaseFragment<Fra
private val viewModel: LiveLocationMapViewModel by fragmentViewModel()
private var mapboxMap: WeakReference<MapboxMap>? = null
private var mapView: MapView? = null
private var symbolManager: SymbolManager? = null
private var mapStyle: Style? = null
private val pendingLiveLocations = mutableListOf<UserLiveLocationViewState>()
private var isMapFirstUpdate = true
private var onSymbolClickListener: OnSymbolClickListener? = null
private var mapLoadingErrorListener: MapView.OnDidFailLoadingMapListener? = null
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLiveLocationMapViewBinding {
return FragmentLiveLocationMapViewBinding.inflate(layoutInflater, container, false)
@ -84,6 +86,7 @@ class LiveLocationMapViewFragment @Inject constructor() : VectorBaseFragment<Fra
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
observeViewEvents()
setupMap()
views.liveLocationBottomSheetRecyclerView.configureWith(bottomSheetController, hasFixedSize = false, disableItemAnimation = true)
@ -106,22 +109,24 @@ class LiveLocationMapViewFragment @Inject constructor() : VectorBaseFragment<Fra
}
}
override fun onResume() {
super.onResume()
setupMap()
}
override fun onDestroyView() {
onSymbolClickListener?.let { symbolManager?.removeClickListener(it) }
symbolManager?.onDestroy()
bottomSheetController.callback = null
views.liveLocationBottomSheetRecyclerView.cleanup()
mapLoadingErrorListener?.let { mapView?.removeOnDidFailLoadingMapListener(it) }
mapLoadingErrorListener = null
mapView = null
super.onDestroyView()
}
private fun setupMap() {
val mapFragment = getOrCreateSupportMapFragment()
mapFragment.getMapAsync { mapboxMap ->
(mapFragment.view as? MapView)?.let {
mapView = it
listenMapLoadingError(it)
}
lifecycleScope.launch {
mapboxMap.setStyle(urlMapProvider.getMapUrl()) { style ->
mapStyle = style
@ -141,6 +146,12 @@ class LiveLocationMapViewFragment @Inject constructor() : VectorBaseFragment<Fra
}
}
private fun listenMapLoadingError(mapView: MapView) {
mapLoadingErrorListener = MapView.OnDidFailLoadingMapListener {
viewModel.handle(LiveLocationMapAction.ShowMapLoadingError)
}.also { mapView.addOnDidFailLoadingMapListener(it) }
}
private fun onSymbolClicked(symbol: Symbol?) {
symbol?.let {
mapboxMap
@ -173,7 +184,12 @@ class LiveLocationMapViewFragment @Inject constructor() : VectorBaseFragment<Fra
}
override fun invalidate() = withState(viewModel) { viewState ->
updateMap(viewState.userLocations)
if (viewState.loadingMapHasFailed) {
views.mapPreviewLoadingError.isVisible = true
} else {
views.mapPreviewLoadingError.isGone = true
updateMap(viewState.userLocations)
}
updateUserListBottomSheet(viewState.userLocations)
}

View file

@ -61,6 +61,7 @@ class LiveLocationMapViewModel @AssistedInject constructor(
is LiveLocationMapAction.AddMapSymbol -> handleAddMapSymbol(action)
is LiveLocationMapAction.RemoveMapSymbol -> handleRemoveMapSymbol(action)
LiveLocationMapAction.StopSharing -> handleStopSharing()
LiveLocationMapAction.ShowMapLoadingError -> handleShowMapLoadingError()
}
}
@ -87,6 +88,10 @@ class LiveLocationMapViewModel @AssistedInject constructor(
}
}
private fun handleShowMapLoadingError() {
setState { copy(loadingMapHasFailed = true) }
}
override fun onLocationServiceRunning(roomIds: Set<String>) {
// NOOP
}

View file

@ -27,7 +27,8 @@ data class LiveLocationMapViewState(
/**
* Map to keep track of symbol ids associated to each user Id.
*/
val mapSymbolIds: Map<String, Long> = emptyMap()
val mapSymbolIds: Map<String, Long> = emptyMap(),
val loadingMapHasFailed: Boolean = false,
) : MavericksState {
constructor(liveLocationMapViewArgs: LiveLocationMapViewArgs) : this(
roomId = liveLocationMapViewArgs.roomId

View file

@ -31,6 +31,7 @@ import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.home.room.detail.arguments.TimelineArgs
import im.vector.app.features.location.live.map.LiveLocationMapViewActivity
import im.vector.app.features.location.live.map.LiveLocationMapViewArgs
import im.vector.app.features.notifications.NotificationActionIds
import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.themes.ThemeUtils
import javax.inject.Inject
@ -41,6 +42,7 @@ class LiveLocationNotificationBuilder @Inject constructor(
private val context: Context,
private val stringProvider: StringProvider,
private val clock: Clock,
private val actionIds: NotificationActionIds,
) {
/**
@ -66,7 +68,7 @@ class LiveLocationNotificationBuilder @Inject constructor(
liveLocationMapViewArgs = LiveLocationMapViewArgs(roomId = roomId),
firstStartMainActivity = true
)
mapIntent.action = NotificationUtils.TAP_TO_VIEW_ACTION
mapIntent.action = actionIds.tapToView
// pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that
mapIntent.data = createIgnoredUri("openLiveLocationMap?$roomId")

View file

@ -0,0 +1,23 @@
/*
* 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.features.location.preview
import im.vector.app.core.platform.VectorViewModelAction
sealed class LocationPreviewAction : VectorViewModelAction {
object ShowMapLoadingError : LocationPreviewAction()
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021 New Vector Ltd
* 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.
@ -14,15 +14,18 @@
* limitations under the License.
*/
package im.vector.app.features.location
package im.vector.app.features.location.preview
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import com.mapbox.mapboxsdk.maps.MapView
import im.vector.app.R
import im.vector.app.core.platform.VectorBaseFragment
@ -30,6 +33,10 @@ import im.vector.app.core.platform.VectorMenuProvider
import im.vector.app.core.utils.openLocation
import im.vector.app.databinding.FragmentLocationPreviewBinding
import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider
import im.vector.app.features.location.DEFAULT_PIN_ID
import im.vector.app.features.location.LocationSharingArgs
import im.vector.app.features.location.MapState
import im.vector.app.features.location.UrlMapProvider
import java.lang.ref.WeakReference
import javax.inject.Inject
@ -44,9 +51,13 @@ class LocationPreviewFragment @Inject constructor(
private val args: LocationSharingArgs by args()
private val viewModel: LocationPreviewViewModel by fragmentViewModel()
// Keep a ref to handle properly the onDestroy callback
private var mapView: WeakReference<MapView>? = null
private var mapLoadingErrorListener: MapView.OnDidFailLoadingMapListener? = null
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLocationPreviewBinding {
return FragmentLocationPreviewBinding.inflate(layoutInflater, container, false)
}
@ -55,6 +66,9 @@ class LocationPreviewFragment @Inject constructor(
super.onViewCreated(view, savedInstanceState)
mapView = WeakReference(views.mapView)
mapLoadingErrorListener = MapView.OnDidFailLoadingMapListener {
viewModel.handle(LocationPreviewAction.ShowMapLoadingError)
}.also { views.mapView.addOnDidFailLoadingMapListener(it) }
views.mapView.onCreate(savedInstanceState)
lifecycleScope.launchWhenCreated {
@ -63,6 +77,12 @@ class LocationPreviewFragment @Inject constructor(
}
}
override fun onDestroyView() {
mapLoadingErrorListener?.let { mapView?.get()?.removeOnDidFailLoadingMapListener(it) }
mapLoadingErrorListener = null
super.onDestroyView()
}
override fun onResume() {
super.onResume()
views.mapView.onResume()
@ -99,6 +119,10 @@ class LocationPreviewFragment @Inject constructor(
super.onDestroy()
}
override fun invalidate() = withState(viewModel) { state ->
views.mapPreviewLoadingError.isVisible = state.loadingMapHasFailed
}
override fun getMenuRes() = R.menu.menu_location_preview
override fun handleMenuItemSelected(item: MenuItem): Boolean {

View file

@ -0,0 +1,48 @@
/*
* 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.features.location.preview
import com.airbnb.mvrx.MavericksViewModelFactory
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
class LocationPreviewViewModel @AssistedInject constructor(
@Assisted private val initialState: LocationPreviewViewState,
) : VectorViewModel<LocationPreviewViewState, LocationPreviewAction, EmptyViewEvents>(initialState) {
@AssistedFactory
interface Factory : MavericksAssistedViewModelFactory<LocationPreviewViewModel, LocationPreviewViewState> {
override fun create(initialState: LocationPreviewViewState): LocationPreviewViewModel
}
companion object : MavericksViewModelFactory<LocationPreviewViewModel, LocationPreviewViewState> by hiltMavericksViewModelFactory()
override fun handle(action: LocationPreviewAction) {
when (action) {
LocationPreviewAction.ShowMapLoadingError -> handleShowMapLoadingError()
}
}
private fun handleShowMapLoadingError() {
setState { copy(loadingMapHasFailed = true) }
}
}

View file

@ -14,14 +14,10 @@
* limitations under the License.
*/
package im.vector.app.config
package im.vector.app.features.location.preview
import im.vector.app.BuildConfig
import im.vector.app.features.analytics.AnalyticsConfig
import com.airbnb.mvrx.MavericksState
val analyticsConfig: AnalyticsConfig = object : AnalyticsConfig {
override val isEnabled = BuildConfig.APPLICATION_ID == "im.vector.app.debug"
override val postHogHost = "https://posthog.element.dev"
override val postHogApiKey = "phc_VtA1L35nw3aeAtHIx1ayrGdzGkss7k1xINeXcoIQzXN"
override val policyLink = "https://element.io/cookie-policy"
}
data class LocationPreviewViewState(
val loadingMapHasFailed: Boolean = false
) : MavericksState

View file

@ -27,9 +27,9 @@ import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.google.android.material.textfield.TextInputLayout
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.resources.BuildMeta
import im.vector.app.core.utils.ensureProtocol
import im.vector.app.core.utils.openUrlInChromeCustomTab
import im.vector.app.databinding.FragmentLoginServerUrlFormBinding
@ -43,7 +43,9 @@ import javax.inject.Inject
/**
* In this screen, the user is prompted to enter a homeserver url.
*/
class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment<FragmentLoginServerUrlFormBinding>() {
class LoginServerUrlFormFragment @Inject constructor(
private val buildMeta: BuildMeta,
) : AbstractLoginFragment<FragmentLoginServerUrlFormBinding>() {
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginServerUrlFormBinding {
return FragmentLoginServerUrlFormBinding.inflate(inflater, container, false)
@ -99,7 +101,7 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment<F
views.loginServerUrlFormNotice.text = getString(R.string.login_server_url_form_common_notice)
}
}
val completions = state.knownCustomHomeServersUrls + if (BuildConfig.DEBUG) listOf("http://10.0.2.2:8080") else emptyList()
val completions = state.knownCustomHomeServersUrls + if (buildMeta.isDebug) listOf("http://10.0.2.2:8080") else emptyList()
views.loginServerUrlFormHomeServerUrl.setAdapter(
ArrayAdapter(
requireContext(),

View file

@ -23,8 +23,8 @@ import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.resources.BuildMeta
import im.vector.app.databinding.FragmentLoginSplashBinding
import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.settings.VectorPreferences
@ -36,7 +36,8 @@ import javax.inject.Inject
* In this screen, the user is viewing an introduction to what he can do with this application.
*/
class LoginSplashFragment @Inject constructor(
private val vectorPreferences: VectorPreferences
private val vectorPreferences: VectorPreferences,
private val buildMeta: BuildMeta,
) : AbstractLoginFragment<FragmentLoginSplashBinding>() {
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginSplashBinding {
@ -57,12 +58,12 @@ class LoginSplashFragment @Inject constructor(
private fun setupViews() {
views.loginSplashSubmit.debouncedClicks { getStarted() }
if (BuildConfig.DEBUG || vectorPreferences.developerMode()) {
if (buildMeta.isDebug || vectorPreferences.developerMode()) {
views.loginSplashVersion.isVisible = true
@SuppressLint("SetTextI18n")
views.loginSplashVersion.text = "Version : ${BuildConfig.VERSION_NAME}\n" +
"Branch: ${BuildConfig.GIT_BRANCH_NAME}\n" +
"Build: ${BuildConfig.BUILD_NUMBER}"
views.loginSplashVersion.text = "Version : ${buildMeta.versionName}\n" +
"Branch: ${buildMeta.gitBranchName}\n" +
"Build: ${buildMeta.buildNumber}"
views.loginSplashVersion.debouncedClicks { navigator.openDebug(requireContext()) }
}
}

View file

@ -26,9 +26,9 @@ import android.widget.ArrayAdapter
import androidx.core.view.isInvisible
import androidx.lifecycle.lifecycleScope
import com.google.android.material.textfield.TextInputLayout
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.resources.BuildMeta
import im.vector.app.core.utils.ensureProtocol
import im.vector.app.databinding.FragmentLoginServerUrlForm2Binding
import kotlinx.coroutines.flow.launchIn
@ -43,7 +43,9 @@ import javax.net.ssl.HttpsURLConnection
/**
* In this screen, the user is prompted to enter a homeserver url.
*/
class LoginServerUrlFormFragment2 @Inject constructor() : AbstractLoginFragment2<FragmentLoginServerUrlForm2Binding>() {
class LoginServerUrlFormFragment2 @Inject constructor(
private val buildMeta: BuildMeta,
) : AbstractLoginFragment2<FragmentLoginServerUrlForm2Binding>() {
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginServerUrlForm2Binding {
return FragmentLoginServerUrlForm2Binding.inflate(inflater, container, false)
@ -80,7 +82,7 @@ class LoginServerUrlFormFragment2 @Inject constructor() : AbstractLoginFragment2
}
private fun setupUi(state: LoginViewState2) {
val completions = state.knownCustomHomeServersUrls + if (BuildConfig.DEBUG) listOf("http://10.0.2.2:8080") else emptyList()
val completions = state.knownCustomHomeServersUrls + if (buildMeta.isDebug) listOf("http://10.0.2.2:8080") else emptyList()
views.loginServerUrlFormHomeServerUrl.setAdapter(
ArrayAdapter(
requireContext(),

View file

@ -22,7 +22,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import im.vector.app.BuildConfig
import im.vector.app.core.resources.BuildMeta
import im.vector.app.databinding.FragmentLoginSplash2Binding
import im.vector.app.features.settings.VectorPreferences
import javax.inject.Inject
@ -32,7 +32,8 @@ import javax.inject.Inject
* This is the new splash screen.
*/
class LoginSplashSignUpSignInSelectionFragment2 @Inject constructor(
private val vectorPreferences: VectorPreferences
private val vectorPreferences: VectorPreferences,
private val buildMeta: BuildMeta,
) : AbstractLoginFragment2<FragmentLoginSplash2Binding>() {
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginSplash2Binding {
@ -49,12 +50,12 @@ class LoginSplashSignUpSignInSelectionFragment2 @Inject constructor(
views.loginSignupSigninSignUp.setOnClickListener { signUp() }
views.loginSignupSigninSignIn.setOnClickListener { signIn() }
if (BuildConfig.DEBUG || vectorPreferences.developerMode()) {
if (buildMeta.isDebug || vectorPreferences.developerMode()) {
views.loginSplashVersion.isVisible = true
@SuppressLint("SetTextI18n")
views.loginSplashVersion.text = "Version : ${BuildConfig.VERSION_NAME}\n" +
"Branch: ${BuildConfig.GIT_BRANCH_NAME}\n" +
"Build: ${BuildConfig.BUILD_NUMBER}"
views.loginSplashVersion.text = "Version : ${buildMeta.versionName}\n" +
"Branch: ${buildMeta.gitBranchName}\n" +
"Build: ${buildMeta.buildNumber}"
views.loginSplashVersion.debouncedClicks { navigator.openDebug(requireContext()) }
}
}

View file

@ -33,10 +33,10 @@ import androidx.core.view.ViewCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.R
import im.vector.app.SpaceStateHandler
import im.vector.app.config.OnboardingVariant
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.error.fatalError
import im.vector.app.features.VectorFeatures
import im.vector.app.features.VectorFeatures.OnboardingVariant
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.extensions.toAnalyticsViewRoom
import im.vector.app.features.analytics.plan.ViewRoom

View file

@ -16,9 +16,9 @@
package im.vector.app.features.notifications
import android.net.Uri
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.extensions.takeAs
import im.vector.app.core.resources.BuildMeta
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.time.Clock
import im.vector.app.features.displayname.getBestName
@ -62,6 +62,7 @@ class NotifiableEventResolver @Inject constructor(
private val noticeEventFormatter: NoticeEventFormatter,
private val displayableEventFormatter: DisplayableEventFormatter,
private val clock: Clock,
private val buildMeta: BuildMeta,
) {
// private val eventDisplay = RiotEventDisplay(context)
@ -264,7 +265,7 @@ class NotifiableEventResolver @Inject constructor(
)
} else {
Timber.e("## unsupported notifiable event for eventId [${event.eventId}]")
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
if (buildMeta.lowPrivacyLoggingEnabled) {
Timber.e("## unsupported notifiable event for event [$event]")
}
// TODO generic handling?

View file

@ -0,0 +1,41 @@
/*
* 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.features.notifications
import im.vector.app.core.resources.BuildMeta
import javax.inject.Inject
/**
* Util class for creating notifications.
* Note: Cannot inject ColorProvider in the constructor, because it requires an Activity
*/
data class NotificationActionIds @Inject constructor(
private val buildMeta: BuildMeta,
) {
val join = "${buildMeta.applicationId}.NotificationActions.JOIN_ACTION"
val reject = "${buildMeta.applicationId}.NotificationActions.REJECT_ACTION"
val quickLaunch = "${buildMeta.applicationId}.NotificationActions.QUICK_LAUNCH_ACTION"
val markRoomRead = "${buildMeta.applicationId}.NotificationActions.MARK_ROOM_READ_ACTION"
val smartReply = "${buildMeta.applicationId}.NotificationActions.SMART_REPLY_ACTION"
val dismissSummary = "${buildMeta.applicationId}.NotificationActions.DISMISS_SUMMARY_ACTION"
val dismissRoom = "${buildMeta.applicationId}.NotificationActions.DISMISS_ROOM_NOTIF_ACTION"
val tapToView = "${buildMeta.applicationId}.NotificationActions.TAP_TO_VIEW_ACTION"
val diagnostic = "${buildMeta.applicationId}.NotificationActions.DIAGNOSTIC"
val push = "${buildMeta.applicationId}.PUSH"
}

View file

@ -48,31 +48,32 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var analyticsTracker: AnalyticsTracker
@Inject lateinit var clock: Clock
@Inject lateinit var actionIds: NotificationActionIds
override fun onReceive(context: Context?, intent: Intent?) {
if (intent == null || context == null) return
Timber.v("NotificationBroadcastReceiver received : $intent")
when (intent.action) {
NotificationUtils.SMART_REPLY_ACTION ->
actionIds.smartReply ->
handleSmartReply(intent, context)
NotificationUtils.DISMISS_ROOM_NOTIF_ACTION ->
actionIds.dismissRoom ->
intent.getStringExtra(KEY_ROOM_ID)?.let { roomId ->
notificationDrawerManager.updateEvents { it.clearMessagesForRoom(roomId) }
}
NotificationUtils.DISMISS_SUMMARY_ACTION ->
actionIds.dismissSummary ->
notificationDrawerManager.clearAllEvents()
NotificationUtils.MARK_ROOM_READ_ACTION ->
actionIds.markRoomRead ->
intent.getStringExtra(KEY_ROOM_ID)?.let { roomId ->
notificationDrawerManager.updateEvents { it.clearMessagesForRoom(roomId) }
handleMarkAsRead(roomId)
}
NotificationUtils.JOIN_ACTION -> {
actionIds.join -> {
intent.getStringExtra(KEY_ROOM_ID)?.let { roomId ->
notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(roomId) }
handleJoinRoom(roomId)
}
}
NotificationUtils.REJECT_ACTION -> {
actionIds.reject -> {
intent.getStringExtra(KEY_ROOM_ID)?.let { roomId ->
notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(roomId) }
handleRejectRoom(roomId)

View file

@ -20,8 +20,8 @@ import android.os.Handler
import android.os.HandlerThread
import androidx.annotation.WorkerThread
import im.vector.app.ActiveSessionDataSource
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.resources.BuildMeta
import im.vector.app.core.utils.FirstThrottler
import im.vector.app.features.displayname.getBestName
import im.vector.app.features.settings.VectorPreferences
@ -46,7 +46,8 @@ class NotificationDrawerManager @Inject constructor(
private val activeSessionDataSource: ActiveSessionDataSource,
private val notifiableEventProcessor: NotifiableEventProcessor,
private val notificationRenderer: NotificationRenderer,
private val notificationEventPersistence: NotificationEventPersistence
private val notificationEventPersistence: NotificationEventPersistence,
private val buildMeta: BuildMeta,
) {
private val handlerThread: HandlerThread = HandlerThread("NotificationDrawerManager", Thread.MIN_PRIORITY)
@ -92,7 +93,7 @@ class NotificationDrawerManager @Inject constructor(
}
// If we support multi session, event list should be per userId
// Currently only manage single session
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
if (buildMeta.lowPrivacyLoggingEnabled) {
Timber.d("onNotifiableEventReceived(): $notifiableEvent")
} else {
Timber.d("onNotifiableEventReceived(): is push: ${notifiableEvent.canBeReplaced}")

View file

@ -45,7 +45,6 @@ import androidx.core.content.getSystemService
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.IconCompat
import androidx.fragment.app.Fragment
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.extensions.createIgnoredUri
import im.vector.app.core.platform.PendingIntentCompat
@ -69,16 +68,13 @@ import javax.inject.Inject
import javax.inject.Singleton
import kotlin.random.Random
/**
* Util class for creating notifications.
* Note: Cannot inject ColorProvider in the constructor, because it requires an Activity
*/
@Singleton
class NotificationUtils @Inject constructor(
private val context: Context,
private val stringProvider: StringProvider,
private val vectorPreferences: VectorPreferences,
private val clock: Clock,
private val actionIds: NotificationActionIds,
) {
companion object {
@ -94,21 +90,6 @@ class NotificationUtils @Inject constructor(
*/
const val NOTIFICATION_ID_FOREGROUND_SERVICE = 61
/* ==========================================================================================
* IDs for actions
* ========================================================================================== */
const val JOIN_ACTION = "${BuildConfig.APPLICATION_ID}.NotificationActions.JOIN_ACTION"
const val REJECT_ACTION = "${BuildConfig.APPLICATION_ID}.NotificationActions.REJECT_ACTION"
private const val QUICK_LAUNCH_ACTION = "${BuildConfig.APPLICATION_ID}.NotificationActions.QUICK_LAUNCH_ACTION"
const val MARK_ROOM_READ_ACTION = "${BuildConfig.APPLICATION_ID}.NotificationActions.MARK_ROOM_READ_ACTION"
const val SMART_REPLY_ACTION = "${BuildConfig.APPLICATION_ID}.NotificationActions.SMART_REPLY_ACTION"
const val DISMISS_SUMMARY_ACTION = "${BuildConfig.APPLICATION_ID}.NotificationActions.DISMISS_SUMMARY_ACTION"
const val DISMISS_ROOM_NOTIF_ACTION = "${BuildConfig.APPLICATION_ID}.NotificationActions.DISMISS_ROOM_NOTIF_ACTION"
const val TAP_TO_VIEW_ACTION = "${BuildConfig.APPLICATION_ID}.NotificationActions.TAP_TO_VIEW_ACTION"
const val DIAGNOSTIC_ACTION = "${BuildConfig.APPLICATION_ID}.NotificationActions.DIAGNOSTIC"
const val PUSH_ACTION = "${BuildConfig.APPLICATION_ID}.PUSH"
/* ==========================================================================================
* IDs for channels
* ========================================================================================== */
@ -651,7 +632,7 @@ class NotificationUtils @Inject constructor(
// Add actions and notification intents
// Mark room as read
val markRoomReadIntent = Intent(context, NotificationBroadcastReceiver::class.java)
markRoomReadIntent.action = MARK_ROOM_READ_ACTION
markRoomReadIntent.action = actionIds.markRoomRead
markRoomReadIntent.data = createIgnoredUri(roomInfo.roomId)
markRoomReadIntent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomInfo.roomId)
val markRoomReadPendingIntent = PendingIntent.getBroadcast(
@ -698,7 +679,7 @@ class NotificationUtils @Inject constructor(
val intent = Intent(context, NotificationBroadcastReceiver::class.java)
intent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomInfo.roomId)
intent.action = DISMISS_ROOM_NOTIF_ACTION
intent.action = actionIds.dismissRoom
val pendingIntent = PendingIntent.getBroadcast(
context.applicationContext,
clock.epochMillis().toInt(),
@ -733,7 +714,7 @@ class NotificationUtils @Inject constructor(
val roomId = inviteNotifiableEvent.roomId
// offer to type a quick reject button
val rejectIntent = Intent(context, NotificationBroadcastReceiver::class.java)
rejectIntent.action = REJECT_ACTION
rejectIntent.action = actionIds.reject
rejectIntent.data = createIgnoredUri("$roomId&$matrixId")
rejectIntent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomId)
val rejectIntentPendingIntent = PendingIntent.getBroadcast(
@ -751,7 +732,7 @@ class NotificationUtils @Inject constructor(
// offer to type a quick accept button
val joinIntent = Intent(context, NotificationBroadcastReceiver::class.java)
joinIntent.action = JOIN_ACTION
joinIntent.action = actionIds.join
joinIntent.data = createIgnoredUri("$roomId&$matrixId")
joinIntent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomId)
val joinIntentPendingIntent = PendingIntent.getBroadcast(
@ -834,7 +815,7 @@ class NotificationUtils @Inject constructor(
private fun buildOpenRoomIntent(roomId: String): PendingIntent? {
val roomIntentTap = RoomDetailActivity.newIntent(context, TimelineArgs(roomId = roomId, switchToParentSpace = true), true)
roomIntentTap.action = TAP_TO_VIEW_ACTION
roomIntentTap.action = actionIds.tapToView
// pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that
roomIntentTap.data = createIgnoredUri("openRoom?$roomId")
@ -872,7 +853,7 @@ class NotificationUtils @Inject constructor(
val intent: Intent
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent = Intent(context, NotificationBroadcastReceiver::class.java)
intent.action = SMART_REPLY_ACTION
intent.action = actionIds.smartReply
intent.data = createIgnoredUri(roomId)
intent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomId)
return PendingIntent.getBroadcast(
@ -948,7 +929,7 @@ class NotificationUtils @Inject constructor(
private fun getDismissSummaryPendingIntent(): PendingIntent {
val intent = Intent(context, NotificationBroadcastReceiver::class.java)
intent.action = DISMISS_SUMMARY_ACTION
intent.action = actionIds.dismissSummary
intent.data = createIgnoredUri("deleteSummary")
return PendingIntent.getBroadcast(
context.applicationContext,
@ -987,7 +968,7 @@ class NotificationUtils @Inject constructor(
fun displayDiagnosticNotification() {
val testActionIntent = Intent(context, TestNotificationReceiver::class.java)
testActionIntent.action = DIAGNOSTIC_ACTION
testActionIntent.action = actionIds.diagnostic
val testPendingIntent = PendingIntent.getBroadcast(
context,
0,

View file

@ -16,6 +16,7 @@
package im.vector.app.features.onboarding
import im.vector.app.config.OnboardingVariant
import im.vector.app.core.platform.ScreenOrientationLocker
import im.vector.app.databinding.ActivityLoginBinding
import im.vector.app.features.VectorFeatures
@ -34,8 +35,8 @@ class OnboardingVariantFactory @Inject constructor(
onboardingViewModel: Lazy<OnboardingViewModel>,
loginViewModel2: Lazy<LoginViewModel2>
) = when (vectorFeatures.onboardingVariant()) {
VectorFeatures.OnboardingVariant.LEGACY -> error("Legacy is not supported by the FTUE")
VectorFeatures.OnboardingVariant.FTUE_AUTH -> FtueAuthVariant(
OnboardingVariant.LEGACY -> error("Legacy is not supported by the FTUE")
OnboardingVariant.FTUE_AUTH -> FtueAuthVariant(
views = views,
onboardingViewModel = onboardingViewModel.value,
activity = activity,
@ -43,7 +44,7 @@ class OnboardingVariantFactory @Inject constructor(
vectorFeatures = vectorFeatures,
orientationLocker = orientationLocker
)
VectorFeatures.OnboardingVariant.LOGIN_2 -> Login2Variant(
OnboardingVariant.LOGIN_2 -> Login2Variant(
views = views,
loginViewModel = loginViewModel2.value,
activity = activity,

View file

@ -32,7 +32,6 @@ import im.vector.app.core.extensions.isMatrixId
import im.vector.app.core.extensions.toReducedUrl
import im.vector.app.core.extensions.vectorStore
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.BuildMeta
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.ensureProtocol
import im.vector.app.core.utils.ensureTrailingSlash
@ -63,6 +62,7 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationAvailability
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
import org.matrix.android.sdk.api.failure.isHomeserverUnavailable
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider
import timber.log.Timber
import java.util.UUID
import java.util.concurrent.CancellationException
@ -86,7 +86,7 @@ class OnboardingViewModel @AssistedInject constructor(
private val startAuthenticationFlowUseCase: StartAuthenticationFlowUseCase,
private val vectorOverrides: VectorOverrides,
private val registrationActionHandler: RegistrationActionHandler,
private val buildMeta: BuildMeta,
private val sdkIntProvider: BuildVersionSdkIntProvider,
) : VectorViewModel<OnboardingViewState, OnboardingAction, OnboardingViewEvents>(initialState) {
@AssistedFactory
@ -708,7 +708,7 @@ class OnboardingViewModel @AssistedInject constructor(
private fun onAuthenticationStartError(error: Throwable, trigger: OnboardingAction.HomeServerChange) {
when {
error.isHomeserverUnavailable() && applicationContext.inferNoConnectivity(buildMeta) -> _viewEvents.post(
error.isHomeserverUnavailable() && applicationContext.inferNoConnectivity(sdkIntProvider) -> _viewEvents.post(
OnboardingViewEvents.Failure(error)
)
deeplinkUrlIsUnavailable(error, trigger) -> _viewEvents.post(

View file

@ -63,7 +63,9 @@ class FtueAuthCombinedLoginFragment @Inject constructor(
views.loginRoot.realignPercentagesToParent()
views.editServerButton.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.EditServerSelection)) }
views.loginPasswordInput.setOnImeDoneListener { submit() }
views.loginInput.setOnFocusLostListener { viewModel.handle(OnboardingAction.UserNameEnteredAction.Login(views.loginInput.content())) }
views.loginInput.setOnFocusLostListener(viewLifecycleOwner) {
viewModel.handle(OnboardingAction.UserNameEnteredAction.Login(views.loginInput.content()))
}
views.loginForgotPassword.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnForgetPasswordClicked)) }
}

View file

@ -86,7 +86,7 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu
views.createAccountEntryFooter.text = ""
}
views.createAccountInput.setOnFocusLostListener {
views.createAccountInput.setOnFocusLostListener(viewLifecycleOwner) {
viewModel.handle(OnboardingAction.UserNameEnteredAction.Registration(views.createAccountInput.content()))
}
}

View file

@ -27,9 +27,9 @@ import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.google.android.material.textfield.TextInputLayout
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.resources.BuildMeta
import im.vector.app.core.utils.ensureProtocol
import im.vector.app.core.utils.openUrlInChromeCustomTab
import im.vector.app.databinding.FragmentLoginServerUrlFormBinding
@ -47,7 +47,9 @@ import javax.inject.Inject
/**
* In this screen, the user is prompted to enter a homeserver url.
*/
class FtueAuthServerUrlFormFragment @Inject constructor() : AbstractFtueAuthFragment<FragmentLoginServerUrlFormBinding>() {
class FtueAuthServerUrlFormFragment @Inject constructor(
private val buildMeta: BuildMeta,
) : AbstractFtueAuthFragment<FragmentLoginServerUrlFormBinding>() {
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginServerUrlFormBinding {
return FragmentLoginServerUrlFormBinding.inflate(inflater, container, false)
@ -103,7 +105,7 @@ class FtueAuthServerUrlFormFragment @Inject constructor() : AbstractFtueAuthFrag
views.loginServerUrlFormNotice.text = getString(R.string.login_server_url_form_common_notice)
}
}
val completions = state.knownCustomHomeServersUrls + if (BuildConfig.DEBUG) listOf("http://10.0.2.2:8080") else emptyList()
val completions = state.knownCustomHomeServersUrls + if (buildMeta.isDebug) listOf("http://10.0.2.2:8080") else emptyList()
views.loginServerUrlFormHomeServerUrl.setAdapter(
ArrayAdapter(
requireContext(),

View file

@ -27,10 +27,10 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.tabs.TabLayoutMediator
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.extensions.incrementByOneAndWrap
import im.vector.app.core.extensions.setCurrentItem
import im.vector.app.core.resources.BuildMeta
import im.vector.app.databinding.FragmentFtueSplashCarouselBinding
import im.vector.app.features.VectorFeatures
import im.vector.app.features.onboarding.OnboardingAction
@ -48,9 +48,12 @@ class FtueAuthSplashCarouselFragment @Inject constructor(
private val vectorPreferences: VectorPreferences,
private val vectorFeatures: VectorFeatures,
private val carouselController: SplashCarouselController,
private val carouselStateFactory: SplashCarouselStateFactory
private val carouselStateFactory: SplashCarouselStateFactory,
private val buildMeta: BuildMeta,
) : AbstractFtueAuthFragment<FragmentFtueSplashCarouselBinding>() {
private var tabLayoutMediator: TabLayoutMediator? = null
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueSplashCarouselBinding {
return FragmentFtueSplashCarouselBinding.inflate(inflater, container, false)
}
@ -60,10 +63,19 @@ class FtueAuthSplashCarouselFragment @Inject constructor(
setupViews()
}
override fun onDestroyView() {
tabLayoutMediator?.detach()
tabLayoutMediator = null
views.splashCarousel.adapter = null
super.onDestroyView()
}
private fun setupViews() {
val carouselAdapter = carouselController.adapter
views.splashCarousel.adapter = carouselAdapter
TabLayoutMediator(views.carouselIndicator, views.splashCarousel) { _, _ -> }.attach()
tabLayoutMediator = TabLayoutMediator(views.carouselIndicator, views.splashCarousel) { _, _ -> }
.also { it.attach() }
carouselController.setData(carouselStateFactory.create())
val isAlreadyHaveAccountEnabled = vectorFeatures.isOnboardingAlreadyHaveAccountSplashEnabled()
@ -76,11 +88,11 @@ class FtueAuthSplashCarouselFragment @Inject constructor(
debouncedClicks { alreadyHaveAnAccount() }
}
if (BuildConfig.DEBUG || vectorPreferences.developerMode()) {
if (buildMeta.isDebug || vectorPreferences.developerMode()) {
views.loginSplashVersion.isVisible = true
@SuppressLint("SetTextI18n")
views.loginSplashVersion.text = "Version : ${BuildConfig.VERSION_NAME}#${BuildConfig.BUILD_NUMBER}\n" +
"Branch: ${BuildConfig.GIT_BRANCH_NAME}"
views.loginSplashVersion.text = "Version : ${buildMeta.versionName}#${buildMeta.buildNumber}\n" +
"Branch: ${buildMeta.gitBranchName}"
views.loginSplashVersion.debouncedClicks { navigator.openDebug(requireContext()) }
}
views.splashCarousel.registerAutomaticUntilInteractionTransitions()

View file

@ -22,8 +22,8 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.resources.BuildMeta
import im.vector.app.databinding.FragmentFtueAuthSplashBinding
import im.vector.app.features.VectorFeatures
import im.vector.app.features.onboarding.OnboardingAction
@ -36,7 +36,8 @@ import javax.inject.Inject
*/
class FtueAuthSplashFragment @Inject constructor(
private val vectorPreferences: VectorPreferences,
private val vectorFeatures: VectorFeatures
private val vectorFeatures: VectorFeatures,
private val buildMeta: BuildMeta,
) : AbstractFtueAuthFragment<FragmentFtueAuthSplashBinding>() {
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueAuthSplashBinding {
@ -59,12 +60,12 @@ class FtueAuthSplashFragment @Inject constructor(
debouncedClicks { alreadyHaveAnAccount() }
}
if (BuildConfig.DEBUG || vectorPreferences.developerMode()) {
if (buildMeta.isDebug || vectorPreferences.developerMode()) {
views.loginSplashVersion.isVisible = true
@SuppressLint("SetTextI18n")
views.loginSplashVersion.text = "Version : ${BuildConfig.VERSION_NAME}\n" +
"Branch: ${BuildConfig.GIT_BRANCH_NAME}\n" +
"Build: ${BuildConfig.BUILD_NUMBER}"
views.loginSplashVersion.text = "Version : ${buildMeta.versionName}\n" +
"Branch: ${buildMeta.gitBranchName}\n" +
"Build: ${buildMeta.buildNumber}"
views.loginSplashVersion.debouncedClicks { navigator.openDebug(requireContext()) }
}
}

View file

@ -30,6 +30,7 @@ import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.extensions.getAllChildFragments
import im.vector.app.core.extensions.toOnOff
import im.vector.app.core.resources.BuildMeta
import im.vector.app.features.settings.VectorLocale
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.settings.devtools.GossipingEventsSerializer
@ -50,6 +51,7 @@ import okhttp3.Response
import org.json.JSONException
import org.json.JSONObject
import org.matrix.android.sdk.api.Matrix
import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider
import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.api.util.MatrixJsonParser
import org.matrix.android.sdk.api.util.MimeTypes
@ -74,7 +76,9 @@ class BugReporter @Inject constructor(
private val vectorPreferences: VectorPreferences,
private val vectorFileLogger: VectorFileLogger,
private val systemLocaleProvider: SystemLocaleProvider,
private val matrix: Matrix
private val matrix: Matrix,
private val buildMeta: BuildMeta,
private val sdkIntProvider: BuildVersionSdkIntProvider,
) {
var inMultiWindowMode = false
@ -278,14 +282,14 @@ class BugReporter @Inject constructor(
.addFormDataPart("can_contact", canContact.toString())
.addFormDataPart("device_id", deviceId)
.addFormDataPart("version", versionProvider.getVersion(longFormat = true, useBuildNumber = false))
.addFormDataPart("branch_name", BuildConfig.GIT_BRANCH_NAME)
.addFormDataPart("branch_name", buildMeta.gitBranchName)
.addFormDataPart("matrix_sdk_version", Matrix.getSdkVersion())
.addFormDataPart("olm_version", olmVersion)
.addFormDataPart("device", Build.MODEL.trim())
.addFormDataPart("verbose_log", vectorPreferences.labAllowedExtendedLogging().toOnOff())
.addFormDataPart("multi_window", inMultiWindowMode.toOnOff())
.addFormDataPart(
"os", Build.VERSION.RELEASE + " (API " + Build.VERSION.SDK_INT + ") " +
"os", Build.VERSION.RELEASE + " (API " + sdkIntProvider.get() + ") " +
Build.VERSION.INCREMENTAL + "-" + Build.VERSION.CODENAME
)
.addFormDataPart("locale", Locale.getDefault().toString())
@ -299,7 +303,7 @@ class BugReporter @Inject constructor(
}
}
val buildNumber = BuildConfig.BUILD_NUMBER
val buildNumber = buildMeta.buildNumber
if (buildNumber.isNotEmpty() && buildNumber != "0") {
builder.addFormDataPart("build_number", buildNumber)
}
@ -339,9 +343,9 @@ class BugReporter @Inject constructor(
screenshot = null
// add some github labels
builder.addFormDataPart("label", BuildConfig.VERSION_NAME)
builder.addFormDataPart("label", BuildConfig.FLAVOR_DESCRIPTION)
builder.addFormDataPart("label", BuildConfig.GIT_BRANCH_NAME)
builder.addFormDataPart("label", buildMeta.versionName)
builder.addFormDataPart("label", buildMeta.flavorDescription)
builder.addFormDataPart("label", buildMeta.gitBranchName)
// Special for Element
builder.addFormDataPart("label", "[Element]")

View file

@ -0,0 +1,23 @@
/*
* 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.features.raw.wellknown
import im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy
data class CryptoConfig(
val fallbackKeySharingStrategy: OutboundSessionKeySharingStrategy
)

View file

@ -68,7 +68,7 @@ data class E2EWellKnownConfig(
val secureBackupSetupMethods: List<String>? = null,
/**
* Configuration for sharing keys strategy which should be used instead of [im.vector.app.BuildConfig.outboundSessionKeySharingStrategy].
* Configuration for sharing keys strategy which should be used instead of [im.vector.app.config.Config.KEY_SHARING_STRATEGY].
* One of on_room_opening, on_typing or disabled.
*/
@Json(name = "outbound_keys_pre_sharing_mode")

View file

@ -16,7 +16,6 @@
package im.vector.app.features.raw.wellknown
import im.vector.app.BuildConfig
import im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -35,12 +34,12 @@ suspend fun RawService.getElementWellknown(sessionParams: SessionParams): Elemen
fun ElementWellKnown.isE2EByDefault() = elementE2E?.e2eDefault ?: riotE2E?.e2eDefault ?: true
fun ElementWellKnown?.getOutboundSessionKeySharingStrategyOrDefault(): OutboundSessionKeySharingStrategy {
fun ElementWellKnown?.getOutboundSessionKeySharingStrategyOrDefault(fallback: OutboundSessionKeySharingStrategy): OutboundSessionKeySharingStrategy {
return when (this?.elementE2E?.outboundsKeyPreSharingMode) {
"on_room_opening" -> OutboundSessionKeySharingStrategy.WhenEnteringRoom
"on_typing" -> OutboundSessionKeySharingStrategy.WhenTyping
"disabled" -> OutboundSessionKeySharingStrategy.WhenSendingEvent
else -> BuildConfig.outboundSessionKeySharingStrategy
else -> fallback
}
}

View file

@ -19,9 +19,9 @@ package im.vector.app.features.settings
import android.content.Context
import android.content.res.Configuration
import androidx.core.content.edit
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.di.DefaultSharedPreferences
import im.vector.app.core.resources.BuildMeta
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import timber.log.Timber
@ -53,12 +53,14 @@ object VectorLocale {
private set
private lateinit var context: Context
private lateinit var buildMeta: BuildMeta
/**
* Init this object.
*/
fun init(context: Context) {
fun init(context: Context, buildMeta: BuildMeta) {
this.context = context
this.buildMeta = buildMeta
val preferences = DefaultSharedPreferences.getInstance(context)
if (preferences.contains(APPLICATION_LOCALE_LANGUAGE_KEY)) {
@ -174,7 +176,7 @@ object VectorLocale {
.setScript(script)
.build()
} catch (exception: IllformedLocaleException) {
if (BuildConfig.DEBUG) {
if (buildMeta.isDebug) {
throw exception
}
// Ignore this locale in production

View file

@ -23,9 +23,9 @@ import android.provider.MediaStore
import androidx.annotation.BoolRes
import androidx.core.content.edit
import com.squareup.seismic.ShakeDetector
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.di.DefaultSharedPreferences
import im.vector.app.core.resources.BuildMeta
import im.vector.app.core.time.Clock
import im.vector.app.features.disclaimer.SHARED_PREF_KEY
import im.vector.app.features.home.ShortcutsHandler
@ -38,6 +38,7 @@ import javax.inject.Inject
class VectorPreferences @Inject constructor(
private val context: Context,
private val clock: Clock,
private val buildMeta: BuildMeta,
) {
companion object {
@ -364,7 +365,7 @@ class VectorPreferences @Inject constructor(
}
fun failFast(): Boolean {
return BuildConfig.DEBUG || (developerMode() && defaultPrefs.getBoolean(SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY, false))
return buildMeta.isDebug || (developerMode() && defaultPrefs.getBoolean(SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY, false))
}
fun didAskUserToEnableSessionPush(): Boolean {

View file

@ -18,10 +18,10 @@ package im.vector.app.features.settings
import android.os.Bundle
import androidx.preference.Preference
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.extensions.orEmpty
import im.vector.app.core.preference.VectorPreference
import im.vector.app.core.resources.BuildMeta
import im.vector.app.core.utils.FirstThrottler
import im.vector.app.core.utils.copyToClipboard
import im.vector.app.core.utils.openAppSettingsPage
@ -32,7 +32,8 @@ import org.matrix.android.sdk.api.Matrix
import javax.inject.Inject
class VectorSettingsHelpAboutFragment @Inject constructor(
private val versionProvider: VersionProvider
private val versionProvider: VersionProvider,
private val buildMeta: BuildMeta,
) : VectorSettingsBaseFragment() {
override var titleRes = R.string.preference_root_help_about
@ -66,9 +67,9 @@ class VectorSettingsHelpAboutFragment @Inject constructor(
findPreference<VectorPreference>(VectorPreferences.SETTINGS_VERSION_PREFERENCE_KEY)!!.let {
it.summary = buildString {
append(versionProvider.getVersion(longFormat = false, useBuildNumber = true))
if (BuildConfig.DEBUG) {
if (buildMeta.isDebug) {
append(" ")
append(BuildConfig.GIT_BRANCH_NAME)
append(buildMeta.gitBranchName)
}
}

View file

@ -35,7 +35,6 @@ import androidx.recyclerview.widget.RecyclerView
import com.airbnb.mvrx.fragmentViewModel
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.R
import im.vector.app.config.analyticsConfig
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.dialogs.ExportKeysDialog
import im.vector.app.core.extensions.queryExportKeys
@ -51,6 +50,7 @@ import im.vector.app.core.utils.copyToClipboard
import im.vector.app.core.utils.openFileSelection
import im.vector.app.core.utils.toast
import im.vector.app.databinding.DialogImportE2eKeysBinding
import im.vector.app.features.analytics.AnalyticsConfig
import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.analytics.ui.consent.AnalyticsConsentViewActions
import im.vector.app.features.analytics.ui.consent.AnalyticsConsentViewModel
@ -84,7 +84,8 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
private val keysExporter: KeysExporter,
private val keysImporter: KeysImporter,
private val rawService: RawService,
private val navigator: Navigator
private val navigator: Navigator,
private val analyticsConfig: AnalyticsConfig,
) : VectorSettingsBaseFragment() {
override var titleRes = R.string.settings_security_and_privacy

Some files were not shown because too many files have changed in this diff Show more