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) 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.pinterest.ktlint',
'com.posthog.android', 'com.posthog.android',
'com.squareup', 'com.squareup',
'com.squareup.curtains',
'com.squareup.duktape', 'com.squareup.duktape',
'com.squareup.leakcanary',
'com.squareup.moshi', 'com.squareup.moshi',
'com.squareup.okhttp3', 'com.squareup.okhttp3',
'com.squareup.okio', 'com.squareup.okio',

View File

@ -23,6 +23,7 @@ Here are the checks that Danger does so far:
- PR description is not empty - PR description is not empty
- Big PR got a warning to recommend to split - Big PR got a warning to recommend to split
- PR contains a file for towncrier and extension is checked - 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 contains a Sign-Off, with exception for Element employee contributors
- PR with change on layout should include screenshot in the description - PR with change on layout should include screenshot in the description
- PR which adds png file warn about the usage of vector drawables - 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 distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionSha256Sum=97a52d145762adc241bad7fd18289bf7f6801e08ece6badf80402fe2b9f250b1 distributionSha256Sum=db9c8211ed63f61f60292c69e80d89196f9eb36665e369e7f00ac4cc841c2219
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists 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. // that the app's state is completely cleared between tests.
testInstrumentationRunnerArguments clearPackageData: 'true' 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", "\"${gitRevision()}\""
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\"" 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"] // Required. Whether guests can join the room. One of: ["can_join", "forbidden"]
@Json(name = "guest_access") val guestAccessStr: String? = null @Json(name = "guest_access") val guestAccessStr: String? = null
) { ) {
val guestAccess: GuestAccess? = when (guestAccessStr) { val guestAccess: GuestAccess? = GuestAccess.values()
"can_join" -> GuestAccess.CanJoin .find { it.value == guestAccessStr }
"forbidden" -> GuestAccess.Forbidden ?: run {
else -> { Timber.w("Invalid value for GuestAccess: `$guestAccessStr`")
Timber.w("Invalid value for GuestAccess: `$guestAccessStr`") null
null }
}
}
} }
@JsonClass(generateAdapter = false) @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 * Ref: https://matrix.org/docs/spec/client_server/latest#room-history-visibility
*/ */
@JsonClass(generateAdapter = false) @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 * 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. * 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 * 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. * 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 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. * 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 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. * 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( data class RoomHistoryVisibilityContent(
@Json(name = "history_visibility") val historyVisibilityStr: String? = null @Json(name = "history_visibility") val historyVisibilityStr: String? = null
) { ) {
val historyVisibility: RoomHistoryVisibility? = when (historyVisibilityStr) { val historyVisibility: RoomHistoryVisibility? = RoomHistoryVisibility.values()
"world_readable" -> RoomHistoryVisibility.WORLD_READABLE .find { it.value == historyVisibilityStr }
"shared" -> RoomHistoryVisibility.SHARED ?: run {
"invited" -> RoomHistoryVisibility.INVITED Timber.w("Invalid value for RoomHistoryVisibility: `$historyVisibilityStr`")
"joined" -> RoomHistoryVisibility.JOINED null
else -> { }
Timber.w("Invalid value for RoomHistoryVisibility: `$historyVisibilityStr`")
null
}
}
} }

View File

