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

# Conflicts:
#	vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt
This commit is contained in:
ericdecanini 2022-08-16 11:58:25 +02:00
commit 8b0fba2ef5
124 changed files with 1648 additions and 357 deletions

View file

@ -1,3 +1,38 @@
Changes in Element v1.4.32 (2022-08-10)
=======================================
Features ✨
----------
- [Location Share] Render fallback UI when map fails to load ([#6711](https://github.com/vector-im/element-android/issues/6711))
Bugfixes 🐛
----------
- Fix message content sometimes appearing in the log ([#6706](https://github.com/vector-im/element-android/issues/6706))
- Disable 'Enable biometrics' option if there are not biometric authenticators enrolled. ([#6713](https://github.com/vector-im/element-android/issues/6713))
- Fix crash when biometric key is used when coming back to foreground and KeyStore reports that the device is still locked. ([#6768](https://github.com/vector-im/element-android/issues/6768))
- Catch all exceptions on lockscreen system key migrations. ([#6769](https://github.com/vector-im/element-android/issues/6769))
- Fixes crash when entering non ascii characters during account creation ([#6735](https://github.com/vector-im/element-android/issues/6735))
- Fixes onboarding login/account creation errors showing after navigation ([#6737](https://github.com/vector-im/element-android/issues/6737))
- [Location sharing] Invisible text on map symbol ([#6687](https://github.com/vector-im/element-android/issues/6687))
In development 🚧
----------------
- Adds new app layout toolbar ([#6655](https://github.com/vector-im/element-android/issues/6655))
Other changes
-------------
- [Modularization] Provides abstraction to avoid direct usages of BuildConfig ([#6406](https://github.com/vector-im/element-android/issues/6406))
- Refactors SpaceStateHandler (previously AppStateHandler) and adds unit tests for it ([#6598](https://github.com/vector-im/element-android/issues/6598))
- Setup Danger to the project ([#6637](https://github.com/vector-im/element-android/issues/6637))
- [Location Share] Open maximized map on tapping on live sharing notification ([#6642](https://github.com/vector-im/element-android/issues/6642))
- [Location sharing] Align naming of components for live location feature ([#6647](https://github.com/vector-im/element-android/issues/6647))
- [Location share] Update minimum sending period to 5 seconds for a live ([#6653](https://github.com/vector-im/element-android/issues/6653))
- [Location sharing] - Fix the memory leaks ([#6674](https://github.com/vector-im/element-android/issues/6674))
- [Timeline] Memory leak in audio message playback tracker ([#6678](https://github.com/vector-im/element-android/issues/6678))
- [FTUE] Memory leak on FtueAuthSplashCarouselFragment ([#6680](https://github.com/vector-im/element-android/issues/6680))
- Link directly to DCO docs from danger message. ([#6739](https://github.com/vector-im/element-android/issues/6739))
Changes in Element v1.4.31 (2022-08-01)
=======================================

View file

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

1
changelog.d/6505.wip Normal file
View file

@ -0,0 +1 @@
added filter tabs for new App layout's Home screen

View file

@ -1 +0,0 @@
Refactors SpaceStateHandler (previously AppStateHandler) and adds unit tests for it

View file

@ -1 +0,0 @@
Setup Danger to the project

View file

@ -1 +0,0 @@
[Location Share] Open maximized map on tapping on live sharing notification

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

@ -0,0 +1 @@
Enable auto-capitalization for Room creation Title field

View file

@ -1 +0,0 @@
[Location sharing] Align naming of components for live location feature

View file

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

View file

@ -1 +0,0 @@
Adds new app layout toolbar (feature flagged)

View file

@ -1 +0,0 @@
[Location sharing] - Fix the memory leaks

View file

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

View file

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

View file

@ -1 +0,0 @@
[Location sharing] Invisible text on map symbol

View file

@ -1 +0,0 @@
Fix message content sometimes appearing in the log

View file

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

View file

@ -1 +0,0 @@
Disable 'Enable biometrics' option if there are not biometric authenticators enrolled.

View file

@ -1 +0,0 @@
Fixes crash when entering non ascii characters during account creation

View file

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

View file

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

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

@ -0,0 +1 @@
[Notification] - Handle creation of notification for live location and poll start

View file

@ -1 +0,0 @@
Fix crash when biometric key is used when coming back to foreground and KeyStore reports that the device is still locked.

View file

@ -1 +0,0 @@
Catch all exceptions on lockscreen system key migrations.

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

@ -0,0 +1 @@
Decouples the variant logic from the vector module

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

@ -0,0 +1 @@
Add a developer setting to enable LeakCanary at runtime

1
changelog.d/6798.wip Normal file
View file

@ -0,0 +1 @@
[Devices management] Add a feature flag and empty screen for future new layout

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

@ -0,0 +1 @@
[Create Room] Reduce some boilerplate with room state event contents

1
changelog.d/6806.wip Normal file
View file

@ -0,0 +1 @@
[Devices management] Other sessions section in new layout

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

@ -0,0 +1 @@
[Call] Memory leak after a call

View file

@ -107,7 +107,9 @@ ext.groups = [
'com.pinterest.ktlint',
'com.posthog.android',
'com.squareup',
'com.squareup.curtains',
'com.squareup.duktape',
'com.squareup.leakcanary',
'com.squareup.moshi',
'com.squareup.okhttp3',
'com.squareup.okio',

View file

@ -23,6 +23,7 @@ Here are the checks that Danger does so far:
- PR description is not empty
- Big PR got a warning to recommend to split
- PR contains a file for towncrier and extension is checked
- PR does not modify frozen classes
- PR contains a Sign-Off, with exception for Element employee contributors
- PR with change on layout should include screenshot in the description
- PR which adds png file warn about the usage of vector drawables

View file

@ -0,0 +1,2 @@
Main changes in this version: Various bug fixes and stability improvements.
Full changelog: https://github.com/vector-im/element-android/releases

View file

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=97a52d145762adc241bad7fd18289bf7f6801e08ece6badf80402fe2b9f250b1
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
distributionSha256Sum=db9c8211ed63f61f60292c69e80d89196f9eb36665e369e7f00ac4cc841c2219
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View file

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

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="TextAppearance.Vector.Subtitle.Medium.DevicesManagement">
<item name="android:textColor">?vctr_content_primary</item>
</style>
<style name="TextAppearance.Vector.Body.DevicesManagement">
<item name="android:textColor">?vctr_content_secondary</item>
</style>
</resources>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Widget.Vector.TabLayout" parent="Widget.MaterialComponents.TabLayout">
<item name="materialThemeOverlay">@style/ThemeOverlay.Vector.HomeFilterTabLayout</item>
<item name="tabTextAppearance">@style/TextAppearance.Vector.FilterTabTextAppearance</item>
</style>
<style name="TextAppearance.Vector.FilterTabTextAppearance" parent="TextAppearance.Vector.Subtitle">
<item name="textAllCaps">false</item>
</style>
<style name="ThemeOverlay.Vector.HomeFilterTabLayout" parent="Theme.Vector.Launcher">
<item name="colorSurface">?vctr_toolbar_background</item>
<item name="colorOnSurface">?vctr_content_secondary</item>
</style>
</resources>

View file

@ -60,7 +60,7 @@ android {
// that the app's state is completely cleared between tests.
testInstrumentationRunnerArguments clearPackageData: 'true'
buildConfigField "String", "SDK_VERSION", "\"1.4.32\""
buildConfigField "String", "SDK_VERSION", "\"1.4.34\""
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""

View file

@ -29,14 +29,12 @@ data class RoomGuestAccessContent(
// Required. Whether guests can join the room. One of: ["can_join", "forbidden"]
@Json(name = "guest_access") val guestAccessStr: String? = null
) {
val guestAccess: GuestAccess? = when (guestAccessStr) {
"can_join" -> GuestAccess.CanJoin
"forbidden" -> GuestAccess.Forbidden
else -> {
Timber.w("Invalid value for GuestAccess: `$guestAccessStr`")
null
}
}
val guestAccess: GuestAccess? = GuestAccess.values()
.find { it.value == guestAccessStr }
?: run {
Timber.w("Invalid value for GuestAccess: `$guestAccessStr`")
null
}
}
@JsonClass(generateAdapter = false)

View file

@ -23,30 +23,30 @@ import com.squareup.moshi.JsonClass
* Ref: https://matrix.org/docs/spec/client_server/latest#room-history-visibility
*/
@JsonClass(generateAdapter = false)
enum class RoomHistoryVisibility {
enum class RoomHistoryVisibility(val value: String) {
/**
* All events while this is the m.room.history_visibility value may be shared by any
* participating homeserver with anyone, regardless of whether they have ever joined the room.
*/
@Json(name = "world_readable") WORLD_READABLE,
@Json(name = "world_readable") WORLD_READABLE("world_readable"),
/**
* Previous events are always accessible to newly joined members. All events in the
* room are accessible, even those sent when the member was not a part of the room.
*/
@Json(name = "shared") SHARED,
@Json(name = "shared") SHARED("shared"),
/**
* Events are accessible to newly joined members from the point they were invited onwards.
* Events stop being accessible when the member's state changes to something other than invite or join.
*/
@Json(name = "invited") INVITED,
@Json(name = "invited") INVITED("invited"),
/**
* Events are accessible to newly joined members from the point they joined the room onwards.
* Events stop being accessible when the member's state changes to something other than join.
*/
@Json(name = "joined") JOINED
@Json(name = "joined") JOINED("joined")
}
/**

View file

@ -24,14 +24,10 @@ import timber.log.Timber
data class RoomHistoryVisibilityContent(
@Json(name = "history_visibility") val historyVisibilityStr: String? = null
) {
val historyVisibility: RoomHistoryVisibility? = when (historyVisibilityStr) {
"world_readable" -> RoomHistoryVisibility.WORLD_READABLE
"shared" -> RoomHistoryVisibility.SHARED
"invited" -> RoomHistoryVisibility.INVITED
"joined" -> RoomHistoryVisibility.JOINED
else -> {
Timber.w("Invalid value for RoomHistoryVisibility: `$historyVisibilityStr`")
null
}
}
val historyVisibility: RoomHistoryVisibility? = RoomHistoryVisibility.values()
.find { it.value == historyVisibilityStr }
?: run {
Timber.w("Invalid value for RoomHistoryVisibility: `$historyVisibilityStr`")
null
}
}

View file

@ -16,7 +16,6 @@
package org.matrix.android.sdk.api.session.room.model.create
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
@ -30,7 +29,7 @@ interface RoomFeaturePreset {
fun updateRoomParams(params: CreateRoomParams)
fun setupInitialStates(): List<Event>?
fun setupInitialStates(): List<CreateRoomStateEvent>?
}
class RestrictedRoomPreset(val homeServerCapabilities: HomeServerCapabilities, val restrictedList: List<RoomJoinRulesAllowEntry>) : RoomFeaturePreset {
@ -41,9 +40,9 @@ class RestrictedRoomPreset(val homeServerCapabilities: HomeServerCapabilities, v
params.roomVersion = homeServerCapabilities.versionOverrideForFeature(HomeServerCapabilities.ROOM_CAP_RESTRICTED)
}
override fun setupInitialStates(): List<Event>? {
override fun setupInitialStates(): List<CreateRoomStateEvent> {
return listOf(
Event(
CreateRoomStateEvent(
type = EventType.STATE_ROOM_JOIN_RULES,
stateKey = "",
content = RoomJoinRulesContent(

View file

@ -22,7 +22,7 @@ import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
/**
* Content of the state event of type
* Content of the event of type
* [EventType.BEACON_LOCATION_DATA][org.matrix.android.sdk.api.session.events.model.EventType.BEACON_LOCATION_DATA]
*
* It contains location data related to a live location share.

View file

@ -20,8 +20,13 @@ import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.identity.IdentityServiceError
import org.matrix.android.sdk.api.session.identity.toMedium
import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent
import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
import org.matrix.android.sdk.api.util.MimeTypes
import org.matrix.android.sdk.internal.crypto.DeviceListManager
@ -78,7 +83,7 @@ internal class CreateRoomBodyBuilder @Inject constructor(
buildAvatarEvent(params),
buildGuestAccess(params)
) +
params.featurePreset?.setupInitialStates().orEmpty() +
buildFeaturePresetInitialStates(params) +
buildCustomInitialStates(params)
)
.takeIf { it.isNotEmpty() }
@ -99,6 +104,16 @@ internal class CreateRoomBodyBuilder @Inject constructor(
)
}
private fun buildFeaturePresetInitialStates(params: CreateRoomParams): List<Event> {
return params.featurePreset?.setupInitialStates().orEmpty().map {
Event(
type = it.type,
stateKey = it.stateKey,
content = it.content
)
}
}
private fun buildCustomInitialStates(params: CreateRoomParams): List<Event> {
return params.initialStates.map {
Event(
@ -123,7 +138,7 @@ internal class CreateRoomBodyBuilder @Inject constructor(
Event(
type = EventType.STATE_ROOM_AVATAR,
stateKey = "",
content = mapOf("url" to response.contentUri)
content = RoomAvatarContent(response.contentUri).toContent()
)
}
}
@ -134,7 +149,7 @@ internal class CreateRoomBodyBuilder @Inject constructor(
Event(
type = EventType.STATE_ROOM_HISTORY_VISIBILITY,
stateKey = "",
content = mapOf("history_visibility" to it)
content = RoomHistoryVisibilityContent(it.value).toContent()
)
}
}
@ -145,7 +160,7 @@ internal class CreateRoomBodyBuilder @Inject constructor(
Event(
type = EventType.STATE_ROOM_GUEST_ACCESS,
stateKey = "",
content = mapOf("guest_access" to it.value)
content = RoomGuestAccessContent(it.value).toContent()
)
}
}
@ -167,7 +182,7 @@ internal class CreateRoomBodyBuilder @Inject constructor(
Event(
type = EventType.STATE_ROOM_ENCRYPTION,
stateKey = "",
content = mapOf("algorithm" to it)
content = EncryptionEventContent(it).toContent()
)
}
}

View file

@ -16,21 +16,6 @@
# limitations under the License.
#
#######################################################################################################################
# Check frozen class modification
#######################################################################################################################
echo "Check if frozen class modified"
git diff "HEAD@{1}" --name-only | grep -e OlmInboundGroupSessionWrapper.kt -e OlmInboundGroupSessionWrapper2.kt
FROZEN_CHANGED=$?
if [ ${FROZEN_CHANGED} -eq 0 ]; then
echo "❌ FROZEN CLASS CHANGED ERROR"
exit 1
else
echo "Frozen check OK"
fi
#######################################################################################################################
# Check drawable quantity
#######################################################################################################################

View file

@ -52,6 +52,19 @@ if (requiresChangelog) {
}
}
// check that frozen classes have not been modified
const frozenClasses = [
"OlmInboundGroupSessionWrapper.kt",
"OlmInboundGroupSessionWrapper2.kt",
]
frozenClasses.forEach(frozen => {
if (editedFiles.some(file => file.endsWith(frozen))) {
fail("Frozen class `" + frozen + "` has been modified. Please do not modify frozen class.")
}
}
)
// Check for a sign-off
const signOff = "Signed-off-by:"

View file

@ -37,7 +37,7 @@ ext.versionMinor = 4
// Note: even values are reserved for regular release, odd values for hotfix release.
// When creating a hotfix, you should decrease the value, since the current value
// is the value for the next regular release.
ext.versionPatch = 32
ext.versionPatch = 34
static def getGitTimestamp() {
def cmd = 'git show -s --format=%ct'
@ -307,7 +307,6 @@ android {
isDefault = true
versionName "${versionMajor}.${versionMinor}.${versionPatch}${getGplayVersionSuffix()}"
resValue "bool", "isGplay", "true"
buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"G\""
buildConfigField "String", "FLAVOR_DESCRIPTION", "\"GooglePlay\""
}
@ -317,7 +316,6 @@ android {
versionName "${versionMajor}.${versionMinor}.${versionPatch}${getFdroidVersionSuffix()}"
resValue "bool", "isGplay", "false"
buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"F\""
buildConfigField "String", "FLAVOR_DESCRIPTION", "\"FDroid\""
}
@ -502,7 +500,7 @@ dependencies {
implementation 'com.posthog.android:posthog:1.1.2'
// UnifiedPush
implementation 'com.github.UnifiedPush:android-connector:2.0.0'
implementation 'com.github.UnifiedPush:android-connector:2.0.1'
// UnifiedPush gplay flavor only
gplayImplementation('com.github.UnifiedPush:android-embedded_fcm_distributor:2.1.1') {
exclude group: 'com.google.firebase', module: 'firebase-core'
@ -578,7 +576,7 @@ dependencies {
debugImplementation "com.kgurgul.flipper:flipper-realm-android:2.2.0"
// Activate when you want to check for leaks, from time to time.
//debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
androidTestImplementation libs.androidx.testCore
androidTestImplementation libs.androidx.testRunner

View file

@ -9,6 +9,8 @@
<activity android:name=".features.debug.settings.DebugPrivateSettingsActivity" />
<activity android:name=".features.debug.sas.DebugSasEmojiActivity" />
<activity android:name=".features.debug.features.DebugFeaturesSettingsActivity" />
<activity android:name=".features.debug.DebugMenuActivity" />
<activity android:name=".features.debug.leak.DebugMemoryLeaksActivity" />
<activity
android:name="com.facebook.flipper.android.diagnostics.FlipperDiagnosticActivity"

View file

@ -37,6 +37,7 @@ import im.vector.app.core.utils.toast
import im.vector.app.databinding.ActivityDebugMenuBinding
import im.vector.app.features.debug.analytics.DebugAnalyticsActivity
import im.vector.app.features.debug.features.DebugFeaturesSettingsActivity
import im.vector.app.features.debug.leak.DebugMemoryLeaksActivity
import im.vector.app.features.debug.sas.DebugSasEmojiActivity
import im.vector.app.features.debug.settings.DebugPrivateSettingsActivity
import im.vector.app.features.qrcode.QrCodeScannerActivity
@ -86,6 +87,7 @@ class DebugMenuActivity : VectorBaseActivity<ActivityDebugMenuBinding>() {
views.debugAnalytics.setOnClickListener {
startActivity(Intent(this, DebugAnalyticsActivity::class.java))
}
views.debugMemoryLeaks.setOnClickListener { openMemoryLeaksSettings() }
views.debugTestTextViewLink.setOnClickListener { testTextViewLink() }
views.debugOpenButtonStylesLight.setOnClickListener {
startActivity(Intent(this, DebugVectorButtonStylesLightActivity::class.java))
@ -130,6 +132,10 @@ class DebugMenuActivity : VectorBaseActivity<ActivityDebugMenuBinding>() {
startActivity(Intent(this, DebugPrivateSettingsActivity::class.java))
}
private fun openMemoryLeaksSettings() {
startActivity(Intent(this, DebugMemoryLeaksActivity::class.java))
}
private fun renderQrCode(text: String) {
views.debugQrCode.setData(text)
}

View file

@ -0,0 +1,57 @@
/*
* 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.debug.di
import android.content.Context
import android.content.Intent
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import im.vector.app.core.debug.DebugNavigator
import im.vector.app.core.debug.DebugReceiver
import im.vector.app.core.debug.FlipperProxy
import im.vector.app.core.debug.LeakDetector
import im.vector.app.features.debug.DebugMenuActivity
import im.vector.app.flipper.VectorFlipperProxy
import im.vector.app.leakcanary.LeakCanaryLeakDetector
import im.vector.app.receivers.VectorDebugReceiver
@InstallIn(SingletonComponent::class)
@Module
abstract class DebugModule {
companion object {
@Provides
fun providesDebugNavigator() = object : DebugNavigator {
override fun openDebugMenu(context: Context) {
context.startActivity(Intent(context, DebugMenuActivity::class.java))
}
}
}
@Binds
abstract fun bindsDebugReceiver(receiver: VectorDebugReceiver): DebugReceiver
@Binds
abstract fun bindsFlipperProxy(flipperProxy: VectorFlipperProxy): FlipperProxy
@Binds
abstract fun bindsLeakDetector(leakDetector: LeakCanaryLeakDetector): LeakDetector
}

View file

@ -24,6 +24,7 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.MavericksViewModelComponent
import im.vector.app.core.di.MavericksViewModelKey
import im.vector.app.features.debug.analytics.DebugAnalyticsViewModel
import im.vector.app.features.debug.leak.DebugMemoryLeaksViewModel
import im.vector.app.features.debug.settings.DebugPrivateSettingsViewModel
@InstallIn(MavericksViewModelComponent::class)
@ -39,4 +40,9 @@ interface MavericksViewModelDebugModule {
@IntoMap
@MavericksViewModelKey(DebugPrivateSettingsViewModel::class)
fun debugPrivateSettingsViewModelFactory(factory: DebugPrivateSettingsViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
@Binds
@IntoMap
@MavericksViewModelKey(DebugMemoryLeaksViewModel::class)
fun debugMemoryLeaksViewModelFactory(factory: DebugMemoryLeaksViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
}

View file

@ -90,6 +90,11 @@ class DebugFeaturesStateFactory @Inject constructor(
key = DebugFeatureKeys.newAppLayoutEnabled,
factory = VectorFeatures::isNewAppLayoutEnabled
),
createBooleanFeature(
label = "Enable New Device Management",
key = DebugFeatureKeys.newDeviceManagementEnabled,
factory = VectorFeatures::isNewDeviceManagementEnabled
),
)
)
}

View file

@ -79,6 +79,9 @@ class DebugVectorFeatures(
override fun isNewAppLayoutEnabled(): Boolean = read(DebugFeatureKeys.newAppLayoutEnabled)
?: vectorFeatures.isNewAppLayoutEnabled()
override fun isNewDeviceManagementEnabled(): Boolean = read(DebugFeatureKeys.newDeviceManagementEnabled)
?: vectorFeatures.isNewDeviceManagementEnabled()
fun <T> override(value: T?, key: Preferences.Key<T>) = updatePreferences {
if (value == null) {
it.remove(key)
@ -139,4 +142,5 @@ object DebugFeatureKeys {
val forceUsageOfOpusEncoder = booleanPreferencesKey("force-usage-of-opus-encoder")
val startDmOnFirstMsg = booleanPreferencesKey("start-dm-on-first-msg")
val newAppLayoutEnabled = booleanPreferencesKey("new-app-layout-enabled")
val newDeviceManagementEnabled = booleanPreferencesKey("new-device-management-enabled")
}

View file

@ -0,0 +1,37 @@
/*
* 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.debug.leak
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.extensions.addFragment
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivitySimpleBinding
@AndroidEntryPoint
class DebugMemoryLeaksActivity : VectorBaseActivity<ActivitySimpleBinding>() {
override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater)
override fun initUiAndData() {
if (isFirstCreation()) {
addFragment(
views.simpleFragmentContainer,
DebugMemoryLeaksFragment::class.java
)
}
}
}

View file

@ -0,0 +1,54 @@
/*
* 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.debug.leak
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.epoxy.onClick
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentDebugMemoryLeaksBinding
@AndroidEntryPoint
class DebugMemoryLeaksFragment : VectorBaseFragment<FragmentDebugMemoryLeaksBinding>() {
private val viewModel: DebugMemoryLeaksViewModel by fragmentViewModel()
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentDebugMemoryLeaksBinding {
return FragmentDebugMemoryLeaksBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setViewListeners()
}
private fun setViewListeners() {
views.enableMemoryLeakAnalysis.onClick {
viewModel.handle(DebugMemoryLeaksViewActions.EnableMemoryLeaksAnalysis(views.enableMemoryLeakAnalysis.isChecked))
}
}
override fun invalidate() = withState(viewModel) { viewState ->
views.enableMemoryLeakAnalysis.isChecked = viewState.isMemoryLeaksAnalysisEnabled
}
}

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.debug.leak
import im.vector.app.core.platform.VectorViewModelAction
sealed interface DebugMemoryLeaksViewActions : VectorViewModelAction {
data class EnableMemoryLeaksAnalysis(val isEnabled: Boolean) : DebugMemoryLeaksViewActions
}

View file

@ -0,0 +1,67 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.debug.leak
import com.airbnb.mvrx.MavericksViewModelFactory
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.core.debug.LeakDetector
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
import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.launch
class DebugMemoryLeaksViewModel @AssistedInject constructor(
@Assisted initialState: DebugMemoryLeaksViewState,
private val vectorPreferences: VectorPreferences,
private val leakDetector: LeakDetector,
) : VectorViewModel<DebugMemoryLeaksViewState, DebugMemoryLeaksViewActions, EmptyViewEvents>(initialState) {
@AssistedFactory
interface Factory : MavericksAssistedViewModelFactory<DebugMemoryLeaksViewModel, DebugMemoryLeaksViewState> {
override fun create(initialState: DebugMemoryLeaksViewState): DebugMemoryLeaksViewModel
}
companion object : MavericksViewModelFactory<DebugMemoryLeaksViewModel, DebugMemoryLeaksViewState> by hiltMavericksViewModelFactory()
init {
viewModelScope.launch {
refreshStateFromPreferences()
}
}
override fun handle(action: DebugMemoryLeaksViewActions) {
when (action) {
is DebugMemoryLeaksViewActions.EnableMemoryLeaksAnalysis -> handleEnableMemoryLeaksAnalysis(action)
}
}
private fun handleEnableMemoryLeaksAnalysis(action: DebugMemoryLeaksViewActions.EnableMemoryLeaksAnalysis) {
viewModelScope.launch {
vectorPreferences.enableMemoryLeakAnalysis(action.isEnabled)
leakDetector.enable(action.isEnabled)
refreshStateFromPreferences()
}
}
private fun refreshStateFromPreferences() {
setState { copy(isMemoryLeaksAnalysisEnabled = vectorPreferences.isMemoryLeakAnalysisEnabled()) }
}
}

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.debug.leak
import com.airbnb.mvrx.MavericksState
data class DebugMemoryLeaksViewState(
val isMemoryLeaksAnalysisEnabled: Boolean = false
) : MavericksState

View file

@ -29,19 +29,19 @@ import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPl
import com.facebook.soloader.SoLoader
import com.kgurgul.flipper.RealmDatabaseDriver
import com.kgurgul.flipper.RealmDatabaseProvider
import im.vector.app.core.debug.FlipperProxy
import io.realm.RealmConfiguration
import okhttp3.Interceptor
import org.matrix.android.sdk.api.Matrix
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class FlipperProxy @Inject constructor(
class VectorFlipperProxy @Inject constructor(
private val context: Context,
) {
) : FlipperProxy {
private val networkFlipperPlugin = NetworkFlipperPlugin()
fun init(matrix: Matrix) {
override fun init(matrix: Matrix) {
SoLoader.init(context, false)
if (FlipperUtils.shouldEnableFlipper(context)) {
@ -65,8 +65,5 @@ class FlipperProxy @Inject constructor(
}
}
@Suppress("RedundantNullableReturnType")
fun getNetworkInterceptor(): Interceptor? {
return FlipperOkhttpInterceptor(networkFlipperPlugin)
}
override fun networkInterceptor() = FlipperOkhttpInterceptor(networkFlipperPlugin)
}

View file

@ -0,0 +1,27 @@
/*
* 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.leakcanary
import im.vector.app.core.debug.LeakDetector
import leakcanary.LeakCanary
import javax.inject.Inject
class LeakCanaryLeakDetector @Inject constructor() : LeakDetector {
override fun enable(enable: Boolean) {
LeakCanary.config = LeakCanary.config.copy(dumpHeap = enable)
}
}

View file

@ -22,14 +22,24 @@ import android.content.Intent
import android.content.IntentFilter
import android.content.SharedPreferences
import androidx.core.content.edit
import im.vector.app.core.debug.DebugReceiver
import im.vector.app.core.di.DefaultSharedPreferences
import im.vector.app.core.utils.lsFiles
import timber.log.Timber
import javax.inject.Inject
/**
* Receiver to handle some command from ADB
*/
class DebugReceiver : BroadcastReceiver() {
class VectorDebugReceiver @Inject constructor() : BroadcastReceiver(), DebugReceiver {
override fun register(context: Context) {
context.registerReceiver(this, getIntentFilter(context))
}
override fun unregister(context: Context) {
context.unregisterReceiver(this)
}
override fun onReceive(context: Context, intent: Intent) {
Timber.v("Received debug action: ${intent.action}")

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/coordinatorLayout"
android:layout_width="match_parent"
@ -8,8 +9,11 @@
tools:ignore="HardcodedText">
<ScrollView
android:id="@+id/scrollView2"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
app:layout_anchor="@+id/scrollView2"
app:layout_anchorGravity="center">
<LinearLayout
android:layout_width="match_parent"
@ -38,6 +42,12 @@
android:layout_height="wrap_content"
android:text="Analytics" />
<Button
android:id="@+id/debug_memory_leaks"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Memory leaks" />
<Button
android:id="@+id/debug_test_text_view_link"
android:layout_width="wrap_content"

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".features.debug.settings.DebugPrivateSettingsActivity"
tools:ignore="HardcodedText">
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="@drawable/linear_divider"
android:orientation="vertical"
android:padding="@dimen/layout_horizontal_margin"
android:showDividers="middle">
<CheckBox
android:id="@+id/enableMemoryLeakAnalysis"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Enable memory leak analysis" />
</LinearLayout>
</ScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -17,20 +17,46 @@
package im.vector.app.di
import android.content.Context
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import im.vector.app.core.pushers.FcmHelper
import im.vector.app.core.services.GuardServiceStarter
import im.vector.app.fdroid.service.FDroidGuardServiceStarter
import im.vector.app.features.home.NightlyProxy
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.settings.legals.FlavorLegals
import im.vector.app.push.fcm.FdroidFcmHelper
@InstallIn(SingletonComponent::class)
@Module
object FlavorModule {
abstract class FlavorModule {
@Provides
fun provideGuardServiceStarter(preferences: VectorPreferences, appContext: Context): GuardServiceStarter {
return FDroidGuardServiceStarter(preferences, appContext)
companion object {
@Provides
fun provideGuardServiceStarter(preferences: VectorPreferences, appContext: Context): GuardServiceStarter {
return FDroidGuardServiceStarter(preferences, appContext)
}
@Provides
fun provideNightlyProxy() = object : NightlyProxy {
override fun onHomeResumed() {
// no op
}
}
@Provides
fun providesFlavorLegals() = object : FlavorLegals {
override fun hasThirdPartyNotices() = false
override fun navigateToThirdPartyNotices(context: Context) {
// no op
}
}
}
@Binds
abstract fun bindsFcmHelper(fcmHelper: FdroidFcmHelper): FcmHelper
}

View file

@ -0,0 +1,31 @@
/*
* 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.di
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
import im.vector.app.features.push.NotificationTroubleshootTestManagerFactory
import im.vector.app.push.fcm.FdroidNotificationTroubleshootTestManagerFactory
@InstallIn(ActivityComponent::class)
@Module
abstract class NotificationTestModule {
@Binds
abstract fun bindsNotificationTestFactory(factory: FdroidNotificationTroubleshootTestManagerFactory): NotificationTroubleshootTestManagerFactory
}

View file

@ -20,6 +20,7 @@ package im.vector.app.push.fcm
import android.app.Activity
import android.content.Context
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.pushers.FcmHelper
import im.vector.app.core.pushers.PushersManager
import im.vector.app.fdroid.BackgroundSyncStarter
import im.vector.app.fdroid.receiver.AlarmSyncBroadcastReceiver
@ -28,47 +29,32 @@ import javax.inject.Inject
/**
* This class has an alter ego in the gplay variant.
*/
class FcmHelper @Inject constructor(
class FdroidFcmHelper @Inject constructor(
private val context: Context,
private val backgroundSyncStarter: BackgroundSyncStarter,
) {
) : FcmHelper {
fun isFirebaseAvailable(): Boolean = false
override fun isFirebaseAvailable(): Boolean = false
/**
* Retrieves the FCM registration token.
*
* @return the FCM token or null if not received from FCM
*/
fun getFcmToken(): String? {
override fun getFcmToken(): String? {
return null
}
/**
* Store FCM token to the SharedPrefs
*
* @param token the token to store
*/
fun storeFcmToken(token: String?) {
override fun storeFcmToken(token: String?) {
// No op
}
/**
* onNewToken may not be called on application upgrade, so ensure my shared pref is set
*
* @param activity the first launch Activity
*/
fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) {
override fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) {
// No op
}
fun onEnterForeground(activeSessionHolder: ActiveSessionHolder) {
override fun onEnterForeground(activeSessionHolder: ActiveSessionHolder) {
// try to stop all regardless of background mode
activeSessionHolder.getSafeActiveSession()?.syncService()?.stopAnyBackgroundSync()
AlarmSyncBroadcastReceiver.cancelAlarm(context)
}
fun onEnterBackground(activeSessionHolder: ActiveSessionHolder) {
override fun onEnterBackground(activeSessionHolder: ActiveSessionHolder) {
backgroundSyncStarter.start(activeSessionHolder)
}
}

View file

@ -21,6 +21,7 @@ import im.vector.app.fdroid.features.settings.troubleshoot.TestAutoStartBoot
import im.vector.app.fdroid.features.settings.troubleshoot.TestBackgroundRestrictions
import im.vector.app.fdroid.features.settings.troubleshoot.TestBatteryOptimization
import im.vector.app.features.VectorFeatures
import im.vector.app.features.push.NotificationTroubleshootTestManagerFactory
import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager
import im.vector.app.features.settings.troubleshoot.TestAccountSettings
import im.vector.app.features.settings.troubleshoot.TestAvailableUnifiedPushDistributors
@ -35,7 +36,7 @@ import im.vector.app.features.settings.troubleshoot.TestUnifiedPushEndpoint
import im.vector.app.features.settings.troubleshoot.TestUnifiedPushGateway
import javax.inject.Inject
class NotificationTroubleshootTestManagerFactory @Inject constructor(
class FdroidNotificationTroubleshootTestManagerFactory @Inject constructor(
private val unifiedPushHelper: UnifiedPushHelper,
private val testSystemSettings: TestSystemSettings,
private val testAccountSettings: TestAccountSettings,
@ -52,9 +53,9 @@ class NotificationTroubleshootTestManagerFactory @Inject constructor(
private val testBatteryOptimization: TestBatteryOptimization,
private val testNotification: TestNotification,
private val vectorFeatures: VectorFeatures,
) {
) : NotificationTroubleshootTestManagerFactory {
fun create(fragment: Fragment): NotificationTroubleshootTestManager {
override fun create(fragment: Fragment): NotificationTroubleshootTestManager {
val mgr = NotificationTroubleshootTestManager(fragment)
mgr.addTest(testSystemSettings)
mgr.addTest(testAccountSettings)

View file

@ -0,0 +1,33 @@
/*
* 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
import android.content.Context
import android.content.Intent
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
import im.vector.app.features.settings.legals.FlavorLegals
import javax.inject.Inject
class GoogleFlavorLegals @Inject constructor() : FlavorLegals {
override fun hasThirdPartyNotices() = true
override fun navigateToThirdPartyNotices(context: Context) {
// See https://developers.google.com/android/guides/opensource
context.startActivity(Intent(context, OssLicensesMenuActivity::class.java))
}
}

View file

@ -16,18 +16,36 @@
package im.vector.app.di
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import im.vector.app.GoogleFlavorLegals
import im.vector.app.core.pushers.FcmHelper
import im.vector.app.core.services.GuardServiceStarter
import im.vector.app.features.home.NightlyProxy
import im.vector.app.features.settings.legals.FlavorLegals
import im.vector.app.nightly.FirebaseNightlyProxy
import im.vector.app.push.fcm.GoogleFcmHelper
@InstallIn(SingletonComponent::class)
@Module
object FlavorModule {
abstract class FlavorModule {
@Provides
fun provideGuardServiceStarter(): GuardServiceStarter {
return object : GuardServiceStarter {}
companion object {
@Provides
fun provideGuardServiceStarter(): GuardServiceStarter {
return object : GuardServiceStarter {}
}
}
@Binds
abstract fun bindsNightlyProxy(nightlyProxy: FirebaseNightlyProxy): NightlyProxy
@Binds
abstract fun bindsFcmHelper(fcmHelper: GoogleFcmHelper): FcmHelper
@Binds
abstract fun bindsFlavorLegals(legals: GoogleFlavorLegals): FlavorLegals
}

View file

@ -0,0 +1,31 @@
/*
* 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.di
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
import im.vector.app.features.push.NotificationTroubleshootTestManagerFactory
import im.vector.app.push.fcm.GoogleNotificationTroubleshootTestManagerFactory
@InstallIn(ActivityComponent::class)
@Module
abstract class NotificationTestModule {
@Binds
abstract fun bindsNotificationTestFactory(factory: GoogleNotificationTroubleshootTestManagerFactory): NotificationTroubleshootTestManagerFactory
}

View file

@ -20,10 +20,10 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.fragment.app.FragmentActivity
import com.google.firebase.messaging.FirebaseMessaging
import im.vector.app.R
import im.vector.app.core.pushers.FcmHelper
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.startAddGoogleAccountIntent
import im.vector.app.features.settings.troubleshoot.TroubleshootTest
import im.vector.app.push.fcm.FcmHelper
import timber.log.Timber
import javax.inject.Inject

View file

@ -23,10 +23,10 @@ import androidx.work.WorkInfo
import androidx.work.WorkManager
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.pushers.FcmHelper
import im.vector.app.core.pushers.PushersManager
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.settings.troubleshoot.TroubleshootTest
import im.vector.app.push.fcm.FcmHelper
import org.matrix.android.sdk.api.session.pushers.PusherState
import javax.inject.Inject

View file

@ -23,15 +23,17 @@ import com.google.firebase.appdistribution.FirebaseAppDistributionException
import im.vector.app.BuildConfig
import im.vector.app.core.di.DefaultPreferences
import im.vector.app.core.time.Clock
import im.vector.app.features.home.NightlyProxy
import timber.log.Timber
import javax.inject.Inject
class NightlyProxy @Inject constructor(
class FirebaseNightlyProxy @Inject constructor(
private val clock: Clock,
@DefaultPreferences
private val sharedPreferences: SharedPreferences,
) {
fun onHomeResumed() {
) : NightlyProxy {
override fun onHomeResumed() {
if (!canDisplayPopup()) return
val firebaseAppDistribution = FirebaseAppDistribution.getInstance()
firebaseAppDistribution.updateIfNewReleaseAvailable()

View file

@ -25,6 +25,7 @@ import com.google.firebase.messaging.FirebaseMessaging
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.DefaultSharedPreferences
import im.vector.app.core.pushers.FcmHelper
import im.vector.app.core.pushers.PushersManager
import timber.log.Timber
import javax.inject.Inject
@ -33,44 +34,29 @@ import javax.inject.Inject
* This class store the FCM token in SharedPrefs and ensure this token is retrieved.
* It has an alter ego in the fdroid variant.
*/
class FcmHelper @Inject constructor(
class GoogleFcmHelper @Inject constructor(
context: Context,
) {
) : FcmHelper {
companion object {
private const val PREFS_KEY_FCM_TOKEN = "FCM_TOKEN"
}
private val sharedPrefs = DefaultSharedPreferences.getInstance(context)
fun isFirebaseAvailable(): Boolean = true
override fun isFirebaseAvailable(): Boolean = true
/**
* Retrieves the FCM registration token.
*
* @return the FCM token or null if not received from FCM
*/
fun getFcmToken(): String? {
override fun getFcmToken(): String? {
return sharedPrefs.getString(PREFS_KEY_FCM_TOKEN, null)
}
/**
* Store FCM token to the SharedPrefs
* TODO Store in realm
*
* @param token the token to store
*/
fun storeFcmToken(token: String?) {
override fun storeFcmToken(token: String?) {
// TODO Store in realm
sharedPrefs.edit {
putString(PREFS_KEY_FCM_TOKEN, token)
}
}
/**
* onNewToken may not be called on application upgrade, so ensure my shared pref is set
*
* @param activity the first launch Activity
*/
fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) {
override fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) {
// if (TextUtils.isEmpty(getFcmToken(activity))) {
// 'app should always check the device for a compatible Google Play services APK before accessing Google Play services features'
if (checkPlayServices(activity)) {
@ -105,13 +91,11 @@ class FcmHelper @Inject constructor(
return resultCode == ConnectionResult.SUCCESS
}
@Suppress("UNUSED_PARAMETER")
fun onEnterForeground(activeSessionHolder: ActiveSessionHolder) {
override fun onEnterForeground(activeSessionHolder: ActiveSessionHolder) {
// No op
}
@Suppress("UNUSED_PARAMETER")
fun onEnterBackground(activeSessionHolder: ActiveSessionHolder) {
override fun onEnterBackground(activeSessionHolder: ActiveSessionHolder) {
// No op
}
}

View file

@ -18,6 +18,7 @@ package im.vector.app.push.fcm
import androidx.fragment.app.Fragment
import im.vector.app.core.pushers.UnifiedPushHelper
import im.vector.app.features.VectorFeatures
import im.vector.app.features.push.NotificationTroubleshootTestManagerFactory
import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager
import im.vector.app.features.settings.troubleshoot.TestAccountSettings
import im.vector.app.features.settings.troubleshoot.TestAvailableUnifiedPushDistributors
@ -35,7 +36,7 @@ import im.vector.app.gplay.features.settings.troubleshoot.TestPlayServices
import im.vector.app.gplay.features.settings.troubleshoot.TestTokenRegistration
import javax.inject.Inject
class NotificationTroubleshootTestManagerFactory @Inject constructor(
class GoogleNotificationTroubleshootTestManagerFactory @Inject constructor(
private val unifiedPushHelper: UnifiedPushHelper,
private val testSystemSettings: TestSystemSettings,
private val testAccountSettings: TestAccountSettings,
@ -52,9 +53,9 @@ class NotificationTroubleshootTestManagerFactory @Inject constructor(
private val testPushFromPushGateway: TestPushFromPushGateway,
private val testNotification: TestNotification,
private val vectorFeatures: VectorFeatures,
) {
) : NotificationTroubleshootTestManagerFactory {
fun create(fragment: Fragment): NotificationTroubleshootTestManager {
override fun create(fragment: Fragment): NotificationTroubleshootTestManager {
val mgr = NotificationTroubleshootTestManager(fragment)
mgr.addTest(testSystemSettings)
mgr.addTest(testAccountSettings)

View file

@ -187,7 +187,6 @@
android:name="android.support.PARENT_ACTIVITY"
android:value=".features.home.HomeActivity" />
</activity>
<activity android:name=".features.debug.DebugMenuActivity" />
<activity android:name=".features.createdirect.CreateDirectRoomActivity" />
<activity android:name=".features.invite.InviteUsersToRoomActivity" />
<activity android:name=".features.webview.VectorWebViewActivity" />

View file

@ -45,7 +45,10 @@ 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.debug.FlipperProxy
import im.vector.app.core.debug.LeakDetector
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.pushers.FcmHelper
import im.vector.app.core.resources.BuildMeta
import im.vector.app.features.analytics.VectorAnalytics
import im.vector.app.features.call.webrtc.WebRtcCallManager
@ -63,8 +66,6 @@ import im.vector.app.features.settings.VectorLocale
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.themes.ThemeUtils
import im.vector.app.features.version.VersionProvider
import im.vector.app.flipper.FlipperProxy
import im.vector.app.push.fcm.FcmHelper
import org.jitsi.meet.sdk.log.JitsiMeetDefaultLogHandler
import org.matrix.android.sdk.api.Matrix
import org.matrix.android.sdk.api.auth.AuthenticationService
@ -106,6 +107,7 @@ class VectorApplication :
@Inject lateinit var matrix: Matrix
@Inject lateinit var fcmHelper: FcmHelper
@Inject lateinit var buildMeta: BuildMeta
@Inject lateinit var leakDetector: LeakDetector
// font thread handler
private var fontThreadHandler: Handler? = null
@ -201,6 +203,8 @@ class VectorApplication :
// Initialize Mapbox before inflating mapViews
Mapbox.getInstance(this)
initMemoryLeakAnalysis()
}
private fun configureEpoxy() {
@ -266,4 +270,8 @@ class VectorApplication :
handlerThread.start()
return Handler(handlerThread.looper)
}
private fun initMemoryLeakAnalysis() {
leakDetector.enable(vectorPreferences.isMemoryLeakAnalysisEnabled())
}
}

View file

@ -14,10 +14,10 @@
* limitations under the License.
*/
package im.vector.app.nightly
package im.vector.app.core.debug
import javax.inject.Inject
import android.content.Context
class NightlyProxy @Inject constructor() {
fun onHomeResumed() = Unit
interface DebugNavigator {
fun openDebugMenu(context: Context)
}

View file

@ -1,5 +1,5 @@
/*
* Copyright 2020 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,9 +14,11 @@
* limitations under the License.
*/
package im.vector.app
package im.vector.app.core.debug
import android.content.Context
// No op
fun openOssLicensesMenuActivity(@Suppress("UNUSED_PARAMETER") context: Context) = Unit
interface DebugReceiver {
fun register(context: Context)
fun unregister(context: Context)
}

View file

@ -14,18 +14,12 @@
* limitations under the License.
*/
package im.vector.app.flipper
package im.vector.app.core.debug
import okhttp3.Interceptor
import org.matrix.android.sdk.api.Matrix
import javax.inject.Inject
/**
* No op version.
*/
@Suppress("UNUSED_PARAMETER")
class FlipperProxy @Inject constructor() {
fun init(matrix: Matrix) {}
fun getNetworkInterceptor(): Interceptor? = null
interface FlipperProxy {
fun init(matrix: Matrix)
fun networkInterceptor(): Interceptor?
}

View file

@ -1,5 +1,5 @@
/*
* Copyright 2019 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,9 +14,11 @@
* limitations under the License.
*/
package im.vector.app.features.debug
package im.vector.app.core.debug
import androidx.appcompat.app.AppCompatActivity
// This activity is not accessible
class DebugMenuActivity : AppCompatActivity()
/**
* Used for memory leak analysis control.
*/
interface LeakDetector {
fun enable(enable: Boolean)
}

View file

@ -34,6 +34,7 @@ import im.vector.app.EmojiSpanify
import im.vector.app.SpaceStateHandler
import im.vector.app.SpaceStateHandlerImpl
import im.vector.app.config.Config
import im.vector.app.core.debug.FlipperProxy
import im.vector.app.core.dispatchers.CoroutineDispatchers
import im.vector.app.core.error.DefaultErrorFormatter
import im.vector.app.core.error.ErrorFormatter
@ -57,7 +58,6 @@ import im.vector.app.features.settings.FontScalePreferencesImpl
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.ui.SharedPreferencesUiStateRepository
import im.vector.app.features.ui.UiStateRepository
import im.vector.app.flipper.FlipperProxy
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
@ -144,7 +144,7 @@ object VectorStaticModule {
roomDisplayNameFallbackProvider = vectorRoomDisplayNameFallbackProvider,
threadMessagesEnabledDefault = vectorPreferences.areThreadMessagesEnabled(),
networkInterceptors = listOfNotNull(
flipperProxy.getNetworkInterceptor(),
flipperProxy.networkInterceptor(),
)
)
}

View file

@ -55,6 +55,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.EntryPointAccessors
import im.vector.app.R
import im.vector.app.core.debug.DebugReceiver
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.ActivityEntryPoint
import im.vector.app.core.dialogs.DialogLocker
@ -91,7 +92,6 @@ import im.vector.app.features.settings.FontScalePreferencesImpl
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.themes.ActivityOtherThemes
import im.vector.app.features.themes.ThemeUtils
import im.vector.app.receivers.DebugReceiver
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.extensions.orFalse
@ -161,6 +161,9 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
@Inject lateinit var buildMeta: BuildMeta
@Inject lateinit var fontScalePreferences: FontScalePreferences
// For debug only
@Inject lateinit var debugReceiver: DebugReceiver
@Inject
lateinit var vectorFeatures: VectorFeatures
@ -176,9 +179,6 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
private var savedInstanceState: Bundle? = null
// For debug only
private var debugReceiver: DebugReceiver? = null
private val restorables = ArrayList<Restorable>()
override fun attachBaseContext(base: Context) {
@ -418,13 +418,7 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
if (this !is BugReportActivity && vectorPreferences.useRageshake()) {
rageShake.start()
}
DebugReceiver
.getIntentFilter(this)
.takeIf { buildMeta.isDebug }
?.let {
debugReceiver = DebugReceiver()
registerReceiver(debugReceiver, it)
}
debugReceiver.register(this)
}
private val postResumeScheduledActions = mutableListOf<() -> Unit>()
@ -454,11 +448,7 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
Timber.i("onPause Activity ${javaClass.simpleName}")
rageShake.stop()
debugReceiver?.let {
unregisterReceiver(debugReceiver)
debugReceiver = null
}
debugReceiver.unregister(this)
}
override fun onWindowFocusChanged(hasFocus: Boolean) {

View file

@ -0,0 +1,51 @@
/*
* 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.pushers
import android.app.Activity
import im.vector.app.core.di.ActiveSessionHolder
interface FcmHelper {
fun isFirebaseAvailable(): Boolean
/**
* Retrieves the FCM registration token.
*
* @return the FCM token or null if not received from FCM.
*/
fun getFcmToken(): String?
/**
* Store FCM token to the SharedPrefs.
*
* @param token the token to store.
*/
fun storeFcmToken(token: String?)
/**
* onNewToken may not be called on application upgrade, so ensure my shared pref is set.
*
* @param activity the first launch Activity.
* @param pushersManager the instance to register the pusher on.
* @param registerPusher whether the pusher should be registered.
*/
fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean)
fun onEnterForeground(activeSessionHolder: ActiveSessionHolder)
fun onEnterBackground(activeSessionHolder: ActiveSessionHolder)
}

View file

@ -28,7 +28,6 @@ import im.vector.app.core.utils.getApplicationLabel
import im.vector.app.features.VectorFeatures
import im.vector.app.features.settings.BackgroundSyncMode
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.push.fcm.FcmHelper
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.Matrix
import org.matrix.android.sdk.api.cache.CacheStrategy

View file

@ -34,6 +34,7 @@ interface VectorFeatures {
fun forceUsageOfOpusEncoder(): Boolean
fun shouldStartDmOnFirstMessage(): Boolean
fun isNewAppLayoutEnabled(): Boolean
fun isNewDeviceManagementEnabled(): Boolean
}
class DefaultVectorFeatures : VectorFeatures {
@ -50,4 +51,5 @@ class DefaultVectorFeatures : VectorFeatures {
override fun forceUsageOfOpusEncoder(): Boolean = false
override fun shouldStartDmOnFirstMessage(): Boolean = false
override fun isNewAppLayoutEnabled(): Boolean = false
override fun isNewDeviceManagementEnabled(): Boolean = false
}

View file

@ -241,6 +241,7 @@ class VectorCallActivity :
detachRenderersIfNeeded()
turnScreenOffAndKeyguardOn()
removeOnPictureInPictureModeChangedListener(pictureInPictureModeChangedInfoConsumer)
screenCaptureServiceConnection.unbind()
super.onDestroy()
}

View file

@ -47,6 +47,10 @@ class ScreenCaptureServiceConnection @Inject constructor(
}
}
fun unbind() {
callback = null
}
fun stopScreenCapturing() {
screenCaptureAndroidService?.stopService()
}

View file

@ -58,6 +58,9 @@ abstract class FormEditTextItem : VectorEpoxyModel<FormEditTextItem.Holder>(R.la
@EpoxyAttribute
var singleLine: Boolean = true
@EpoxyAttribute
var autoCapitalize: Boolean = false
@EpoxyAttribute
var imeOptions: Int? = null
@ -132,10 +135,9 @@ abstract class FormEditTextItem : VectorEpoxyModel<FormEditTextItem.Holder>(R.la
*/
private fun configureInputType(holder: Holder) {
val newInputType =
inputType ?: when (singleLine) {
true -> InputType.TYPE_CLASS_TEXT
false -> InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE
}
inputType ?: InputType.TYPE_CLASS_TEXT
.let { if (autoCapitalize) it or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES else it }
.let { if (!singleLine) it or InputType.TYPE_TEXT_FLAG_MULTI_LINE else it }
// This is a must in order to avoid extreme lag in some devices, on fast typing
if (holder.textInputEditText.inputType != newInputType) {

View file

@ -44,6 +44,7 @@ import im.vector.app.core.extensions.replaceFragment
import im.vector.app.core.extensions.validateBackPressed
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.platform.VectorMenuProvider
import im.vector.app.core.pushers.FcmHelper
import im.vector.app.core.pushers.PushersManager
import im.vector.app.core.pushers.UnifiedPushHelper
import im.vector.app.core.utils.startSharePlainTextIntent
@ -79,8 +80,6 @@ import im.vector.app.features.spaces.invite.SpaceInviteBottomSheet
import im.vector.app.features.spaces.share.ShareSpaceBottomSheet
import im.vector.app.features.themes.ThemeUtils
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
import im.vector.app.nightly.NightlyProxy
import im.vector.app.push.fcm.FcmHelper
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch

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
interface NightlyProxy {
fun onHomeResumed()
}

View file

@ -17,6 +17,7 @@
package im.vector.app.features.home.room.list.home
import im.vector.app.core.platform.VectorViewModelAction
import im.vector.app.features.home.room.list.home.filter.HomeRoomFilter
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
@ -25,4 +26,5 @@ sealed class HomeRoomListAction : VectorViewModelAction {
data class ChangeRoomNotificationState(val roomId: String, val notificationState: RoomNotificationState) : HomeRoomListAction()
data class ToggleTag(val roomId: String, val tag: String) : HomeRoomListAction()
data class LeaveRoom(val roomId: String) : HomeRoomListAction()
data class ChangeRoomFilter(val filter: HomeRoomFilter) : HomeRoomListAction()
}

View file

@ -37,16 +37,16 @@ import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.UserPreferencesProvider
import im.vector.app.databinding.FragmentRoomListBinding
import im.vector.app.features.analytics.plan.ViewRoom
import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.home.room.list.RoomListAnimator
import im.vector.app.features.home.room.list.RoomListListener
import im.vector.app.features.home.room.list.RoomSummaryItemFactory
import im.vector.app.features.home.room.list.RoomSummaryPagedController
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
import im.vector.app.features.home.room.list.actions.RoomListSharedAction
import im.vector.app.features.home.room.list.actions.RoomListSharedActionViewModel
import im.vector.app.features.home.room.list.home.filter.HomeFilteredRoomsController
import im.vector.app.features.home.room.list.home.filter.HomeRoomFilter
import im.vector.app.features.home.room.list.home.recent.RecentRoomCarouselController
import im.vector.app.features.spaces.SpaceListBottomSheet
import kotlinx.coroutines.flow.launchIn
@ -227,28 +227,36 @@ class HomeRoomListFragment @Inject constructor(
.show()
}
private fun getAdapterForData(data: HomeRoomSection): EpoxyControllerAdapter {
return when (data) {
private fun getAdapterForData(section: HomeRoomSection): EpoxyControllerAdapter {
return when (section) {
is HomeRoomSection.RoomSummaryData -> {
RoomSummaryPagedController(
HomeFilteredRoomsController(
roomSummaryItemFactory,
RoomListDisplayMode.ROOMS
showFilters = section.showFilters,
).also { controller ->
controller.listener = this
data.list.observe(viewLifecycleOwner) { list ->
controller.onFilterChanged = ::onRoomFilterChanged
section.filtersData.onEach {
controller.submitFiltersData(it)
}.launchIn(lifecycleScope)
section.list.observe(viewLifecycleOwner) { list ->
controller.submitList(list)
}
}.adapter
}
is HomeRoomSection.RecentRoomsData -> recentRoomCarouselController.also { controller ->
controller.listener = this
data.list.observe(viewLifecycleOwner) { list ->
section.list.observe(viewLifecycleOwner) { list ->
controller.submitList(list)
}
}.adapter
}
}
private fun onRoomFilterChanged(filter: HomeRoomFilter) {
roomListViewModel.handle(HomeRoomListAction.ChangeRoomFilter(filter))
}
private fun handleSelectRoom(event: HomeRoomListViewEvents.SelectRoom, isInviteAlreadyAccepted: Boolean) {
navigator.openRoom(
context = requireActivity(),

View file

@ -27,33 +27,38 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.StateView
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.home.room.list.home.filter.HomeRoomFilter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.query.SpaceFilter
import org.matrix.android.sdk.api.query.RoomCategoryFilter
import org.matrix.android.sdk.api.query.RoomTagQueryFilter
import org.matrix.android.sdk.api.query.toActiveSpaceOrNoFilter
import org.matrix.android.sdk.api.query.toActiveSpaceOrOrphanRooms
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.RoomSortOrder
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.api.session.room.state.isPublic
import org.matrix.android.sdk.flow.flow
class HomeRoomListViewModel @AssistedInject constructor(
@Assisted initialState: HomeRoomListViewState,
private val session: Session,
private val spaceStateHandler: SpaceStateHandler,
private val vectorPreferences: VectorPreferences,
) : VectorViewModel<HomeRoomListViewState, HomeRoomListAction, HomeRoomListViewEvents>(initialState) {
@AssistedFactory
@ -73,6 +78,8 @@ class HomeRoomListViewModel @AssistedInject constructor(
private val _sections = MutableSharedFlow<Set<HomeRoomSection>>(replay = 1)
val sections = _sections.asSharedFlow()
private var filteredPagedRoomSummariesLive: UpdatableLivePageResult? = null
init {
configureSections()
}
@ -81,7 +88,7 @@ class HomeRoomListViewModel @AssistedInject constructor(
val newSections = mutableSetOf<HomeRoomSection>()
newSections.add(getRecentRoomsSection())
newSections.add(getAllRoomsSection())
newSections.add(getFilteredRoomsSection())
viewModelScope.launch {
_sections.emit(newSections)
@ -104,15 +111,21 @@ class HomeRoomListViewModel @AssistedInject constructor(
)
}
private fun getAllRoomsSection(): HomeRoomSection.RoomSummaryData {
private fun getFilteredRoomsSection(): HomeRoomSection.RoomSummaryData {
val builder = RoomSummaryQueryParams.Builder().also {
it.memberships = listOf(Membership.JOIN)
}
val filteredPagedRoomSummariesLive = session.roomService().getFilteredPagedRoomSummariesLive(
builder.build(),
pagedListConfig
)
val params = getFilteredQueryParams(HomeRoomFilter.ALL, builder.build())
val sortOrder = RoomSortOrder.ACTIVITY // #6506
val liveResults = session.roomService().getFilteredPagedRoomSummariesLive(
params,
pagedListConfig,
sortOrder
).also {
this.filteredPagedRoomSummariesLive = it
}
spaceStateHandler.getSelectedSpaceFlow()
.distinctUntilChanged()
@ -121,20 +134,83 @@ class HomeRoomListViewModel @AssistedInject constructor(
}
.onEach { selectedSpaceOption ->
val selectedSpace = selectedSpaceOption.orNull()
filteredPagedRoomSummariesLive.queryParams = filteredPagedRoomSummariesLive.queryParams.copy(
spaceFilter = getSpaceFilter(selectedSpaceId = selectedSpace?.roomId)
liveResults.queryParams = liveResults.queryParams.copy(
spaceFilter = selectedSpace?.roomId.toActiveSpaceOrNoFilter()
)
}.launchIn(viewModelScope)
return HomeRoomSection.RoomSummaryData(
list = filteredPagedRoomSummariesLive.livePagedList
list = liveResults.livePagedList,
showFilters = true, // #6506
filtersData = getFiltersDataFlow()
)
}
private fun getSpaceFilter(selectedSpaceId: String?): SpaceFilter {
return when {
vectorPreferences.prefSpacesShowAllRoomInHome() -> selectedSpaceId.toActiveSpaceOrNoFilter()
else -> selectedSpaceId.toActiveSpaceOrOrphanRooms()
private fun getFiltersDataFlow(): SharedFlow<List<HomeRoomFilter>> {
val flow = MutableSharedFlow<List<HomeRoomFilter>>(replay = 1)
val favouritesFlow = session.flow()
.liveRoomSummaries(
RoomSummaryQueryParams.Builder().also { builder ->
builder.roomTagQueryFilter = RoomTagQueryFilter(true, null, null)
}.build()
)
.map { it.isNotEmpty() }
.distinctUntilChanged()
val dmsFLow = session.flow()
.liveRoomSummaries(
RoomSummaryQueryParams.Builder().also { builder ->
builder.memberships = listOf(Membership.JOIN)
builder.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
}.build()
)
.map { it.isNotEmpty() }
.distinctUntilChanged()
favouritesFlow.combine(dmsFLow) { hasFavourite, hasDm ->
hasFavourite to hasDm
}.onEach { (hasFavourite, hasDm) ->
val filtersData = mutableListOf(
HomeRoomFilter.ALL,
HomeRoomFilter.UNREADS
)
if (hasFavourite) {
filtersData.add(
HomeRoomFilter.FAVOURITES
)
}
if (hasDm) {
filtersData.add(
HomeRoomFilter.PEOPlE
)
}
flow.emit(filtersData)
}.launchIn(viewModelScope)
return flow
}
private fun getFilteredQueryParams(filter: HomeRoomFilter, currentParams: RoomSummaryQueryParams): RoomSummaryQueryParams {
return when (filter) {
HomeRoomFilter.ALL -> currentParams.copy(
roomCategoryFilter = null,
roomTagQueryFilter = null
)
HomeRoomFilter.UNREADS -> currentParams.copy(
roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS,
roomTagQueryFilter = RoomTagQueryFilter(null, false, null)
)
HomeRoomFilter.FAVOURITES ->
currentParams.copy(
roomCategoryFilter = null,
roomTagQueryFilter = RoomTagQueryFilter(true, null, null)
)
HomeRoomFilter.PEOPlE -> currentParams.copy(
roomCategoryFilter = RoomCategoryFilter.ONLY_DM,
roomTagQueryFilter = null
)
}
}
@ -144,6 +220,13 @@ class HomeRoomListViewModel @AssistedInject constructor(
is HomeRoomListAction.LeaveRoom -> handleLeaveRoom(action)
is HomeRoomListAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action)
is HomeRoomListAction.ToggleTag -> handleToggleTag(action)
is HomeRoomListAction.ChangeRoomFilter -> handleChangeRoomFilter(action)
}
}
private fun handleChangeRoomFilter(action: HomeRoomListAction.ChangeRoomFilter) {
filteredPagedRoomSummariesLive?.let { liveResults ->
liveResults.queryParams = getFilteredQueryParams(action.filter, liveResults.queryParams)
}
}

View file

@ -18,11 +18,15 @@ package im.vector.app.features.home.room.list.home
import androidx.lifecycle.LiveData
import androidx.paging.PagedList
import im.vector.app.features.home.room.list.home.filter.HomeRoomFilter
import kotlinx.coroutines.flow.SharedFlow
import org.matrix.android.sdk.api.session.room.model.RoomSummary
sealed class HomeRoomSection {
data class RoomSummaryData(
val list: LiveData<PagedList<RoomSummary>>
val list: LiveData<PagedList<RoomSummary>>,
val showFilters: Boolean,
val filtersData: SharedFlow<List<HomeRoomFilter>>
) : HomeRoomSection()
data class RecentRoomsData(

View file

@ -0,0 +1,70 @@
/*
* 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.list.home.filter
import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.paging.PagedListEpoxyController
import im.vector.app.core.utils.createUIHandler
import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.home.room.list.RoomListListener
import im.vector.app.features.home.room.list.RoomSummaryItemFactory
import im.vector.app.features.home.room.list.RoomSummaryItemPlaceHolder_
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.RoomSummary
class HomeFilteredRoomsController(
private val roomSummaryItemFactory: RoomSummaryItemFactory,
private val showFilters: Boolean,
) : PagedListEpoxyController<RoomSummary>(
// Important it must match the PageList builder notify Looper
modelBuildingHandler = createUIHandler()
) {
private var roomChangeMembershipStates: Map<String, ChangeMembershipState>? = null
set(value) {
field = value
// ideally we could search for visible models and update only those
requestForcedModelBuild()
}
var listener: RoomListListener? = null
var onFilterChanged: ((HomeRoomFilter) -> Unit)? = null
private var filtersData: List<HomeRoomFilter>? = null
override fun addModels(models: List<EpoxyModel<*>>) {
val host = this
if (showFilters) {
roomFilterHeaderItem {
id("filter_header")
filtersData(host.filtersData)
onFilterChangedListener(host.onFilterChanged)
}
}
super.addModels(models)
}
fun submitFiltersData(data: List<HomeRoomFilter>) {
this.filtersData = data
requestForcedModelBuild()
}
override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> {
item ?: return RoomSummaryItemPlaceHolder_().apply { id(currentPosition) }
return roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), RoomListDisplayMode.ROOMS, listener)
}
}

View file

@ -0,0 +1,27 @@
/*
* 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.list.home.filter
import androidx.annotation.StringRes
import im.vector.app.R
enum class HomeRoomFilter(@StringRes val titleRes: Int) {
ALL(R.string.room_list_filter_all),
UNREADS(R.string.room_list_filter_unreads),
FAVOURITES(R.string.room_list_filter_favourites),
PEOPlE(R.string.room_list_filter_people),
}

View file

@ -0,0 +1,65 @@
/*
* 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.list.home.filter
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import com.google.android.material.tabs.TabLayout
import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
@EpoxyModelClass
abstract class RoomFilterHeaderItem : VectorEpoxyModel<RoomFilterHeaderItem.Holder>(R.layout.item_home_filter_tabs) {
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var onFilterChangedListener: ((HomeRoomFilter) -> Unit)? = null
@EpoxyAttribute
var filtersData: List<HomeRoomFilter>? = null
override fun bind(holder: Holder) {
super.bind(holder)
with(holder.tabLayout) {
removeAllTabs()
filtersData?.forEach { filter ->
addTab(newTab().setText(filter.titleRes).setTag(filter))
}
addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
(tab?.tag as? HomeRoomFilter)?.let { filter ->
onFilterChangedListener?.invoke(filter)
}
}
override fun onTabUnselected(tab: TabLayout.Tab?) = Unit
override fun onTabReselected(tab: TabLayout.Tab?) = Unit
})
}
}
override fun unbind(holder: Holder) {
holder.tabLayout.clearOnTabSelectedListeners()
super.unbind(holder)
}
class Holder : VectorEpoxyHolder() {
val tabLayout by bind<TabLayout>(R.id.home_filter_tabs_tabs)
}
}

View file

@ -34,6 +34,7 @@ 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.debug.DebugNavigator
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.error.fatalError
import im.vector.app.features.VectorFeatures
@ -51,7 +52,6 @@ import im.vector.app.features.crypto.recover.BootstrapBottomSheet
import im.vector.app.features.crypto.recover.SetupMode
import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider
import im.vector.app.features.crypto.verification.VerificationBottomSheet
import im.vector.app.features.debug.DebugMenuActivity
import im.vector.app.features.devtools.RoomDevToolActivity
import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.home.room.detail.arguments.TimelineArgs
@ -123,7 +123,8 @@ class DefaultNavigator @Inject constructor(
private val spaceStateHandler: SpaceStateHandler,
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider,
private val features: VectorFeatures,
private val analyticsTracker: AnalyticsTracker
private val analyticsTracker: AnalyticsTracker,
private val debugNavigator: DebugNavigator,
) : Navigator {
override fun openLogin(context: Context, loginConfig: LoginConfig?, flags: Int) {
@ -367,7 +368,7 @@ class DefaultNavigator @Inject constructor(
}
override fun openDebug(context: Context) {
context.startActivity(Intent(context, DebugMenuActivity::class.java))
debugNavigator.openDebugMenu(context)
}
override fun openKeysBackupSetup(context: Context, showManualExport: Boolean) {

View file

@ -65,9 +65,10 @@ class NotifiableEventResolver @Inject constructor(
private val buildMeta: BuildMeta,
) {
// private val eventDisplay = RiotEventDisplay(context)
private val nonEncryptedNotifiableEventTypes: List<String> =
listOf(EventType.MESSAGE) + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO
suspend fun resolveEvent(event: Event/*, roomState: RoomState?, bingRule: PushRule?*/, session: Session, isNoisy: Boolean): NotifiableEvent? {
suspend fun resolveEvent(event: Event, session: Session, isNoisy: Boolean): NotifiableEvent? {
val roomID = event.roomId ?: return null
val eventId = event.eventId ?: return null
if (event.getClearType() == EventType.STATE_ROOM_MEMBER) {
@ -75,7 +76,7 @@ class NotifiableEventResolver @Inject constructor(
}
val timelineEvent = session.getRoom(roomID)?.getTimelineEvent(eventId) ?: return null
return when (event.getClearType()) {
EventType.MESSAGE,
in nonEncryptedNotifiableEventTypes,
EventType.ENCRYPTED -> {
resolveMessageEvent(timelineEvent, session, canBeReplaced = false, isNoisy = isNoisy)
}
@ -161,9 +162,7 @@ class NotifiableEventResolver @Inject constructor(
event.attemptToDecryptIfNeeded(session)
// only convert encrypted messages to NotifiableMessageEvents
when (event.root.getClearType()) {
EventType.MESSAGE,
in EventType.POLL_START,
in EventType.STATE_ROOM_BEACON_INFO -> {
in nonEncryptedNotifiableEventTypes -> {
val body = displayableEventFormatter.format(event, isDm = room.roomSummary()?.isDirect.orFalse(), appendAuthor = false).toString()
val roomName = room.roomSummary()?.displayName ?: ""
val senderDisplayName = event.senderInfo.disambiguatedDisplayName

View file

@ -64,8 +64,6 @@ class LockScreenViewModel @AssistedInject constructor(
private val biometricHelper = biometricHelperFactory.create(initialState.lockScreenConfiguration)
private var firstEnteredCode: String? = null
// BiometricPrompt will automatically disable system auth after too many failed auth attempts
private var isSystemAuthTemporarilyDisabledByBiometricPrompt = false
@ -108,18 +106,17 @@ class LockScreenViewModel @AssistedInject constructor(
val state = awaitState()
when (state.lockScreenConfiguration.mode) {
LockScreenMode.CREATE -> {
if (firstEnteredCode == null && state.lockScreenConfiguration.needsNewCodeValidation) {
firstEnteredCode = code
_viewEvents.post(LockScreenViewEvent.ClearPinCode(false))
emit(PinCodeState.FirstCodeEntered)
val enteredPinCode = (state.pinCodeState as? PinCodeState.FirstCodeEntered)?.pinCode
if (enteredPinCode == null && state.lockScreenConfiguration.needsNewCodeValidation) {
_viewEvents.post(LockScreenViewEvent.ClearPinCode(confirmationFailed = false))
emit(PinCodeState.FirstCodeEntered(code))
} else {
if (!state.lockScreenConfiguration.needsNewCodeValidation || code == firstEnteredCode) {
if (!state.lockScreenConfiguration.needsNewCodeValidation || code == enteredPinCode) {
pinCodeHelper.createPinCode(code)
_viewEvents.post(LockScreenViewEvent.CodeCreationComplete)
emit(null)
} else {
firstEnteredCode = null
_viewEvents.post(LockScreenViewEvent.ClearPinCode(true))
_viewEvents.post(LockScreenViewEvent.ClearPinCode(confirmationFailed = true))
emit(PinCodeState.Idle)
}
}
@ -137,7 +134,9 @@ class LockScreenViewModel @AssistedInject constructor(
}.catch { error ->
_viewEvents.post(LockScreenViewEvent.AuthError(AuthMethod.PIN_CODE, error))
}.onEach { newPinState ->
newPinState?.let { setState { copy(pinCodeState = it) } }
if (newPinState != null) {
setState { copy(pinCodeState = newPinState) }
}
}.launchIn(viewModelScope)
@SuppressLint("NewApi")

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