@ -16,7 +16,6 @@
package org.matrix.android.sdk.api.session.room.model.create 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.EventType
import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
@ -30,7 +29,7 @@ interface RoomFeaturePreset {
fun updateRoomParams(params: CreateRoomParams) fun updateRoomParams(params: CreateRoomParams)
fun setupInitialStates(): List<Event>? fun setupInitialStates(): List<CreateRoomStateEvent>?
} }
class RestrictedRoomPreset(val homeServerCapabilities: HomeServerCapabilities, val restrictedList: List<RoomJoinRulesAllowEntry>) : RoomFeaturePreset { 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) params.roomVersion = homeServerCapabilities.versionOverrideForFeature(HomeServerCapabilities.ROOM_CAP_RESTRICTED)
} }
override fun setupInitialStates(): List<Event>? { override fun setupInitialStates(): List<CreateRoomStateEvent> {
return listOf( return listOf(
Event( CreateRoomStateEvent(
type = EventType.STATE_ROOM_JOIN_RULES, type = EventType.STATE_ROOM_JOIN_RULES,
stateKey = "", stateKey = "",
content = RoomJoinRulesContent( 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 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] * [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. * 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.extensions.tryOrNull
import org.matrix.android.sdk.api.session.events.model.Event 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.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.IdentityServiceError
import org.matrix.android.sdk.api.session.identity.toMedium 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.session.room.model.create.CreateRoomParams
import org.matrix.android.sdk.api.util.MimeTypes import org.matrix.android.sdk.api.util.MimeTypes
import org.matrix.android.sdk.internal.crypto.DeviceListManager import org.matrix.android.sdk.internal.crypto.DeviceListManager
@ -78,7 +83,7 @@ internal class CreateRoomBodyBuilder @Inject constructor(
buildAvatarEvent(params), buildAvatarEvent(params),
buildGuestAccess(params) buildGuestAccess(params)
) + ) +
params.featurePreset?.setupInitialStates().orEmpty() + buildFeaturePresetInitialStates(params) +
buildCustomInitialStates(params) buildCustomInitialStates(params)
) )
.takeIf { it.isNotEmpty() } .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> { private fun buildCustomInitialStates(params: CreateRoomParams): List<Event> {
return params.initialStates.map { return params.initialStates.map {
Event( Event(
@ -123,7 +138,7 @@ internal class CreateRoomBodyBuilder @Inject constructor(
Event( Event(
type = EventType.STATE_ROOM_AVATAR, type = EventType.STATE_ROOM_AVATAR,
stateKey = "", stateKey = "",
content = mapOf("url" to response.contentUri) content = RoomAvatarContent(response.contentUri).toContent()
) )
} }
} }
@ -134,7 +149,7 @@ internal class CreateRoomBodyBuilder @Inject constructor(
Event( Event(
type = EventType.STATE_ROOM_HISTORY_VISIBILITY, type = EventType.STATE_ROOM_HISTORY_VISIBILITY,
stateKey = "", stateKey = "",
content = mapOf("history_visibility" to it) content = RoomHistoryVisibilityContent(it.value).toContent()
) )
} }
} }
@ -145,7 +160,7 @@ internal class CreateRoomBodyBuilder @Inject constructor(
Event( Event(
type = EventType.STATE_ROOM_GUEST_ACCESS, type = EventType.STATE_ROOM_GUEST_ACCESS,
stateKey = "", stateKey = "",
content = mapOf("guest_access" to it.value) content = RoomGuestAccessContent(it.value).toContent()
) )
} }
} }
@ -167,7 +182,7 @@ internal class CreateRoomBodyBuilder @Inject constructor(
Event( Event(
type = EventType.STATE_ROOM_ENCRYPTION, type = EventType.STATE_ROOM_ENCRYPTION,
stateKey = "", stateKey = "",
content = mapOf("algorithm" to it) content = EncryptionEventContent(it).toContent()
) )
} }
} }

View File

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

View File

@ -9,6 +9,8 @@
<activity android:name=".features.debug.settings.DebugPrivateSettingsActivity" /> <activity android:name=".features.debug.settings.DebugPrivateSettingsActivity" />
<activity android:name=".features.debug.sas.DebugSasEmojiActivity" /> <activity android:name=".features.debug.sas.DebugSasEmojiActivity" />
<activity android:name=".features.debug.features.DebugFeaturesSettingsActivity" /> <activity android:name=".features.debug.features.DebugFeaturesSettingsActivity" />
<activity android:name=".features.debug.DebugMenuActivity" />
<activity android:name=".features.debug.leak.DebugMemoryLeaksActivity" />
<activity <activity
android:name="com.facebook.flipper.android.diagnostics.FlipperDiagnosticActivity" 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.databinding.ActivityDebugMenuBinding
import im.vector.app.features.debug.analytics.DebugAnalyticsActivity import im.vector.app.features.debug.analytics.DebugAnalyticsActivity
import im.vector.app.features.debug.features.DebugFeaturesSettingsActivity 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.sas.DebugSasEmojiActivity
import im.vector.app.features.debug.settings.DebugPrivateSettingsActivity import im.vector.app.features.debug.settings.DebugPrivateSettingsActivity
import im.vector.app.features.qrcode.QrCodeScannerActivity import im.vector.app.features.qrcode.QrCodeScannerActivity
@ -86,6 +87,7 @@ class DebugMenuActivity : VectorBaseActivity<ActivityDebugMenuBinding>() {
views.debugAnalytics.setOnClickListener { views.debugAnalytics.setOnClickListener {
startActivity(Intent(this, DebugAnalyticsActivity::class.java)) startActivity(Intent(this, DebugAnalyticsActivity::class.java))
} }
views.debugMemoryLeaks.setOnClickListener { openMemoryLeaksSettings() }
views.debugTestTextViewLink.setOnClickListener { testTextViewLink() } views.debugTestTextViewLink.setOnClickListener { testTextViewLink() }
views.debugOpenButtonStylesLight.setOnClickListener { views.debugOpenButtonStylesLight.setOnClickListener {
startActivity(Intent(this, DebugVectorButtonStylesLightActivity::class.java)) startActivity(Intent(this, DebugVectorButtonStylesLightActivity::class.java))
@ -130,6 +132,10 @@ class DebugMenuActivity : VectorBaseActivity<ActivityDebugMenuBinding>() {
startActivity(Intent(this, DebugPrivateSettingsActivity::class.java)) startActivity(Intent(this, DebugPrivateSettingsActivity::class.java))
} }
private fun openMemoryLeaksSettings() {
startActivity(Intent(this, DebugMemoryLeaksActivity::class.java))
}
private fun renderQrCode(text: String) { private fun renderQrCode(text: String) {
views.debugQrCode.setData(text) 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.MavericksViewModelComponent
import im.vector.app.core.di.MavericksViewModelKey import im.vector.app.core.di.MavericksViewModelKey
import im.vector.app.features.debug.analytics.DebugAnalyticsViewModel import im.vector.app.features.debug.analytics.DebugAnalyticsViewModel
import im.vector.app.features.debug.leak.DebugMemoryLeaksViewModel
import im.vector.app.features.debug.settings.DebugPrivateSettingsViewModel import im.vector.app.features.debug.settings.DebugPrivateSettingsViewModel
@InstallIn(MavericksViewModelComponent::class) @InstallIn(MavericksViewModelComponent::class)
@ -39,4 +40,9 @@ interface MavericksViewModelDebugModule {
@IntoMap @IntoMap
@MavericksViewModelKey(DebugPrivateSettingsViewModel::class) @MavericksViewModelKey(DebugPrivateSettingsViewModel::class)
fun debugPrivateSettingsViewModelFactory(factory: DebugPrivateSettingsViewModel.Factory): MavericksAssistedViewModelFactory<*, *> 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, key = DebugFeatureKeys.newAppLayoutEnabled,
factory = VectorFeatures::isNewAppLayoutEnabled 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) override fun isNewAppLayoutEnabled(): Boolean = read(DebugFeatureKeys.newAppLayoutEnabled)
?: vectorFeatures.isNewAppLayoutEnabled() ?: vectorFeatures.isNewAppLayoutEnabled()
override fun isNewDeviceManagementEnabled(): Boolean = read(DebugFeatureKeys.newDeviceManagementEnabled)
?: vectorFeatures.isNewDeviceManagementEnabled()
fun <T> override(value: T?, key: Preferences.Key<T>) = updatePreferences { fun <T> override(value: T?, key: Preferences.Key<T>) = updatePreferences {
if (value == null) { if (value == null) {
it.remove(key) it.remove(key)
@ -139,4 +142,5 @@ object DebugFeatureKeys {
val forceUsageOfOpusEncoder = booleanPreferencesKey("force-usage-of-opus-encoder") val forceUsageOfOpusEncoder = booleanPreferencesKey("force-usage-of-opus-encoder")
val startDmOnFirstMsg = booleanPreferencesKey("start-dm-on-first-msg") val startDmOnFirstMsg = booleanPreferencesKey("start-dm-on-first-msg")
val newAppLayoutEnabled = booleanPreferencesKey("new-app-layout-enabled") 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.facebook.soloader.SoLoader
import com.kgurgul.flipper.RealmDatabaseDriver import com.kgurgul.flipper.RealmDatabaseDriver
import com.kgurgul.flipper.RealmDatabaseProvider import com.kgurgul.flipper.RealmDatabaseProvider
import im.vector.app.core.debug.FlipperProxy
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import okhttp3.Interceptor
import org.matrix.android.sdk.api.Matrix import org.matrix.android.sdk.api.Matrix
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class FlipperProxy @Inject constructor( class VectorFlipperProxy @Inject constructor(
private val context: Context, private val context: Context,
) { ) : FlipperProxy {
private val networkFlipperPlugin = NetworkFlipperPlugin() private val networkFlipperPlugin = NetworkFlipperPlugin()
fun init(matrix: Matrix) { override fun init(matrix: Matrix) {
SoLoader.init(context, false) SoLoader.init(context, false)
if (FlipperUtils.shouldEnableFlipper(context)) { if (FlipperUtils.shouldEnableFlipper(context)) {
@ -65,8 +65,5 @@ class FlipperProxy @Inject constructor(
} }
} }
@Suppress("RedundantNullableReturnType") override fun networkInterceptor() = FlipperOkhttpInterceptor(networkFlipperPlugin)
fun getNetworkInterceptor(): Interceptor? {
return 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.IntentFilter
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.core.content.edit import androidx.core.content.edit
import im.vector.app.core.debug.DebugReceiver
import im.vector.app.core.di.DefaultSharedPreferences import im.vector.app.core.di.DefaultSharedPreferences
import im.vector.app.core.utils.lsFiles import im.vector.app.core.utils.lsFiles
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject
/** /**
* Receiver to handle some command from ADB * 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) { override fun onReceive(context: Context, intent: Intent) {
Timber.v("Received debug action: ${intent.action}") Timber.v("Received debug action: ${intent.action}")

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <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" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/coordinatorLayout" android:id="@+id/coordinatorLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -8,8 +9,11 @@
tools:ignore="HardcodedText"> tools:ignore="HardcodedText">
<ScrollView <ScrollView
android:id="@+id/scrollView2"
android:layout_width="match_parent" 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 <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -38,6 +42,12 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Analytics" /> 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 <Button
android:id="@+id/debug_test_text_view_link" android:id="@+id/debug_test_text_view_link"
android:layout_width="wrap_content" 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 package im.vector.app.di
import android.content.Context import android.content.Context
import dagger.Binds
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import im.vector.app.core.pushers.FcmHelper
import im.vector.app.core.services.GuardServiceStarter import im.vector.app.core.services.GuardServiceStarter
import im.vector.app.fdroid.service.FDroidGuardServiceStarter 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.VectorPreferences
import im.vector.app.features.settings.legals.FlavorLegals
import im.vector.app.push.fcm.FdroidFcmHelper
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
@Module @Module
object FlavorModule { abstract class FlavorModule {
@Provides companion object {
fun provideGuardServiceStarter(preferences: VectorPreferences, appContext: Context): GuardServiceStarter { @Provides
return FDroidGuardServiceStarter(preferences, appContext) 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.app.Activity
import android.content.Context import android.content.Context
import im.vector.app.core.di.ActiveSessionHolder 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.pushers.PushersManager
import im.vector.app.fdroid.BackgroundSyncStarter import im.vector.app.fdroid.BackgroundSyncStarter
import im.vector.app.fdroid.receiver.AlarmSyncBroadcastReceiver 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. * This class has an alter ego in the gplay variant.
*/ */
class FcmHelper @Inject constructor( class FdroidFcmHelper @Inject constructor(
private val context: Context, private val context: Context,
private val backgroundSyncStarter: BackgroundSyncStarter, private val backgroundSyncStarter: BackgroundSyncStarter,
) { ) : FcmHelper {
fun isFirebaseAvailable(): Boolean = false override fun isFirebaseAvailable(): Boolean = false
/** override fun getFcmToken(): String? {
* Retrieves the FCM registration token.
*
* @return the FCM token or null if not received from FCM
*/
fun getFcmToken(): String? {
return null return null
} }
/** override fun storeFcmToken(token: String?) {
* Store FCM token to the SharedPrefs
*
* @param token the token to store
*/
fun storeFcmToken(token: String?) {
// No op // No op
} }
/** override fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) {
* 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) {
// No op // No op
} }
fun onEnterForeground(activeSessionHolder: ActiveSessionHolder) { override fun onEnterForeground(activeSessionHolder: ActiveSessionHolder) {
// try to stop all regardless of background mode // try to stop all regardless of background mode
activeSessionHolder.getSafeActiveSession()?.syncService()?.stopAnyBackgroundSync() activeSessionHolder.getSafeActiveSession()?.syncService()?.stopAnyBackgroundSync()
AlarmSyncBroadcastReceiver.cancelAlarm(context) AlarmSyncBroadcastReceiver.cancelAlarm(context)
} }
fun onEnterBackground(activeSessionHolder: ActiveSessionHolder) { override fun onEnterBackground(activeSessionHolder: ActiveSessionHolder) {
backgroundSyncStarter.start(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.TestBackgroundRestrictions
import im.vector.app.fdroid.features.settings.troubleshoot.TestBatteryOptimization import im.vector.app.fdroid.features.settings.troubleshoot.TestBatteryOptimization
import im.vector.app.features.VectorFeatures 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.NotificationTroubleshootTestManager
import im.vector.app.features.settings.troubleshoot.TestAccountSettings import im.vector.app.features.settings.troubleshoot.TestAccountSettings
import im.vector.app.features.settings.troubleshoot.TestAvailableUnifiedPushDistributors 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 im.vector.app.features.settings.troubleshoot.TestUnifiedPushGateway
import javax.inject.Inject import javax.inject.Inject
class NotificationTroubleshootTestManagerFactory @Inject constructor( class FdroidNotificationTroubleshootTestManagerFactory @Inject constructor(
private val unifiedPushHelper: UnifiedPushHelper, private val unifiedPushHelper: UnifiedPushHelper,
private val testSystemSettings: TestSystemSettings, private val testSystemSettings: TestSystemSettings,
private val testAccountSettings: TestAccountSettings, private val testAccountSettings: TestAccountSettings,
@ -52,9 +53,9 @@ class NotificationTroubleshootTestManagerFactory @Inject constructor(
private val testBatteryOptimization: TestBatteryOptimization, private val testBatteryOptimization: TestBatteryOptimization,
private val testNotification: TestNotification, private val testNotification: TestNotification,
private val vectorFeatures: VectorFeatures, private val vectorFeatures: VectorFeatures,
) { ) : NotificationTroubleshootTestManagerFactory {
fun create(fragment: Fragment): NotificationTroubleshootTestManager { override fun create(fragment: Fragment): NotificationTroubleshootTestManager {
val mgr = NotificationTroubleshootTestManager(fragment) val mgr = NotificationTroubleshootTestManager(fragment)
mgr.addTest(testSystemSettings) mgr.addTest(testSystemSettings)
mgr.addTest(testAccountSettings) 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 package im.vector.app.di
import dagger.Binds
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent 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.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) @InstallIn(SingletonComponent::class)
@Module @Module
object FlavorModule { abstract class FlavorModule {
@Provides companion object {
fun provideGuardServiceStarter(): GuardServiceStarter { @Provides
return object : GuardServiceStarter {} 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 androidx.fragment.app.FragmentActivity
import com.google.firebase.messaging.FirebaseMessaging import com.google.firebase.messaging.FirebaseMessaging
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.pushers.FcmHelper
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.startAddGoogleAccountIntent import im.vector.app.core.utils.startAddGoogleAccountIntent
import im.vector.app.features.settings.troubleshoot.TroubleshootTest import im.vector.app.features.settings.troubleshoot.TroubleshootTest
import im.vector.app.push.fcm.FcmHelper
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject

View File

@ -23,10 +23,10 @@ import androidx.work.WorkInfo
import androidx.work.WorkManager import androidx.work.WorkManager
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder 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.pushers.PushersManager
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.settings.troubleshoot.TroubleshootTest 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 org.matrix.android.sdk.api.session.pushers.PusherState
import javax.inject.Inject 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.BuildConfig
import im.vector.app.core.di.DefaultPreferences import im.vector.app.core.di.DefaultPreferences
import im.vector.app.core.time.Clock import im.vector.app.core.time.Clock
import im.vector.app.features.home.NightlyProxy
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class NightlyProxy @Inject constructor( class FirebaseNightlyProxy @Inject constructor(
private val clock: Clock, private val clock: Clock,
@DefaultPreferences @DefaultPreferences
private val sharedPreferences: SharedPreferences, private val sharedPreferences: SharedPreferences,
) { ) : NightlyProxy {
fun onHomeResumed() {
override fun onHomeResumed() {
if (!canDisplayPopup()) return if (!canDisplayPopup()) return
val firebaseAppDistribution = FirebaseAppDistribution.getInstance() val firebaseAppDistribution = FirebaseAppDistribution.getInstance()
firebaseAppDistribution.updateIfNewReleaseAvailable() firebaseAppDistribution.updateIfNewReleaseAvailable()

View File

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

View File

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

View File

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

View File

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

View File

@ -14,10 +14,10 @@
* limitations under the License. * 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() { interface DebugNavigator {
fun onHomeResumed() = Unit 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,9 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.app package im.vector.app.core.debug
import android.content.Context import android.content.Context
// No op interface DebugReceiver {
fun openOssLicensesMenuActivity(@Suppress("UNUSED_PARAMETER") context: Context) = Unit fun register(context: Context)
fun unregister(context: Context)
}

View File

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

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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,9 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.app.features.debug package im.vector.app.core.debug
import androidx.appcompat.app.AppCompatActivity /**
* Used for memory leak analysis control.
// This activity is not accessible */
class DebugMenuActivity : AppCompatActivity() 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.SpaceStateHandler
import im.vector.app.SpaceStateHandlerImpl import im.vector.app.SpaceStateHandlerImpl
import im.vector.app.config.Config 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.dispatchers.CoroutineDispatchers
import im.vector.app.core.error.DefaultErrorFormatter import im.vector.app.core.error.DefaultErrorFormatter
import im.vector.app.core.error.ErrorFormatter 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.settings.VectorPreferences
import im.vector.app.features.ui.SharedPreferencesUiStateRepository import im.vector.app.features.ui.SharedPreferencesUiStateRepository
import im.vector.app.features.ui.UiStateRepository import im.vector.app.features.ui.UiStateRepository
import im.vector.app.flipper.FlipperProxy
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -144,7 +144,7 @@ object VectorStaticModule {
roomDisplayNameFallbackProvider = vectorRoomDisplayNameFallbackProvider, roomDisplayNameFallbackProvider = vectorRoomDisplayNameFallbackProvider,
threadMessagesEnabledDefault = vectorPreferences.areThreadMessagesEnabled(), threadMessagesEnabledDefault = vectorPreferences.areThreadMessagesEnabled(),
networkInterceptors = listOfNotNull( 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 com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.EntryPointAccessors import dagger.hilt.android.EntryPointAccessors
import im.vector.app.R 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.ActiveSessionHolder
import im.vector.app.core.di.ActivityEntryPoint import im.vector.app.core.di.ActivityEntryPoint
import im.vector.app.core.dialogs.DialogLocker 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.settings.VectorPreferences
import im.vector.app.features.themes.ActivityOtherThemes import im.vector.app.features.themes.ActivityOtherThemes
import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.themes.ThemeUtils
import im.vector.app.receivers.DebugReceiver
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.extensions.orFalse 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 buildMeta: BuildMeta
@Inject lateinit var fontScalePreferences: FontScalePreferences @Inject lateinit var fontScalePreferences: FontScalePreferences
// For debug only
@Inject lateinit var debugReceiver: DebugReceiver
@Inject @Inject
lateinit var vectorFeatures: VectorFeatures lateinit var vectorFeatures: VectorFeatures
@ -176,9 +179,6 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
private var savedInstanceState: Bundle? = null private var savedInstanceState: Bundle? = null
// For debug only
private var debugReceiver: DebugReceiver? = null
private val restorables = ArrayList<Restorable>() private val restorables = ArrayList<Restorable>()
override fun attachBaseContext(base: Context) { override fun attachBaseContext(base: Context) {
@ -418,13 +418,7 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
if (this !is BugReportActivity && vectorPreferences.useRageshake()) { if (this !is BugReportActivity && vectorPreferences.useRageshake()) {
rageShake.start() rageShake.start()
} }
DebugReceiver debugReceiver.register(this)
.getIntentFilter(this)
.takeIf { buildMeta.isDebug }
?.let {
debugReceiver = DebugReceiver()
registerReceiver(debugReceiver, it)
}
} }
private val postResumeScheduledActions = mutableListOf<() -> Unit>() private val postResumeScheduledActions = mutableListOf<() -> Unit>()
@ -454,11 +448,7 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
Timber.i("onPause Activity ${javaClass.simpleName}") Timber.i("onPause Activity ${javaClass.simpleName}")
rageShake.stop() rageShake.stop()
debugReceiver.unregister(this)
debugReceiver?.let {
unregisterReceiver(debugReceiver)
debugReceiver = null
}
} }
override fun onWindowFocusChanged(hasFocus: Boolean) { 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.VectorFeatures
import im.vector.app.features.settings.BackgroundSyncMode import im.vector.app.features.settings.BackgroundSyncMode
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import im.vector.app.push.fcm.FcmHelper
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.Matrix import org.matrix.android.sdk.api.Matrix
import org.matrix.android.sdk.api.cache.CacheStrategy import org.matrix.android.sdk.api.cache.CacheStrategy

View File

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

View File

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

View File

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

View File

@ -58,6 +58,9 @@ abstract class FormEditTextItem : VectorEpoxyModel<FormEditTextItem.Holder>(R.la
@EpoxyAttribute @EpoxyAttribute
var singleLine: Boolean = true var singleLine: Boolean = true
@EpoxyAttribute
var autoCapitalize: Boolean = false
@EpoxyAttribute @EpoxyAttribute
var imeOptions: Int? = null var imeOptions: Int? = null
@ -132,10 +135,9 @@ abstract class FormEditTextItem : VectorEpoxyModel<FormEditTextItem.Holder>(R.la
*/ */
private fun configureInputType(holder: Holder) { private fun configureInputType(holder: Holder) {
val newInputType = val newInputType =
inputType ?: when (singleLine) { inputType ?: InputType.TYPE_CLASS_TEXT
true -> InputType.TYPE_CLASS_TEXT .let { if (autoCapitalize) it or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES else it }
false -> InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE .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 // This is a must in order to avoid extreme lag in some devices, on fast typing
if (holder.textInputEditText.inputType != newInputType) { 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.extensions.validateBackPressed
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.platform.VectorMenuProvider 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.PushersManager
import im.vector.app.core.pushers.UnifiedPushHelper import im.vector.app.core.pushers.UnifiedPushHelper
import im.vector.app.core.utils.startSharePlainTextIntent 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.spaces.share.ShareSpaceBottomSheet
import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.themes.ThemeUtils
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel 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.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch 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 package im.vector.app.features.home.room.list.home
import im.vector.app.core.platform.VectorViewModelAction 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.model.RoomSummary
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState 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 ChangeRoomNotificationState(val roomId: String, val notificationState: RoomNotificationState) : HomeRoomListAction()
data class ToggleTag(val roomId: String, val tag: String) : HomeRoomListAction() data class ToggleTag(val roomId: String, val tag: String) : HomeRoomListAction()
data class LeaveRoom(val roomId: 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.core.resources.UserPreferencesProvider
import im.vector.app.databinding.FragmentRoomListBinding import im.vector.app.databinding.FragmentRoomListBinding
import im.vector.app.features.analytics.plan.ViewRoom 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.RoomListAnimator
import im.vector.app.features.home.room.list.RoomListListener 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.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.RoomListQuickActionsBottomSheet
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction 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.RoomListQuickActionsSharedActionViewModel
import im.vector.app.features.home.room.list.actions.RoomListSharedAction 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.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.home.room.list.home.recent.RecentRoomCarouselController
import im.vector.app.features.spaces.SpaceListBottomSheet import im.vector.app.features.spaces.SpaceListBottomSheet
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
@ -227,28 +227,36 @@ class HomeRoomListFragment @Inject constructor(
.show() .show()
} }
private fun getAdapterForData(data: HomeRoomSection): EpoxyControllerAdapter { private fun getAdapterForData(section: HomeRoomSection): EpoxyControllerAdapter {
return when (data) { return when (section) {
is HomeRoomSection.RoomSummaryData -> { is HomeRoomSection.RoomSummaryData -> {
RoomSummaryPagedController( HomeFilteredRoomsController(
roomSummaryItemFactory, roomSummaryItemFactory,
RoomListDisplayMode.ROOMS showFilters = section.showFilters,
).also { controller -> ).also { controller ->
controller.listener = this 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) controller.submitList(list)
} }
}.adapter }.adapter
} }
is HomeRoomSection.RecentRoomsData -> recentRoomCarouselController.also { controller -> is HomeRoomSection.RecentRoomsData -> recentRoomCarouselController.also { controller ->
controller.listener = this controller.listener = this
data.list.observe(viewLifecycleOwner) { list -> section.list.observe(viewLifecycleOwner) { list ->
controller.submitList(list) controller.submitList(list)
} }
}.adapter }.adapter
} }
} }
private fun onRoomFilterChanged(filter: HomeRoomFilter) {
roomListViewModel.handle(HomeRoomListAction.ChangeRoomFilter(filter))
}
private fun handleSelectRoom(event: HomeRoomListViewEvents.SelectRoom, isInviteAlreadyAccepted: Boolean) { private fun handleSelectRoom(event: HomeRoomListViewEvents.SelectRoom, isInviteAlreadyAccepted: Boolean) {
navigator.openRoom( navigator.openRoom(
context = requireActivity(), 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.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.StateView import im.vector.app.core.platform.StateView
import im.vector.app.core.platform.VectorViewModel 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.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.query.QueryStringValue 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.toActiveSpaceOrNoFilter
import org.matrix.android.sdk.api.query.toActiveSpaceOrOrphanRooms
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.getRoom 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.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.Membership
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag 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.roomSummaryQueryParams
import org.matrix.android.sdk.api.session.room.state.isPublic import org.matrix.android.sdk.api.session.room.state.isPublic
import org.matrix.android.sdk.flow.flow
class HomeRoomListViewModel @AssistedInject constructor( class HomeRoomListViewModel @AssistedInject constructor(
@Assisted initialState: HomeRoomListViewState, @Assisted initialState: HomeRoomListViewState,
private val session: Session, private val session: Session,
private val spaceStateHandler: SpaceStateHandler, private val spaceStateHandler: SpaceStateHandler,
private val vectorPreferences: VectorPreferences,
) : VectorViewModel<HomeRoomListViewState, HomeRoomListAction, HomeRoomListViewEvents>(initialState) { ) : VectorViewModel<HomeRoomListViewState, HomeRoomListAction, HomeRoomListViewEvents>(initialState) {
@AssistedFactory @AssistedFactory
@ -73,6 +78,8 @@ class HomeRoomListViewModel @AssistedInject constructor(
private val _sections = MutableSharedFlow<Set<HomeRoomSection>>(replay = 1) private val _sections = MutableSharedFlow<Set<HomeRoomSection>>(replay = 1)
val sections = _sections.asSharedFlow() val sections = _sections.asSharedFlow()
private var filteredPagedRoomSummariesLive: UpdatableLivePageResult? = null
init { init {
configureSections() configureSections()
} }
@ -81,7 +88,7 @@ class HomeRoomListViewModel @AssistedInject constructor(
val newSections = mutableSetOf<HomeRoomSection>() val newSections = mutableSetOf<HomeRoomSection>()
newSections.add(getRecentRoomsSection()) newSections.add(getRecentRoomsSection())
newSections.add(getAllRoomsSection()) newSections.add(getFilteredRoomsSection())
viewModelScope.launch { viewModelScope.launch {
_sections.emit(newSections) _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 { val builder = RoomSummaryQueryParams.Builder().also {
it.memberships = listOf(Membership.JOIN) it.memberships = listOf(Membership.JOIN)
} }
val filteredPagedRoomSummariesLive = session.roomService().getFilteredPagedRoomSummariesLive( val params = getFilteredQueryParams(HomeRoomFilter.ALL, builder.build())
builder.build(), val sortOrder = RoomSortOrder.ACTIVITY // #6506
pagedListConfig
) val liveResults = session.roomService().getFilteredPagedRoomSummariesLive(
params,
pagedListConfig,
sortOrder
).also {
this.filteredPagedRoomSummariesLive = it
}
spaceStateHandler.getSelectedSpaceFlow() spaceStateHandler.getSelectedSpaceFlow()
.distinctUntilChanged() .distinctUntilChanged()
@ -121,20 +134,83 @@ class HomeRoomListViewModel @AssistedInject constructor(
} }
.onEach { selectedSpaceOption -> .onEach { selectedSpaceOption ->
val selectedSpace = selectedSpaceOption.orNull() val selectedSpace = selectedSpaceOption.orNull()
filteredPagedRoomSummariesLive.queryParams = filteredPagedRoomSummariesLive.queryParams.copy( liveResults.queryParams = liveResults.queryParams.copy(
spaceFilter = getSpaceFilter(selectedSpaceId = selectedSpace?.roomId) spaceFilter = selectedSpace?.roomId.toActiveSpaceOrNoFilter()
) )
}.launchIn(viewModelScope) }.launchIn(viewModelScope)
return HomeRoomSection.RoomSummaryData( return HomeRoomSection.RoomSummaryData(
list = filteredPagedRoomSummariesLive.livePagedList list = liveResults.livePagedList,
showFilters = true, // #6506
filtersData = getFiltersDataFlow()
) )
} }
private fun getSpaceFilter(selectedSpaceId: String?): SpaceFilter { private fun getFiltersDataFlow(): SharedFlow<List<HomeRoomFilter>> {
return when { val flow = MutableSharedFlow<List<HomeRoomFilter>>(replay = 1)
vectorPreferences.prefSpacesShowAllRoomInHome() -> selectedSpaceId.toActiveSpaceOrNoFilter()
else -> selectedSpaceId.toActiveSpaceOrOrphanRooms() 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.LeaveRoom -> handleLeaveRoom(action)
is HomeRoomListAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action) is HomeRoomListAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action)
is HomeRoomListAction.ToggleTag -> handleToggleTag(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.lifecycle.LiveData
import androidx.paging.PagedList 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 import org.matrix.android.sdk.api.session.room.model.RoomSummary
sealed class HomeRoomSection { sealed class HomeRoomSection {
data class RoomSummaryData( data class RoomSummaryData(
val list: LiveData<PagedList<RoomSummary>> val list: LiveData<PagedList<RoomSummary>>,
val showFilters: Boolean,
val filtersData: SharedFlow<List<HomeRoomFilter>>
) : HomeRoomSection() ) : HomeRoomSection()
data class RecentRoomsData( 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.R
import im.vector.app.SpaceStateHandler import im.vector.app.SpaceStateHandler
import im.vector.app.config.OnboardingVariant 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.di.ActiveSessionHolder
import im.vector.app.core.error.fatalError import im.vector.app.core.error.fatalError
import im.vector.app.features.VectorFeatures 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.recover.SetupMode
import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider
import im.vector.app.features.crypto.verification.VerificationBottomSheet 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.devtools.RoomDevToolActivity
import im.vector.app.features.home.room.detail.RoomDetailActivity import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.home.room.detail.arguments.TimelineArgs import im.vector.app.features.home.room.detail.arguments.TimelineArgs
@ -123,7 +123,8 @@ class DefaultNavigator @Inject constructor(
private val spaceStateHandler: SpaceStateHandler, private val spaceStateHandler: SpaceStateHandler,
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider, private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider,
private val features: VectorFeatures, private val features: VectorFeatures,
private val analyticsTracker: AnalyticsTracker private val analyticsTracker: AnalyticsTracker,
private val debugNavigator: DebugNavigator,
) : Navigator { ) : Navigator {
override fun openLogin(context: Context, loginConfig: LoginConfig?, flags: Int) { override fun openLogin(context: Context, loginConfig: LoginConfig?, flags: Int) {
@ -367,7 +368,7 @@ class DefaultNavigator @Inject constructor(
} }
override fun openDebug(context: Context) { override fun openDebug(context: Context) {
context.startActivity(Intent(context, DebugMenuActivity::class.java)) debugNavigator.openDebugMenu(context)
} }
override fun openKeysBackupSetup(context: Context, showManualExport: Boolean) { override fun openKeysBackupSetup(context: Context, showManualExport: Boolean) {

View File

@ -65,9 +65,10 @@ class NotifiableEventResolver @Inject constructor(
private val buildMeta: BuildMeta, 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 roomID = event.roomId ?: return null
val eventId = event.eventId ?: return null val eventId = event.eventId ?: return null
if (event.getClearType() == EventType.STATE_ROOM_MEMBER) { if (event.getClearType() == EventType.STATE_ROOM_MEMBER) {
@ -75,7 +76,7 @@ class NotifiableEventResolver @Inject constructor(
} }
val timelineEvent = session.getRoom(roomID)?.getTimelineEvent(eventId) ?: return null val timelineEvent = session.getRoom(roomID)?.getTimelineEvent(eventId) ?: return null
return when (event.getClearType()) { return when (event.getClearType()) {
EventType.MESSAGE, in nonEncryptedNotifiableEventTypes,
EventType.ENCRYPTED -> { EventType.ENCRYPTED -> {
resolveMessageEvent(timelineEvent, session, canBeReplaced = false, isNoisy = isNoisy) resolveMessageEvent(timelineEvent, session, canBeReplaced = false, isNoisy = isNoisy)
} }
@ -161,9 +162,7 @@ class NotifiableEventResolver @Inject constructor(
event.attemptToDecryptIfNeeded(session) event.attemptToDecryptIfNeeded(session)
// only convert encrypted messages to NotifiableMessageEvents // only convert encrypted messages to NotifiableMessageEvents
when (event.root.getClearType()) { when (event.root.getClearType()) {
EventType.MESSAGE, in nonEncryptedNotifiableEventTypes -> {
in EventType.POLL_START,
in EventType.STATE_ROOM_BEACON_INFO -> {
val body = displayableEventFormatter.format(event, isDm = room.roomSummary()?.isDirect.orFalse(), appendAuthor = false).toString() val body = displayableEventFormatter.format(event, isDm = room.roomSummary()?.isDirect.orFalse(), appendAuthor = false).toString()
val roomName = room.roomSummary()?.displayName ?: "" val roomName = room.roomSummary()?.displayName ?: ""
val senderDisplayName = event.senderInfo.disambiguatedDisplayName val senderDisplayName = event.senderInfo.disambiguatedDisplayName

View File

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

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