mirror of
https://github.com/element-hq/element-android
synced 2024-11-26 19:35:42 +03:00
Merge branch 'develop' into hughns/msc3824-oidc-aware
This commit is contained in:
commit
bfc58cbdcf
158 changed files with 2648 additions and 784 deletions
35
CHANGES.md
35
CHANGES.md
|
@ -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)
|
||||
=======================================
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
[Modularization] Provides abstraction to avoids direct usages of BuildConfig
|
1
changelog.d/6505.wip
Normal file
1
changelog.d/6505.wip
Normal file
|
@ -0,0 +1 @@
|
|||
added filter tabs for new App layout's Home screen
|
|
@ -1 +0,0 @@
|
|||
Refactors SpaceStateHandler (previously AppStateHandler) and adds unit tests for it
|
|
@ -1 +0,0 @@
|
|||
Setup Danger to the project
|
|
@ -1 +0,0 @@
|
|||
[Location Share] Open maximized map on tapping on live sharing notification
|
|
@ -1 +0,0 @@
|
|||
[Location sharing] Align naming of components for live location feature
|
|
@ -1 +0,0 @@
|
|||
[Location share] Update minimum sending period to 5 seconds for a live
|
|
@ -1 +0,0 @@
|
|||
Adds new app layout toolbar (feature flagged)
|
|
@ -1 +0,0 @@
|
|||
[Location sharing] - Fix the memory leaks
|
|
@ -1 +0,0 @@
|
|||
[Timeline] Memory leak in audio message playback tracker
|
|
@ -1 +0,0 @@
|
|||
[FTUE] Memory leak on FtueAuthSplashCarouselFragment
|
|
@ -1 +0,0 @@
|
|||
[Location sharing] Invisible text on map symbol
|
1
changelog.d/6693.feature
Normal file
1
changelog.d/6693.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Adds New App Layout FABs (hidden behind feature flag)
|
|
@ -1 +0,0 @@
|
|||
Fix message content sometimes appearing in the log
|
|
@ -1 +0,0 @@
|
|||
[Location Share] Render fallback UI when map fails to load
|
|
@ -1 +0,0 @@
|
|||
Disable 'Enable biometrics' option if there are not biometric authenticators enrolled.
|
|
@ -1 +0,0 @@
|
|||
Fixes onboarding login/account creation errors showing after navigation
|
|
@ -1 +0,0 @@
|
|||
Link directly to DCO docs from danger message.
|
1
changelog.d/6746.feature
Normal file
1
changelog.d/6746.feature
Normal file
|
@ -0,0 +1 @@
|
|||
[Notification] - Handle creation of notification for live location and poll start
|
1
changelog.d/6783.misc
Normal file
1
changelog.d/6783.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Decouples the variant logic from the vector module
|
1
changelog.d/6786.misc
Normal file
1
changelog.d/6786.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Add a developer setting to enable LeakCanary at runtime
|
1
changelog.d/6798.wip
Normal file
1
changelog.d/6798.wip
Normal file
|
@ -0,0 +1 @@
|
|||
[Devices management] Add a feature flag and empty screen for future new layout
|
1
changelog.d/6799.misc
Normal file
1
changelog.d/6799.misc
Normal file
|
@ -0,0 +1 @@
|
|||
[Create Room] Reduce some boilerplate with room state event contents
|
1
changelog.d/6808.misc
Normal file
1
changelog.d/6808.misc
Normal file
|
@ -0,0 +1 @@
|
|||
[Call] Memory leak after a call
|
|
@ -74,6 +74,7 @@ ext.groups = [
|
|||
'com.github.javaparser',
|
||||
'com.github.piasy',
|
||||
'com.github.shyiko.klob',
|
||||
'com.github.rubensousa',
|
||||
'com.google',
|
||||
'com.google.android',
|
||||
'com.google.api.grpc',
|
||||
|
@ -106,7 +107,9 @@ ext.groups = [
|
|||
'com.pinterest.ktlint',
|
||||
'com.posthog.android',
|
||||
'com.squareup',
|
||||
'com.squareup.curtains',
|
||||
'com.squareup.duktape',
|
||||
'com.squareup.leakcanary',
|
||||
'com.squareup.moshi',
|
||||
'com.squareup.okhttp3',
|
||||
'com.squareup.okio',
|
||||
|
|
2
fastlane/metadata/android/en-US/changelogs/40104320.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40104320.txt
Normal 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
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,6 +1,6 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=97a52d145762adc241bad7fd18289bf7f6801e08ece6badf80402fe2b9f250b1
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
|
||||
distributionSha256Sum=db9c8211ed63f61f60292c69e80d89196f9eb36665e369e7f00ac4cc841c2219
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
@ -65,4 +65,8 @@
|
|||
<item name="colorPrimary">?colorOnPrimary</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Vector.FloatingActionButton" parent="Widget.MaterialComponents.FloatingActionButton">
|
||||
<item name="shapeAppearanceOverlay">@style/ShapeAppearanceOverlay.Material3.FloatingActionButton</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
|
18
library/ui-styles/src/main/res/values/styles_tablayout.xml
Normal file
18
library/ui-styles/src/main/res/values/styles_tablayout.xml
Normal 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>
|
|
@ -60,7 +60,7 @@ android {
|
|||
// that the app's state is completely cleared between tests.
|
||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||
|
||||
buildConfigField "String", "SDK_VERSION", "\"1.4.32\""
|
||||
buildConfigField "String", "SDK_VERSION", "\"1.4.34\""
|
||||
|
||||
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
|
||||
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
|
||||
|
|
|
@ -62,7 +62,10 @@ fun Throwable.isUsernameInUse() = this is Failure.ServerError &&
|
|||
error.code == MatrixError.M_USER_IN_USE
|
||||
|
||||
fun Throwable.isInvalidUsername() = this is Failure.ServerError &&
|
||||
error.code == MatrixError.M_INVALID_USERNAME
|
||||
(error.code == MatrixError.M_INVALID_USERNAME || usernameContainsNonAsciiCharacters())
|
||||
|
||||
private fun Failure.ServerError.usernameContainsNonAsciiCharacters() = error.code == MatrixError.M_UNKNOWN &&
|
||||
error.message == "Query parameter \'username\' must be ascii"
|
||||
|
||||
fun Throwable.isInvalidPassword() = this is Failure.ServerError &&
|
||||
error.code == MatrixError.M_FORBIDDEN &&
|
||||
|
|
|
@ -29,14 +29,12 @@ data class RoomGuestAccessContent(
|
|||
// Required. Whether guests can join the room. One of: ["can_join", "forbidden"]
|
||||
@Json(name = "guest_access") val guestAccessStr: String? = null
|
||||
) {
|
||||
val guestAccess: GuestAccess? = when (guestAccessStr) {
|
||||
"can_join" -> GuestAccess.CanJoin
|
||||
"forbidden" -> GuestAccess.Forbidden
|
||||
else -> {
|
||||
Timber.w("Invalid value for GuestAccess: `$guestAccessStr`")
|
||||
null
|
||||
}
|
||||
}
|
||||
val guestAccess: GuestAccess? = GuestAccess.values()
|
||||
.find { it.value == guestAccessStr }
|
||||
?: run {
|
||||
Timber.w("Invalid value for GuestAccess: `$guestAccessStr`")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
|
|
|
@ -23,30 +23,30 @@ import com.squareup.moshi.JsonClass
|
|||
* Ref: https://matrix.org/docs/spec/client_server/latest#room-history-visibility
|
||||
*/
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class RoomHistoryVisibility {
|
||||
enum class RoomHistoryVisibility(val value: String) {
|
||||
/**
|
||||
* All events while this is the m.room.history_visibility value may be shared by any
|
||||
* participating homeserver with anyone, regardless of whether they have ever joined the room.
|
||||
*/
|
||||
@Json(name = "world_readable") WORLD_READABLE,
|
||||
@Json(name = "world_readable") WORLD_READABLE("world_readable"),
|
||||
|
||||
/**
|
||||
* Previous events are always accessible to newly joined members. All events in the
|
||||
* room are accessible, even those sent when the member was not a part of the room.
|
||||
*/
|
||||
@Json(name = "shared") SHARED,
|
||||
@Json(name = "shared") SHARED("shared"),
|
||||
|
||||
/**
|
||||
* Events are accessible to newly joined members from the point they were invited onwards.
|
||||
* Events stop being accessible when the member's state changes to something other than invite or join.
|
||||
*/
|
||||
@Json(name = "invited") INVITED,
|
||||
@Json(name = "invited") INVITED("invited"),
|
||||
|
||||
/**
|
||||
* Events are accessible to newly joined members from the point they joined the room onwards.
|
||||
* Events stop being accessible when the member's state changes to something other than join.
|
||||
*/
|
||||
@Json(name = "joined") JOINED
|
||||
@Json(name = "joined") JOINED("joined")
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -24,14 +24,10 @@ import timber.log.Timber
|
|||
data class RoomHistoryVisibilityContent(
|
||||
@Json(name = "history_visibility") val historyVisibilityStr: String? = null
|
||||
) {
|
||||
val historyVisibility: RoomHistoryVisibility? = when (historyVisibilityStr) {
|
||||
"world_readable" -> RoomHistoryVisibility.WORLD_READABLE
|
||||
"shared" -> RoomHistoryVisibility.SHARED
|
||||
"invited" -> RoomHistoryVisibility.INVITED
|
||||
"joined" -> RoomHistoryVisibility.JOINED
|
||||
else -> {
|
||||
Timber.w("Invalid value for RoomHistoryVisibility: `$historyVisibilityStr`")
|
||||
null
|
||||
}
|
||||
}
|
||||
val historyVisibility: RoomHistoryVisibility? = RoomHistoryVisibility.values()
|
||||
.find { it.value == historyVisibilityStr }
|
||||
?: run {
|
||||
Timber.w("Invalid value for RoomHistoryVisibility: `$historyVisibilityStr`")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package org.matrix.android.sdk.api.session.room.model.create
|
||||
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
|
||||
|
@ -30,7 +29,7 @@ interface RoomFeaturePreset {
|
|||
|
||||
fun updateRoomParams(params: CreateRoomParams)
|
||||
|
||||
fun setupInitialStates(): List<Event>?
|
||||
fun setupInitialStates(): List<CreateRoomStateEvent>?
|
||||
}
|
||||
|
||||
class RestrictedRoomPreset(val homeServerCapabilities: HomeServerCapabilities, val restrictedList: List<RoomJoinRulesAllowEntry>) : RoomFeaturePreset {
|
||||
|
@ -41,9 +40,9 @@ class RestrictedRoomPreset(val homeServerCapabilities: HomeServerCapabilities, v
|
|||
params.roomVersion = homeServerCapabilities.versionOverrideForFeature(HomeServerCapabilities.ROOM_CAP_RESTRICTED)
|
||||
}
|
||||
|
||||
override fun setupInitialStates(): List<Event>? {
|
||||
override fun setupInitialStates(): List<CreateRoomStateEvent> {
|
||||
return listOf(
|
||||
Event(
|
||||
CreateRoomStateEvent(
|
||||
type = EventType.STATE_ROOM_JOIN_RULES,
|
||||
stateKey = "",
|
||||
content = RoomJoinRulesContent(
|
||||
|
|
|
@ -22,7 +22,7 @@ import org.matrix.android.sdk.api.session.events.model.Content
|
|||
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
||||
|
||||
/**
|
||||
* Content of the state event of type
|
||||
* Content of the event of type
|
||||
* [EventType.BEACON_LOCATION_DATA][org.matrix.android.sdk.api.session.events.model.EventType.BEACON_LOCATION_DATA]
|
||||
*
|
||||
* It contains location data related to a live location share.
|
||||
|
|
|
@ -20,8 +20,13 @@ import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
|||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent
|
||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||
import org.matrix.android.sdk.api.session.identity.IdentityServiceError
|
||||
import org.matrix.android.sdk.api.session.identity.toMedium
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent
|
||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||
import org.matrix.android.sdk.api.util.MimeTypes
|
||||
import org.matrix.android.sdk.internal.crypto.DeviceListManager
|
||||
|
@ -78,7 +83,7 @@ internal class CreateRoomBodyBuilder @Inject constructor(
|
|||
buildAvatarEvent(params),
|
||||
buildGuestAccess(params)
|
||||
) +
|
||||
params.featurePreset?.setupInitialStates().orEmpty() +
|
||||
buildFeaturePresetInitialStates(params) +
|
||||
buildCustomInitialStates(params)
|
||||
)
|
||||
.takeIf { it.isNotEmpty() }
|
||||
|
@ -99,6 +104,16 @@ internal class CreateRoomBodyBuilder @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun buildFeaturePresetInitialStates(params: CreateRoomParams): List<Event> {
|
||||
return params.featurePreset?.setupInitialStates().orEmpty().map {
|
||||
Event(
|
||||
type = it.type,
|
||||
stateKey = it.stateKey,
|
||||
content = it.content
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildCustomInitialStates(params: CreateRoomParams): List<Event> {
|
||||
return params.initialStates.map {
|
||||
Event(
|
||||
|
@ -123,7 +138,7 @@ internal class CreateRoomBodyBuilder @Inject constructor(
|
|||
Event(
|
||||
type = EventType.STATE_ROOM_AVATAR,
|
||||
stateKey = "",
|
||||
content = mapOf("url" to response.contentUri)
|
||||
content = RoomAvatarContent(response.contentUri).toContent()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -134,7 +149,7 @@ internal class CreateRoomBodyBuilder @Inject constructor(
|
|||
Event(
|
||||
type = EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
||||
stateKey = "",
|
||||
content = mapOf("history_visibility" to it)
|
||||
content = RoomHistoryVisibilityContent(it.value).toContent()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -145,7 +160,7 @@ internal class CreateRoomBodyBuilder @Inject constructor(
|
|||
Event(
|
||||
type = EventType.STATE_ROOM_GUEST_ACCESS,
|
||||
stateKey = "",
|
||||
content = mapOf("guest_access" to it.value)
|
||||
content = RoomGuestAccessContent(it.value).toContent()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -167,7 +182,7 @@ internal class CreateRoomBodyBuilder @Inject constructor(
|
|||
Event(
|
||||
type = EventType.STATE_ROOM_ENCRYPTION,
|
||||
stateKey = "",
|
||||
content = mapOf("algorithm" to it)
|
||||
content = EncryptionEventContent(it).toContent()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ mv ./fastlane/metadata/android/fy ./fastlane_tmp
|
|||
mv ./fastlane/metadata/android/ga ./fastlane_tmp
|
||||
mv ./fastlane/metadata/android/kab ./fastlane_tmp
|
||||
mv ./fastlane/metadata/android/nb ./fastlane_tmp
|
||||
mv ./fastlane/metadata/android/gl ./fastlane_tmp
|
||||
|
||||
# Fastlane / PlayStore require longDescription and shortDescription file to be set, so copy the default
|
||||
# one for languages where they are missing
|
||||
|
|
|
@ -37,7 +37,7 @@ ext.versionMinor = 4
|
|||
// Note: even values are reserved for regular release, odd values for hotfix release.
|
||||
// When creating a hotfix, you should decrease the value, since the current value
|
||||
// is the value for the next regular release.
|
||||
ext.versionPatch = 32
|
||||
ext.versionPatch = 34
|
||||
|
||||
static def getGitTimestamp() {
|
||||
def cmd = 'git show -s --format=%ct'
|
||||
|
@ -307,7 +307,6 @@ android {
|
|||
isDefault = true
|
||||
versionName "${versionMajor}.${versionMinor}.${versionPatch}${getGplayVersionSuffix()}"
|
||||
|
||||
resValue "bool", "isGplay", "true"
|
||||
buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"G\""
|
||||
buildConfigField "String", "FLAVOR_DESCRIPTION", "\"GooglePlay\""
|
||||
}
|
||||
|
@ -317,7 +316,6 @@ android {
|
|||
|
||||
versionName "${versionMajor}.${versionMinor}.${versionPatch}${getFdroidVersionSuffix()}"
|
||||
|
||||
resValue "bool", "isGplay", "false"
|
||||
buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"F\""
|
||||
buildConfigField "String", "FLAVOR_DESCRIPTION", "\"FDroid\""
|
||||
}
|
||||
|
@ -427,6 +425,9 @@ dependencies {
|
|||
implementation libs.airbnb.epoxyPaging
|
||||
implementation libs.airbnb.mavericks
|
||||
|
||||
// Snap Helper https://github.com/rubensousa/GravitySnapHelper
|
||||
implementation 'com.github.rubensousa:gravitysnaphelper:2.2.2'
|
||||
|
||||
// Nightly
|
||||
// API-only library
|
||||
gplayImplementation libs.google.appdistributionApi
|
||||
|
@ -499,7 +500,7 @@ dependencies {
|
|||
implementation 'com.posthog.android:posthog:1.1.2'
|
||||
|
||||
// UnifiedPush
|
||||
implementation 'com.github.UnifiedPush:android-connector:2.0.0'
|
||||
implementation 'com.github.UnifiedPush:android-connector:2.0.1'
|
||||
// UnifiedPush gplay flavor only
|
||||
gplayImplementation('com.github.UnifiedPush:android-embedded_fcm_distributor:2.1.1') {
|
||||
exclude group: 'com.google.firebase', module: 'firebase-core'
|
||||
|
@ -575,7 +576,7 @@ dependencies {
|
|||
debugImplementation "com.kgurgul.flipper:flipper-realm-android:2.2.0"
|
||||
|
||||
// Activate when you want to check for leaks, from time to time.
|
||||
//debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3'
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
|
||||
|
||||
androidTestImplementation libs.androidx.testCore
|
||||
androidTestImplementation libs.androidx.testRunner
|
||||
|
|
|
@ -31,7 +31,6 @@ import androidx.test.filters.SdkSuppress
|
|||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import im.vector.app.TestBuildVersionSdkIntProvider
|
||||
import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguration
|
||||
import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguratorProvider
|
||||
import im.vector.app.features.pin.lockscreen.configuration.LockScreenMode
|
||||
import im.vector.app.features.pin.lockscreen.crypto.LockScreenCryptoConstants
|
||||
import im.vector.app.features.pin.lockscreen.crypto.LockScreenKeyRepository
|
||||
|
@ -40,6 +39,7 @@ import im.vector.app.features.pin.lockscreen.ui.fallbackprompt.FallbackBiometric
|
|||
import im.vector.app.features.pin.lockscreen.utils.DevicePromptCheck
|
||||
import io.mockk.clearAllMocks
|
||||
import io.mockk.every
|
||||
import io.mockk.justRun
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkObject
|
||||
import io.mockk.mockkStatic
|
||||
|
@ -54,8 +54,10 @@ import kotlinx.coroutines.flow.flowOf
|
|||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.amshove.kluent.coInvoking
|
||||
import org.amshove.kluent.shouldBeFalse
|
||||
import org.amshove.kluent.shouldBeTrue
|
||||
import org.amshove.kluent.shouldThrow
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
|
@ -239,36 +241,35 @@ class BiometricHelperTests {
|
|||
|
||||
@Test
|
||||
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R) // Due to some issues with mockk and CryptoObject initialization
|
||||
fun authenticateCreatesSystemKeyIfNeededOnSuccessOnAndroidM() = runTest {
|
||||
fun enableAuthenticationDeletesSystemKeyOnFailure() = runTest {
|
||||
buildVersionSdkIntProvider.value = Build.VERSION_CODES.M
|
||||
every { lockScreenKeyRepository.isSystemKeyValid() } returns true
|
||||
val mockAuthChannel = Channel<Boolean>(capacity = 1)
|
||||
val biometricUtils = spyk(createBiometricHelper(createDefaultConfiguration(isBiometricsEnabled = true))) {
|
||||
every { createAuthChannel() } returns mockAuthChannel
|
||||
every { authenticateWithPromptInternal(any(), any(), any()) } returns mockk()
|
||||
}
|
||||
justRun { lockScreenKeyRepository.deleteSystemKey() }
|
||||
|
||||
val latch = CountDownLatch(1)
|
||||
val intent = Intent(InstrumentationRegistry.getInstrumentation().targetContext, LockScreenTestActivity::class.java)
|
||||
ActivityScenario.launch<LockScreenTestActivity>(intent).onActivity { activity ->
|
||||
activity.lifecycleScope.launch {
|
||||
val exception = IllegalStateException("Some error")
|
||||
launch {
|
||||
mockAuthChannel.send(true)
|
||||
mockAuthChannel.close()
|
||||
mockAuthChannel.close(exception)
|
||||
}
|
||||
biometricUtils.authenticate(activity).collect()
|
||||
coInvoking { biometricUtils.enableAuthentication(activity).collect() } shouldThrow exception
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
|
||||
latch.await(1, TimeUnit.SECONDS)
|
||||
verify { lockScreenKeyRepository.ensureSystemKey() }
|
||||
verify { lockScreenKeyRepository.deleteSystemKey() }
|
||||
}
|
||||
|
||||
private fun createBiometricHelper(configuration: LockScreenConfiguration): BiometricHelper {
|
||||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val configProvider = LockScreenConfiguratorProvider(configuration)
|
||||
return BiometricHelper(context, lockScreenKeyRepository, configProvider, biometricManager, buildVersionSdkIntProvider)
|
||||
return BiometricHelper(configuration, context, lockScreenKeyRepository, biometricManager, buildVersionSdkIntProvider)
|
||||
}
|
||||
|
||||
private fun createDefaultConfiguration(
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
package im.vector.app.features.pin.lockscreen.crypto
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import im.vector.app.features.pin.lockscreen.crypto.migrations.LegacyPinCodeMigrator
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import io.mockk.clearAllMocks
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
|
@ -44,8 +42,6 @@ class LockScreenKeyRepositoryTests {
|
|||
}
|
||||
|
||||
private lateinit var lockScreenKeyRepository: LockScreenKeyRepository
|
||||
private val legacyPinCodeMigrator: LegacyPinCodeMigrator = mockk(relaxed = true)
|
||||
private val vectorPreferences: VectorPreferences = mockk(relaxed = true)
|
||||
|
||||
private val keyStore: KeyStore by lazy {
|
||||
KeyStore.getInstance(LockScreenCryptoConstants.ANDROID_KEY_STORE).also { it.load(null) }
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
<activity android:name=".features.debug.settings.DebugPrivateSettingsActivity" />
|
||||
<activity android:name=".features.debug.sas.DebugSasEmojiActivity" />
|
||||
<activity android:name=".features.debug.features.DebugFeaturesSettingsActivity" />
|
||||
<activity android:name=".features.debug.DebugMenuActivity" />
|
||||
<activity android:name=".features.debug.leak.DebugMemoryLeaksActivity" />
|
||||
|
||||
<activity
|
||||
android:name="com.facebook.flipper.android.diagnostics.FlipperDiagnosticActivity"
|
||||
|
|
|
@ -37,6 +37,7 @@ import im.vector.app.core.utils.toast
|
|||
import im.vector.app.databinding.ActivityDebugMenuBinding
|
||||
import im.vector.app.features.debug.analytics.DebugAnalyticsActivity
|
||||
import im.vector.app.features.debug.features.DebugFeaturesSettingsActivity
|
||||
import im.vector.app.features.debug.leak.DebugMemoryLeaksActivity
|
||||
import im.vector.app.features.debug.sas.DebugSasEmojiActivity
|
||||
import im.vector.app.features.debug.settings.DebugPrivateSettingsActivity
|
||||
import im.vector.app.features.qrcode.QrCodeScannerActivity
|
||||
|
@ -86,6 +87,7 @@ class DebugMenuActivity : VectorBaseActivity<ActivityDebugMenuBinding>() {
|
|||
views.debugAnalytics.setOnClickListener {
|
||||
startActivity(Intent(this, DebugAnalyticsActivity::class.java))
|
||||
}
|
||||
views.debugMemoryLeaks.setOnClickListener { openMemoryLeaksSettings() }
|
||||
views.debugTestTextViewLink.setOnClickListener { testTextViewLink() }
|
||||
views.debugOpenButtonStylesLight.setOnClickListener {
|
||||
startActivity(Intent(this, DebugVectorButtonStylesLightActivity::class.java))
|
||||
|
@ -130,6 +132,10 @@ class DebugMenuActivity : VectorBaseActivity<ActivityDebugMenuBinding>() {
|
|||
startActivity(Intent(this, DebugPrivateSettingsActivity::class.java))
|
||||
}
|
||||
|
||||
private fun openMemoryLeaksSettings() {
|
||||
startActivity(Intent(this, DebugMemoryLeaksActivity::class.java))
|
||||
}
|
||||
|
||||
private fun renderQrCode(text: String) {
|
||||
views.debugQrCode.setData(text)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -24,6 +24,7 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
|||
import im.vector.app.core.di.MavericksViewModelComponent
|
||||
import im.vector.app.core.di.MavericksViewModelKey
|
||||
import im.vector.app.features.debug.analytics.DebugAnalyticsViewModel
|
||||
import im.vector.app.features.debug.leak.DebugMemoryLeaksViewModel
|
||||
import im.vector.app.features.debug.settings.DebugPrivateSettingsViewModel
|
||||
|
||||
@InstallIn(MavericksViewModelComponent::class)
|
||||
|
@ -39,4 +40,9 @@ interface MavericksViewModelDebugModule {
|
|||
@IntoMap
|
||||
@MavericksViewModelKey(DebugPrivateSettingsViewModel::class)
|
||||
fun debugPrivateSettingsViewModelFactory(factory: DebugPrivateSettingsViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@MavericksViewModelKey(DebugMemoryLeaksViewModel::class)
|
||||
fun debugMemoryLeaksViewModelFactory(factory: DebugMemoryLeaksViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||
}
|
||||
|
|
|
@ -90,6 +90,11 @@ class DebugFeaturesStateFactory @Inject constructor(
|
|||
key = DebugFeatureKeys.newAppLayoutEnabled,
|
||||
factory = VectorFeatures::isNewAppLayoutEnabled
|
||||
),
|
||||
createBooleanFeature(
|
||||
label = "Enable New Device Management",
|
||||
key = DebugFeatureKeys.newDeviceManagementEnabled,
|
||||
factory = VectorFeatures::isNewDeviceManagementEnabled
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -79,6 +79,9 @@ class DebugVectorFeatures(
|
|||
override fun isNewAppLayoutEnabled(): Boolean = read(DebugFeatureKeys.newAppLayoutEnabled)
|
||||
?: vectorFeatures.isNewAppLayoutEnabled()
|
||||
|
||||
override fun isNewDeviceManagementEnabled(): Boolean = read(DebugFeatureKeys.newDeviceManagementEnabled)
|
||||
?: vectorFeatures.isNewDeviceManagementEnabled()
|
||||
|
||||
fun <T> override(value: T?, key: Preferences.Key<T>) = updatePreferences {
|
||||
if (value == null) {
|
||||
it.remove(key)
|
||||
|
@ -139,4 +142,5 @@ object DebugFeatureKeys {
|
|||
val forceUsageOfOpusEncoder = booleanPreferencesKey("force-usage-of-opus-encoder")
|
||||
val startDmOnFirstMsg = booleanPreferencesKey("start-dm-on-first-msg")
|
||||
val newAppLayoutEnabled = booleanPreferencesKey("new-app-layout-enabled")
|
||||
val newDeviceManagementEnabled = booleanPreferencesKey("new-device-management-enabled")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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()) }
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -29,19 +29,19 @@ import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPl
|
|||
import com.facebook.soloader.SoLoader
|
||||
import com.kgurgul.flipper.RealmDatabaseDriver
|
||||
import com.kgurgul.flipper.RealmDatabaseProvider
|
||||
import im.vector.app.core.debug.FlipperProxy
|
||||
import io.realm.RealmConfiguration
|
||||
import okhttp3.Interceptor
|
||||
import org.matrix.android.sdk.api.Matrix
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class FlipperProxy @Inject constructor(
|
||||
class VectorFlipperProxy @Inject constructor(
|
||||
private val context: Context,
|
||||
) {
|
||||
) : FlipperProxy {
|
||||
private val networkFlipperPlugin = NetworkFlipperPlugin()
|
||||
|
||||
fun init(matrix: Matrix) {
|
||||
override fun init(matrix: Matrix) {
|
||||
SoLoader.init(context, false)
|
||||
|
||||
if (FlipperUtils.shouldEnableFlipper(context)) {
|
||||
|
@ -65,8 +65,5 @@ class FlipperProxy @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("RedundantNullableReturnType")
|
||||
fun getNetworkInterceptor(): Interceptor? {
|
||||
return FlipperOkhttpInterceptor(networkFlipperPlugin)
|
||||
}
|
||||
override fun networkInterceptor() = FlipperOkhttpInterceptor(networkFlipperPlugin)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -22,14 +22,24 @@ import android.content.Intent
|
|||
import android.content.IntentFilter
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import im.vector.app.core.debug.DebugReceiver
|
||||
import im.vector.app.core.di.DefaultSharedPreferences
|
||||
import im.vector.app.core.utils.lsFiles
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Receiver to handle some command from ADB
|
||||
*/
|
||||
class DebugReceiver : BroadcastReceiver() {
|
||||
class VectorDebugReceiver @Inject constructor() : BroadcastReceiver(), DebugReceiver {
|
||||
|
||||
override fun register(context: Context) {
|
||||
context.registerReceiver(this, getIntentFilter(context))
|
||||
}
|
||||
|
||||
override fun unregister(context: Context) {
|
||||
context.unregisterReceiver(this)
|
||||
}
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
Timber.v("Received debug action: ${intent.action}")
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/coordinatorLayout"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -8,8 +9,11 @@
|
|||
tools:ignore="HardcodedText">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/scrollView2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_anchor="@+id/scrollView2"
|
||||
app:layout_anchorGravity="center">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
@ -38,6 +42,12 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:text="Analytics" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/debug_memory_leaks"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Memory leaks" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/debug_test_text_view_link"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
32
vector/src/debug/res/layout/fragment_debug_memory_leaks.xml
Normal file
32
vector/src/debug/res/layout/fragment_debug_memory_leaks.xml
Normal 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>
|
|
@ -17,20 +17,46 @@
|
|||
package im.vector.app.di
|
||||
|
||||
import android.content.Context
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import im.vector.app.core.pushers.FcmHelper
|
||||
import im.vector.app.core.services.GuardServiceStarter
|
||||
import im.vector.app.fdroid.service.FDroidGuardServiceStarter
|
||||
import im.vector.app.features.home.NightlyProxy
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import im.vector.app.features.settings.legals.FlavorLegals
|
||||
import im.vector.app.push.fcm.FdroidFcmHelper
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
object FlavorModule {
|
||||
abstract class FlavorModule {
|
||||
|
||||
@Provides
|
||||
fun provideGuardServiceStarter(preferences: VectorPreferences, appContext: Context): GuardServiceStarter {
|
||||
return FDroidGuardServiceStarter(preferences, appContext)
|
||||
companion object {
|
||||
@Provides
|
||||
fun provideGuardServiceStarter(preferences: VectorPreferences, appContext: Context): GuardServiceStarter {
|
||||
return FDroidGuardServiceStarter(preferences, appContext)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideNightlyProxy() = object : NightlyProxy {
|
||||
override fun onHomeResumed() {
|
||||
// no op
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun providesFlavorLegals() = object : FlavorLegals {
|
||||
override fun hasThirdPartyNotices() = false
|
||||
|
||||
override fun navigateToThirdPartyNotices(context: Context) {
|
||||
// no op
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Binds
|
||||
abstract fun bindsFcmHelper(fcmHelper: FdroidFcmHelper): FcmHelper
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -20,6 +20,7 @@ package im.vector.app.push.fcm
|
|||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.pushers.FcmHelper
|
||||
import im.vector.app.core.pushers.PushersManager
|
||||
import im.vector.app.fdroid.BackgroundSyncStarter
|
||||
import im.vector.app.fdroid.receiver.AlarmSyncBroadcastReceiver
|
||||
|
@ -28,47 +29,32 @@ import javax.inject.Inject
|
|||
/**
|
||||
* This class has an alter ego in the gplay variant.
|
||||
*/
|
||||
class FcmHelper @Inject constructor(
|
||||
class FdroidFcmHelper @Inject constructor(
|
||||
private val context: Context,
|
||||
private val backgroundSyncStarter: BackgroundSyncStarter,
|
||||
) {
|
||||
) : FcmHelper {
|
||||
|
||||
fun isFirebaseAvailable(): Boolean = false
|
||||
override fun isFirebaseAvailable(): Boolean = false
|
||||
|
||||
/**
|
||||
* Retrieves the FCM registration token.
|
||||
*
|
||||
* @return the FCM token or null if not received from FCM
|
||||
*/
|
||||
fun getFcmToken(): String? {
|
||||
override fun getFcmToken(): String? {
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Store FCM token to the SharedPrefs
|
||||
*
|
||||
* @param token the token to store
|
||||
*/
|
||||
fun storeFcmToken(token: String?) {
|
||||
override fun storeFcmToken(token: String?) {
|
||||
// No op
|
||||
}
|
||||
|
||||
/**
|
||||
* onNewToken may not be called on application upgrade, so ensure my shared pref is set
|
||||
*
|
||||
* @param activity the first launch Activity
|
||||
*/
|
||||
fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) {
|
||||
override fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) {
|
||||
// No op
|
||||
}
|
||||
|
||||
fun onEnterForeground(activeSessionHolder: ActiveSessionHolder) {
|
||||
override fun onEnterForeground(activeSessionHolder: ActiveSessionHolder) {
|
||||
// try to stop all regardless of background mode
|
||||
activeSessionHolder.getSafeActiveSession()?.syncService()?.stopAnyBackgroundSync()
|
||||
AlarmSyncBroadcastReceiver.cancelAlarm(context)
|
||||
}
|
||||
|
||||
fun onEnterBackground(activeSessionHolder: ActiveSessionHolder) {
|
||||
override fun onEnterBackground(activeSessionHolder: ActiveSessionHolder) {
|
||||
backgroundSyncStarter.start(activeSessionHolder)
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ import im.vector.app.fdroid.features.settings.troubleshoot.TestAutoStartBoot
|
|||
import im.vector.app.fdroid.features.settings.troubleshoot.TestBackgroundRestrictions
|
||||
import im.vector.app.fdroid.features.settings.troubleshoot.TestBatteryOptimization
|
||||
import im.vector.app.features.VectorFeatures
|
||||
import im.vector.app.features.push.NotificationTroubleshootTestManagerFactory
|
||||
import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager
|
||||
import im.vector.app.features.settings.troubleshoot.TestAccountSettings
|
||||
import im.vector.app.features.settings.troubleshoot.TestAvailableUnifiedPushDistributors
|
||||
|
@ -35,7 +36,7 @@ import im.vector.app.features.settings.troubleshoot.TestUnifiedPushEndpoint
|
|||
import im.vector.app.features.settings.troubleshoot.TestUnifiedPushGateway
|
||||
import javax.inject.Inject
|
||||
|
||||
class NotificationTroubleshootTestManagerFactory @Inject constructor(
|
||||
class FdroidNotificationTroubleshootTestManagerFactory @Inject constructor(
|
||||
private val unifiedPushHelper: UnifiedPushHelper,
|
||||
private val testSystemSettings: TestSystemSettings,
|
||||
private val testAccountSettings: TestAccountSettings,
|
||||
|
@ -52,9 +53,9 @@ class NotificationTroubleshootTestManagerFactory @Inject constructor(
|
|||
private val testBatteryOptimization: TestBatteryOptimization,
|
||||
private val testNotification: TestNotification,
|
||||
private val vectorFeatures: VectorFeatures,
|
||||
) {
|
||||
) : NotificationTroubleshootTestManagerFactory {
|
||||
|
||||
fun create(fragment: Fragment): NotificationTroubleshootTestManager {
|
||||
override fun create(fragment: Fragment): NotificationTroubleshootTestManager {
|
||||
val mgr = NotificationTroubleshootTestManager(fragment)
|
||||
mgr.addTest(testSystemSettings)
|
||||
mgr.addTest(testAccountSettings)
|
33
vector/src/gplay/java/im/vector/app/GoogleFlavorLegals.kt
Normal file
33
vector/src/gplay/java/im/vector/app/GoogleFlavorLegals.kt
Normal 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))
|
||||
}
|
||||
}
|
|
@ -16,18 +16,36 @@
|
|||
|
||||
package im.vector.app.di
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import im.vector.app.GoogleFlavorLegals
|
||||
import im.vector.app.core.pushers.FcmHelper
|
||||
import im.vector.app.core.services.GuardServiceStarter
|
||||
import im.vector.app.features.home.NightlyProxy
|
||||
import im.vector.app.features.settings.legals.FlavorLegals
|
||||
import im.vector.app.nightly.FirebaseNightlyProxy
|
||||
import im.vector.app.push.fcm.GoogleFcmHelper
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
object FlavorModule {
|
||||
abstract class FlavorModule {
|
||||
|
||||
@Provides
|
||||
fun provideGuardServiceStarter(): GuardServiceStarter {
|
||||
return object : GuardServiceStarter {}
|
||||
companion object {
|
||||
@Provides
|
||||
fun provideGuardServiceStarter(): GuardServiceStarter {
|
||||
return object : GuardServiceStarter {}
|
||||
}
|
||||
}
|
||||
|
||||
@Binds
|
||||
abstract fun bindsNightlyProxy(nightlyProxy: FirebaseNightlyProxy): NightlyProxy
|
||||
|
||||
@Binds
|
||||
abstract fun bindsFcmHelper(fcmHelper: GoogleFcmHelper): FcmHelper
|
||||
|
||||
@Binds
|
||||
abstract fun bindsFlavorLegals(legals: GoogleFlavorLegals): FlavorLegals
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -20,10 +20,10 @@ import androidx.activity.result.ActivityResultLauncher
|
|||
import androidx.fragment.app.FragmentActivity
|
||||
import com.google.firebase.messaging.FirebaseMessaging
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.pushers.FcmHelper
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.utils.startAddGoogleAccountIntent
|
||||
import im.vector.app.features.settings.troubleshoot.TroubleshootTest
|
||||
import im.vector.app.push.fcm.FcmHelper
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
|
@ -23,10 +23,10 @@ import androidx.work.WorkInfo
|
|||
import androidx.work.WorkManager
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.pushers.FcmHelper
|
||||
import im.vector.app.core.pushers.PushersManager
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.settings.troubleshoot.TroubleshootTest
|
||||
import im.vector.app.push.fcm.FcmHelper
|
||||
import org.matrix.android.sdk.api.session.pushers.PusherState
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
|
@ -23,15 +23,17 @@ import com.google.firebase.appdistribution.FirebaseAppDistributionException
|
|||
import im.vector.app.BuildConfig
|
||||
import im.vector.app.core.di.DefaultPreferences
|
||||
import im.vector.app.core.time.Clock
|
||||
import im.vector.app.features.home.NightlyProxy
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class NightlyProxy @Inject constructor(
|
||||
class FirebaseNightlyProxy @Inject constructor(
|
||||
private val clock: Clock,
|
||||
@DefaultPreferences
|
||||
private val sharedPreferences: SharedPreferences,
|
||||
) {
|
||||
fun onHomeResumed() {
|
||||
) : NightlyProxy {
|
||||
|
||||
override fun onHomeResumed() {
|
||||
if (!canDisplayPopup()) return
|
||||
val firebaseAppDistribution = FirebaseAppDistribution.getInstance()
|
||||
firebaseAppDistribution.updateIfNewReleaseAvailable()
|
|
@ -25,6 +25,7 @@ import com.google.firebase.messaging.FirebaseMessaging
|
|||
import im.vector.app.R
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.di.DefaultSharedPreferences
|
||||
import im.vector.app.core.pushers.FcmHelper
|
||||
import im.vector.app.core.pushers.PushersManager
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
@ -33,44 +34,29 @@ import javax.inject.Inject
|
|||
* This class store the FCM token in SharedPrefs and ensure this token is retrieved.
|
||||
* It has an alter ego in the fdroid variant.
|
||||
*/
|
||||
class FcmHelper @Inject constructor(
|
||||
class GoogleFcmHelper @Inject constructor(
|
||||
context: Context,
|
||||
) {
|
||||
) : FcmHelper {
|
||||
companion object {
|
||||
private const val PREFS_KEY_FCM_TOKEN = "FCM_TOKEN"
|
||||
}
|
||||
|
||||
private val sharedPrefs = DefaultSharedPreferences.getInstance(context)
|
||||
|
||||
fun isFirebaseAvailable(): Boolean = true
|
||||
override fun isFirebaseAvailable(): Boolean = true
|
||||
|
||||
/**
|
||||
* Retrieves the FCM registration token.
|
||||
*
|
||||
* @return the FCM token or null if not received from FCM
|
||||
*/
|
||||
fun getFcmToken(): String? {
|
||||
override fun getFcmToken(): String? {
|
||||
return sharedPrefs.getString(PREFS_KEY_FCM_TOKEN, null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Store FCM token to the SharedPrefs
|
||||
* TODO Store in realm
|
||||
*
|
||||
* @param token the token to store
|
||||
*/
|
||||
fun storeFcmToken(token: String?) {
|
||||
override fun storeFcmToken(token: String?) {
|
||||
// TODO Store in realm
|
||||
sharedPrefs.edit {
|
||||
putString(PREFS_KEY_FCM_TOKEN, token)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* onNewToken may not be called on application upgrade, so ensure my shared pref is set
|
||||
*
|
||||
* @param activity the first launch Activity
|
||||
*/
|
||||
fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) {
|
||||
override fun ensureFcmTokenIsRetrieved(activity: Activity, pushersManager: PushersManager, registerPusher: Boolean) {
|
||||
// if (TextUtils.isEmpty(getFcmToken(activity))) {
|
||||
// 'app should always check the device for a compatible Google Play services APK before accessing Google Play services features'
|
||||
if (checkPlayServices(activity)) {
|
||||
|
@ -105,13 +91,11 @@ class FcmHelper @Inject constructor(
|
|||
return resultCode == ConnectionResult.SUCCESS
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun onEnterForeground(activeSessionHolder: ActiveSessionHolder) {
|
||||
override fun onEnterForeground(activeSessionHolder: ActiveSessionHolder) {
|
||||
// No op
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun onEnterBackground(activeSessionHolder: ActiveSessionHolder) {
|
||||
override fun onEnterBackground(activeSessionHolder: ActiveSessionHolder) {
|
||||
// No op
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ package im.vector.app.push.fcm
|
|||
import androidx.fragment.app.Fragment
|
||||
import im.vector.app.core.pushers.UnifiedPushHelper
|
||||
import im.vector.app.features.VectorFeatures
|
||||
import im.vector.app.features.push.NotificationTroubleshootTestManagerFactory
|
||||
import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager
|
||||
import im.vector.app.features.settings.troubleshoot.TestAccountSettings
|
||||
import im.vector.app.features.settings.troubleshoot.TestAvailableUnifiedPushDistributors
|
||||
|
@ -35,7 +36,7 @@ import im.vector.app.gplay.features.settings.troubleshoot.TestPlayServices
|
|||
import im.vector.app.gplay.features.settings.troubleshoot.TestTokenRegistration
|
||||
import javax.inject.Inject
|
||||
|
||||
class NotificationTroubleshootTestManagerFactory @Inject constructor(
|
||||
class GoogleNotificationTroubleshootTestManagerFactory @Inject constructor(
|
||||
private val unifiedPushHelper: UnifiedPushHelper,
|
||||
private val testSystemSettings: TestSystemSettings,
|
||||
private val testAccountSettings: TestAccountSettings,
|
||||
|
@ -52,9 +53,9 @@ class NotificationTroubleshootTestManagerFactory @Inject constructor(
|
|||
private val testPushFromPushGateway: TestPushFromPushGateway,
|
||||
private val testNotification: TestNotification,
|
||||
private val vectorFeatures: VectorFeatures,
|
||||
) {
|
||||
) : NotificationTroubleshootTestManagerFactory {
|
||||
|
||||
fun create(fragment: Fragment): NotificationTroubleshootTestManager {
|
||||
override fun create(fragment: Fragment): NotificationTroubleshootTestManager {
|
||||
val mgr = NotificationTroubleshootTestManager(fragment)
|
||||
mgr.addTest(testSystemSettings)
|
||||
mgr.addTest(testAccountSettings)
|
|
@ -187,7 +187,6 @@
|
|||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".features.home.HomeActivity" />
|
||||
</activity>
|
||||
<activity android:name=".features.debug.DebugMenuActivity" />
|
||||
<activity android:name=".features.createdirect.CreateDirectRoomActivity" />
|
||||
<activity android:name=".features.invite.InviteUsersToRoomActivity" />
|
||||
<activity android:name=".features.webview.VectorWebViewActivity" />
|
||||
|
|
|
@ -25,23 +25,30 @@ import android.content.res.Configuration
|
|||
import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import android.os.StrictMode
|
||||
import android.view.Gravity
|
||||
import androidx.core.provider.FontRequest
|
||||
import androidx.core.provider.FontsContractCompat
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import androidx.multidex.MultiDex
|
||||
import androidx.recyclerview.widget.SnapHelper
|
||||
import com.airbnb.epoxy.Carousel
|
||||
import com.airbnb.epoxy.EpoxyAsyncUtil
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
import com.airbnb.mvrx.Mavericks
|
||||
import com.facebook.stetho.Stetho
|
||||
import com.gabrielittner.threetenbp.LazyThreeTen
|
||||
import com.github.rubensousa.gravitysnaphelper.GravitySnapHelper
|
||||
import com.mapbox.mapboxsdk.Mapbox
|
||||
import com.vanniktech.emoji.EmojiManager
|
||||
import com.vanniktech.emoji.google.GoogleEmojiProvider
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import im.vector.app.config.Config
|
||||
import im.vector.app.core.debug.FlipperProxy
|
||||
import im.vector.app.core.debug.LeakDetector
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.pushers.FcmHelper
|
||||
import im.vector.app.core.resources.BuildMeta
|
||||
import im.vector.app.features.analytics.VectorAnalytics
|
||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
|
@ -59,8 +66,6 @@ import im.vector.app.features.settings.VectorLocale
|
|||
import im.vector.app.features.settings.VectorPreferences
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
import im.vector.app.features.version.VersionProvider
|
||||
import im.vector.app.flipper.FlipperProxy
|
||||
import im.vector.app.push.fcm.FcmHelper
|
||||
import org.jitsi.meet.sdk.log.JitsiMeetDefaultLogHandler
|
||||
import org.matrix.android.sdk.api.Matrix
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
|
@ -102,6 +107,7 @@ class VectorApplication :
|
|||
@Inject lateinit var matrix: Matrix
|
||||
@Inject lateinit var fcmHelper: FcmHelper
|
||||
@Inject lateinit var buildMeta: BuildMeta
|
||||
@Inject lateinit var leakDetector: LeakDetector
|
||||
|
||||
// font thread handler
|
||||
private var fontThreadHandler: Handler? = null
|
||||
|
@ -141,8 +147,9 @@ class VectorApplication :
|
|||
logInfo()
|
||||
LazyThreeTen.init(this)
|
||||
Mavericks.initialize(debugMode = false)
|
||||
EpoxyController.defaultDiffingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
|
||||
EpoxyController.defaultModelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
|
||||
|
||||
configureEpoxy()
|
||||
|
||||
registerActivityLifecycleCallbacks(VectorActivityLifecycleCallbacks(popupAlertManager))
|
||||
val fontRequest = FontRequest(
|
||||
"com.google.android.gms.fonts",
|
||||
|
@ -196,6 +203,18 @@ class VectorApplication :
|
|||
|
||||
// Initialize Mapbox before inflating mapViews
|
||||
Mapbox.getInstance(this)
|
||||
|
||||
initMemoryLeakAnalysis()
|
||||
}
|
||||
|
||||
private fun configureEpoxy() {
|
||||
EpoxyController.defaultDiffingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
|
||||
EpoxyController.defaultModelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
|
||||
Carousel.setDefaultGlobalSnapHelperFactory(object : Carousel.SnapHelperFactory() {
|
||||
override fun buildSnapHelper(context: Context?): SnapHelper {
|
||||
return GravitySnapHelper(Gravity.START)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun enableStrictModeIfNeeded() {
|
||||
|
@ -251,4 +270,8 @@ class VectorApplication :
|
|||
handlerThread.start()
|
||||
return Handler(handlerThread.looper)
|
||||
}
|
||||
|
||||
private fun initMemoryLeakAnalysis() {
|
||||
leakDetector.enable(vectorPreferences.isMemoryLeakAnalysisEnabled())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,10 +14,10 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.nightly
|
||||
package im.vector.app.core.debug
|
||||
|
||||
import javax.inject.Inject
|
||||
import android.content.Context
|
||||
|
||||
class NightlyProxy @Inject constructor() {
|
||||
fun onHomeResumed() = Unit
|
||||
interface DebugNavigator {
|
||||
fun openDebugMenu(context: Context)
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2020 New Vector Ltd
|
||||
* Copyright (c) 2022 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -14,9 +14,11 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app
|
||||
package im.vector.app.core.debug
|
||||
|
||||
import android.content.Context
|
||||
|
||||
// No op
|
||||
fun openOssLicensesMenuActivity(@Suppress("UNUSED_PARAMETER") context: Context) = Unit
|
||||
interface DebugReceiver {
|
||||
fun register(context: Context)
|
||||
fun unregister(context: Context)
|
||||
}
|
|
@ -14,18 +14,12 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.flipper
|
||||
package im.vector.app.core.debug
|
||||
|
||||
import okhttp3.Interceptor
|
||||
import org.matrix.android.sdk.api.Matrix
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* No op version.
|
||||
*/
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
class FlipperProxy @Inject constructor() {
|
||||
fun init(matrix: Matrix) {}
|
||||
|
||||
fun getNetworkInterceptor(): Interceptor? = null
|
||||
interface FlipperProxy {
|
||||
fun init(matrix: Matrix)
|
||||
fun networkInterceptor(): Interceptor?
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
* Copyright (c) 2022 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -14,9 +14,11 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.debug
|
||||
package im.vector.app.core.debug
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
||||
// This activity is not accessible
|
||||
class DebugMenuActivity : AppCompatActivity()
|
||||
/**
|
||||
* Used for memory leak analysis control.
|
||||
*/
|
||||
interface LeakDetector {
|
||||
fun enable(enable: Boolean)
|
||||
}
|
|
@ -34,6 +34,7 @@ import im.vector.app.EmojiSpanify
|
|||
import im.vector.app.SpaceStateHandler
|
||||
import im.vector.app.SpaceStateHandlerImpl
|
||||
import im.vector.app.config.Config
|
||||
import im.vector.app.core.debug.FlipperProxy
|
||||
import im.vector.app.core.dispatchers.CoroutineDispatchers
|
||||
import im.vector.app.core.error.DefaultErrorFormatter
|
||||
import im.vector.app.core.error.ErrorFormatter
|
||||
|
@ -57,7 +58,6 @@ import im.vector.app.features.settings.FontScalePreferencesImpl
|
|||
import im.vector.app.features.settings.VectorPreferences
|
||||
import im.vector.app.features.ui.SharedPreferencesUiStateRepository
|
||||
import im.vector.app.features.ui.UiStateRepository
|
||||
import im.vector.app.flipper.FlipperProxy
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
@ -144,7 +144,7 @@ object VectorStaticModule {
|
|||
roomDisplayNameFallbackProvider = vectorRoomDisplayNameFallbackProvider,
|
||||
threadMessagesEnabledDefault = vectorPreferences.areThreadMessagesEnabled(),
|
||||
networkInterceptors = listOfNotNull(
|
||||
flipperProxy.getNetworkInterceptor(),
|
||||
flipperProxy.networkInterceptor(),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|||
import com.google.android.material.snackbar.Snackbar
|
||||
import dagger.hilt.android.EntryPointAccessors
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.debug.DebugReceiver
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.di.ActivityEntryPoint
|
||||
import im.vector.app.core.dialogs.DialogLocker
|
||||
|
@ -91,7 +92,6 @@ import im.vector.app.features.settings.FontScalePreferencesImpl
|
|||
import im.vector.app.features.settings.VectorPreferences
|
||||
import im.vector.app.features.themes.ActivityOtherThemes
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
import im.vector.app.receivers.DebugReceiver
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
|
@ -161,6 +161,9 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
|||
@Inject lateinit var buildMeta: BuildMeta
|
||||
@Inject lateinit var fontScalePreferences: FontScalePreferences
|
||||
|
||||
// For debug only
|
||||
@Inject lateinit var debugReceiver: DebugReceiver
|
||||
|
||||
@Inject
|
||||
lateinit var vectorFeatures: VectorFeatures
|
||||
|
||||
|
@ -176,9 +179,6 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
|||
|
||||
private var savedInstanceState: Bundle? = null
|
||||
|
||||
// For debug only
|
||||
private var debugReceiver: DebugReceiver? = null
|
||||
|
||||
private val restorables = ArrayList<Restorable>()
|
||||
|
||||
override fun attachBaseContext(base: Context) {
|
||||
|
@ -418,13 +418,7 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
|||
if (this !is BugReportActivity && vectorPreferences.useRageshake()) {
|
||||
rageShake.start()
|
||||
}
|
||||
DebugReceiver
|
||||
.getIntentFilter(this)
|
||||
.takeIf { buildMeta.isDebug }
|
||||
?.let {
|
||||
debugReceiver = DebugReceiver()
|
||||
registerReceiver(debugReceiver, it)
|
||||
}
|
||||
debugReceiver.register(this)
|
||||
}
|
||||
|
||||
private val postResumeScheduledActions = mutableListOf<() -> Unit>()
|
||||
|
@ -454,11 +448,7 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
|||
Timber.i("onPause Activity ${javaClass.simpleName}")
|
||||
|
||||
rageShake.stop()
|
||||
|
||||
debugReceiver?.let {
|
||||
unregisterReceiver(debugReceiver)
|
||||
debugReceiver = null
|
||||
}
|
||||
debugReceiver.unregister(this)
|
||||
}
|
||||
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
|
|
51
vector/src/main/java/im/vector/app/core/pushers/FcmHelper.kt
Normal file
51
vector/src/main/java/im/vector/app/core/pushers/FcmHelper.kt
Normal 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)
|
||||
}
|
|
@ -28,7 +28,6 @@ import im.vector.app.core.utils.getApplicationLabel
|
|||
import im.vector.app.features.VectorFeatures
|
||||
import im.vector.app.features.settings.BackgroundSyncMode
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import im.vector.app.push.fcm.FcmHelper
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.Matrix
|
||||
import org.matrix.android.sdk.api.cache.CacheStrategy
|
||||
|
|
|
@ -34,6 +34,7 @@ interface VectorFeatures {
|
|||
fun forceUsageOfOpusEncoder(): Boolean
|
||||
fun shouldStartDmOnFirstMessage(): Boolean
|
||||
fun isNewAppLayoutEnabled(): Boolean
|
||||
fun isNewDeviceManagementEnabled(): Boolean
|
||||
}
|
||||
|
||||
class DefaultVectorFeatures : VectorFeatures {
|
||||
|
@ -50,4 +51,5 @@ class DefaultVectorFeatures : VectorFeatures {
|
|||
override fun forceUsageOfOpusEncoder(): Boolean = false
|
||||
override fun shouldStartDmOnFirstMessage(): Boolean = false
|
||||
override fun isNewAppLayoutEnabled(): Boolean = false
|
||||
override fun isNewDeviceManagementEnabled(): Boolean = false
|
||||
}
|
||||
|
|
|
@ -241,6 +241,7 @@ class VectorCallActivity :
|
|||
detachRenderersIfNeeded()
|
||||
turnScreenOffAndKeyguardOn()
|
||||
removeOnPictureInPictureModeChangedListener(pictureInPictureModeChangedInfoConsumer)
|
||||
screenCaptureServiceConnection.unbind()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
|
|
|
@ -47,6 +47,10 @@ class ScreenCaptureServiceConnection @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
callback = null
|
||||
}
|
||||
|
||||
fun stopScreenCapturing() {
|
||||
screenCaptureAndroidService?.stopService()
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ import im.vector.app.core.extensions.replaceFragment
|
|||
import im.vector.app.core.extensions.validateBackPressed
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
import im.vector.app.core.platform.VectorMenuProvider
|
||||
import im.vector.app.core.pushers.FcmHelper
|
||||
import im.vector.app.core.pushers.PushersManager
|
||||
import im.vector.app.core.pushers.UnifiedPushHelper
|
||||
import im.vector.app.core.utils.startSharePlainTextIntent
|
||||
|
@ -79,8 +80,6 @@ import im.vector.app.features.spaces.invite.SpaceInviteBottomSheet
|
|||
import im.vector.app.features.spaces.share.ShareSpaceBottomSheet
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
|
||||
import im.vector.app.nightly.NightlyProxy
|
||||
import im.vector.app.push.fcm.FcmHelper
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.app.features.home.room.list.home
|
||||
|
||||
import im.vector.app.core.platform.VectorViewModelAction
|
||||
import im.vector.app.features.home.room.list.home.filter.HomeRoomFilter
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
|
||||
|
||||
|
@ -25,4 +26,5 @@ sealed class HomeRoomListAction : VectorViewModelAction {
|
|||
data class ChangeRoomNotificationState(val roomId: String, val notificationState: RoomNotificationState) : HomeRoomListAction()
|
||||
data class ToggleTag(val roomId: String, val tag: String) : HomeRoomListAction()
|
||||
data class LeaveRoom(val roomId: String) : HomeRoomListAction()
|
||||
data class ChangeRoomFilter(val filter: HomeRoomFilter) : HomeRoomListAction()
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import android.view.ViewGroup
|
|||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.ConcatAdapter
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.airbnb.epoxy.EpoxyControllerAdapter
|
||||
import com.airbnb.epoxy.OnModelBuildFinishedListener
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
|
@ -30,19 +31,21 @@ import com.airbnb.mvrx.withState
|
|||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.LayoutManagerStateRestorer
|
||||
import im.vector.app.core.extensions.cleanup
|
||||
import im.vector.app.core.platform.StateView
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.core.resources.UserPreferencesProvider
|
||||
import im.vector.app.databinding.FragmentRoomListBinding
|
||||
import im.vector.app.features.analytics.plan.ViewRoom
|
||||
import im.vector.app.features.home.RoomListDisplayMode
|
||||
import im.vector.app.features.home.room.list.RoomListAnimator
|
||||
import im.vector.app.features.home.room.list.RoomListListener
|
||||
import im.vector.app.features.home.room.list.RoomSummaryItemFactory
|
||||
import im.vector.app.features.home.room.list.RoomSummaryPagedController
|
||||
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet
|
||||
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction
|
||||
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
|
||||
import im.vector.app.features.home.room.list.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 kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
|
@ -53,7 +56,8 @@ import javax.inject.Inject
|
|||
|
||||
class HomeRoomListFragment @Inject constructor(
|
||||
private val roomSummaryItemFactory: RoomSummaryItemFactory,
|
||||
private val userPreferencesProvider: UserPreferencesProvider
|
||||
private val userPreferencesProvider: UserPreferencesProvider,
|
||||
private val recentRoomCarouselController: RecentRoomCarouselController
|
||||
) : VectorBaseFragment<FragmentRoomListBinding>(),
|
||||
RoomListListener {
|
||||
|
||||
|
@ -71,7 +75,7 @@ class HomeRoomListFragment @Inject constructor(
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java)
|
||||
sharedActionViewModel = activityViewModelProvider[RoomListQuickActionsSharedActionViewModel::class.java]
|
||||
sharedActionViewModel
|
||||
.stream()
|
||||
.onEach { handleQuickActions(it) }
|
||||
|
@ -90,6 +94,7 @@ class HomeRoomListFragment @Inject constructor(
|
|||
}
|
||||
|
||||
setupRecyclerView()
|
||||
setupFabs()
|
||||
}
|
||||
|
||||
private fun setupRecyclerView() {
|
||||
|
@ -108,6 +113,41 @@ class HomeRoomListFragment @Inject constructor(
|
|||
views.roomListView.adapter = concatAdapter
|
||||
}
|
||||
|
||||
private fun setupFabs() {
|
||||
showFABs()
|
||||
|
||||
views.newLayoutCreateChatButton.setOnClickListener {
|
||||
// Click action for create chat modal goes here (Issue #6717)
|
||||
}
|
||||
|
||||
views.newLayoutOpenSpacesButton.setOnClickListener {
|
||||
// Click action for open spaces modal goes here (Issue #6499)
|
||||
}
|
||||
|
||||
// Hide FABs when list is scrolling
|
||||
views.roomListView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||
views.createChatFabMenu.handler.removeCallbacksAndMessages(null)
|
||||
|
||||
when (newState) {
|
||||
RecyclerView.SCROLL_STATE_IDLE -> views.createChatFabMenu.postDelayed(::showFABs, 250)
|
||||
RecyclerView.SCROLL_STATE_DRAGGING,
|
||||
RecyclerView.SCROLL_STATE_SETTLING -> hideFABs()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun showFABs() {
|
||||
views.newLayoutCreateChatButton.show()
|
||||
views.newLayoutOpenSpacesButton.show()
|
||||
}
|
||||
|
||||
private fun hideFABs() {
|
||||
views.newLayoutCreateChatButton.hide()
|
||||
views.newLayoutOpenSpacesButton.hide()
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(roomListViewModel) { state ->
|
||||
views.stateView.state = state.state
|
||||
}
|
||||
|
@ -167,22 +207,36 @@ class HomeRoomListFragment @Inject constructor(
|
|||
.show()
|
||||
}
|
||||
|
||||
private fun getAdapterForData(data: HomeRoomSection): EpoxyControllerAdapter {
|
||||
return when (data) {
|
||||
private fun getAdapterForData(section: HomeRoomSection): EpoxyControllerAdapter {
|
||||
return when (section) {
|
||||
is HomeRoomSection.RoomSummaryData -> {
|
||||
RoomSummaryPagedController(
|
||||
HomeFilteredRoomsController(
|
||||
roomSummaryItemFactory,
|
||||
RoomListDisplayMode.ROOMS
|
||||
showFilters = section.showFilters,
|
||||
).also { controller ->
|
||||
controller.listener = this
|
||||
data.list.observe(viewLifecycleOwner) { list ->
|
||||
controller.onFilterChanged = ::onRoomFilterChanged
|
||||
section.filtersData.onEach {
|
||||
controller.submitFiltersData(it)
|
||||
}.launchIn(lifecycleScope)
|
||||
section.list.observe(viewLifecycleOwner) { list ->
|
||||
controller.submitList(list)
|
||||
}
|
||||
}.adapter
|
||||
}
|
||||
is HomeRoomSection.RecentRoomsData -> recentRoomCarouselController.also { controller ->
|
||||
controller.listener = this
|
||||
section.list.observe(viewLifecycleOwner) { list ->
|
||||
controller.submitList(list)
|
||||
}
|
||||
}.adapter
|
||||
}
|
||||
}
|
||||
|
||||
private fun onRoomFilterChanged(filter: HomeRoomFilter) {
|
||||
roomListViewModel.handle(HomeRoomListAction.ChangeRoomFilter(filter))
|
||||
}
|
||||
|
||||
private fun handleSelectRoom(event: HomeRoomListViewEvents.SelectRoom, isInviteAlreadyAccepted: Boolean) {
|
||||
navigator.openRoom(
|
||||
context = requireActivity(),
|
||||
|
@ -192,6 +246,12 @@ class HomeRoomListFragment @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
views.roomListView.cleanup()
|
||||
recentRoomCarouselController.listener = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
// region RoomListListener
|
||||
|
||||
override fun onRoomClicked(room: RoomSummary) {
|
||||
|
|
|
@ -27,31 +27,38 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
|||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||
import im.vector.app.core.platform.StateView
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import im.vector.app.features.home.room.list.home.filter.HomeRoomFilter
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.query.SpaceFilter
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import org.matrix.android.sdk.api.query.RoomCategoryFilter
|
||||
import org.matrix.android.sdk.api.query.RoomTagQueryFilter
|
||||
import org.matrix.android.sdk.api.query.toActiveSpaceOrNoFilter
|
||||
import org.matrix.android.sdk.api.query.toActiveSpaceOrOrphanRooms
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.getRoom
|
||||
import org.matrix.android.sdk.api.session.room.RoomSortOrder
|
||||
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
|
||||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.room.state.isPublic
|
||||
import org.matrix.android.sdk.flow.flow
|
||||
|
||||
class HomeRoomListViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: HomeRoomListViewState,
|
||||
private val session: Session,
|
||||
private val spaceStateHandler: SpaceStateHandler,
|
||||
private val vectorPreferences: VectorPreferences,
|
||||
) : VectorViewModel<HomeRoomListViewState, HomeRoomListAction, HomeRoomListViewEvents>(initialState) {
|
||||
|
||||
@AssistedFactory
|
||||
|
@ -71,6 +78,8 @@ class HomeRoomListViewModel @AssistedInject constructor(
|
|||
private val _sections = MutableSharedFlow<Set<HomeRoomSection>>(replay = 1)
|
||||
val sections = _sections.asSharedFlow()
|
||||
|
||||
private var filteredPagedRoomSummariesLive: UpdatableLivePageResult? = null
|
||||
|
||||
init {
|
||||
configureSections()
|
||||
}
|
||||
|
@ -78,7 +87,8 @@ class HomeRoomListViewModel @AssistedInject constructor(
|
|||
private fun configureSections() {
|
||||
val newSections = mutableSetOf<HomeRoomSection>()
|
||||
|
||||
newSections.add(getAllRoomsSection())
|
||||
newSections.add(getRecentRoomsSection())
|
||||
newSections.add(getFilteredRoomsSection())
|
||||
|
||||
viewModelScope.launch {
|
||||
_sections.emit(newSections)
|
||||
|
@ -89,15 +99,33 @@ class HomeRoomListViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getAllRoomsSection(): HomeRoomSection.RoomSummaryData {
|
||||
private fun getRecentRoomsSection(): HomeRoomSection {
|
||||
val liveList = session.roomService()
|
||||
.getBreadcrumbsLive(roomSummaryQueryParams {
|
||||
displayName = QueryStringValue.NoCondition
|
||||
memberships = listOf(Membership.JOIN)
|
||||
})
|
||||
|
||||
return HomeRoomSection.RecentRoomsData(
|
||||
list = liveList
|
||||
)
|
||||
}
|
||||
|
||||
private fun getFilteredRoomsSection(): HomeRoomSection.RoomSummaryData {
|
||||
val builder = RoomSummaryQueryParams.Builder().also {
|
||||
it.memberships = listOf(Membership.JOIN)
|
||||
}
|
||||
|
||||
val filteredPagedRoomSummariesLive = session.roomService().getFilteredPagedRoomSummariesLive(
|
||||
builder.build(),
|
||||
pagedListConfig
|
||||
)
|
||||
val params = getFilteredQueryParams(HomeRoomFilter.ALL, builder.build())
|
||||
val sortOrder = RoomSortOrder.ACTIVITY // #6506
|
||||
|
||||
val liveResults = session.roomService().getFilteredPagedRoomSummariesLive(
|
||||
params,
|
||||
pagedListConfig,
|
||||
sortOrder
|
||||
).also {
|
||||
this.filteredPagedRoomSummariesLive = it
|
||||
}
|
||||
|
||||
spaceStateHandler.getSelectedSpaceFlow()
|
||||
.distinctUntilChanged()
|
||||
|
@ -106,20 +134,83 @@ class HomeRoomListViewModel @AssistedInject constructor(
|
|||
}
|
||||
.onEach { selectedSpaceOption ->
|
||||
val selectedSpace = selectedSpaceOption.orNull()
|
||||
filteredPagedRoomSummariesLive.queryParams = filteredPagedRoomSummariesLive.queryParams.copy(
|
||||
spaceFilter = getSpaceFilter(selectedSpaceId = selectedSpace?.roomId)
|
||||
liveResults.queryParams = liveResults.queryParams.copy(
|
||||
spaceFilter = selectedSpace?.roomId.toActiveSpaceOrNoFilter()
|
||||
)
|
||||
}.launchIn(viewModelScope)
|
||||
|
||||
return HomeRoomSection.RoomSummaryData(
|
||||
list = filteredPagedRoomSummariesLive.livePagedList
|
||||
list = liveResults.livePagedList,
|
||||
showFilters = true, // #6506
|
||||
filtersData = getFiltersDataFlow()
|
||||
)
|
||||
}
|
||||
|
||||
private fun getSpaceFilter(selectedSpaceId: String?): SpaceFilter {
|
||||
return when {
|
||||
vectorPreferences.prefSpacesShowAllRoomInHome() -> selectedSpaceId.toActiveSpaceOrNoFilter()
|
||||
else -> selectedSpaceId.toActiveSpaceOrOrphanRooms()
|
||||
private fun getFiltersDataFlow(): SharedFlow<List<HomeRoomFilter>> {
|
||||
val flow = MutableSharedFlow<List<HomeRoomFilter>>(replay = 1)
|
||||
|
||||
val favouritesFlow = session.flow()
|
||||
.liveRoomSummaries(
|
||||
RoomSummaryQueryParams.Builder().also { builder ->
|
||||
builder.roomTagQueryFilter = RoomTagQueryFilter(true, null, null)
|
||||
}.build()
|
||||
)
|
||||
.map { it.isNotEmpty() }
|
||||
.distinctUntilChanged()
|
||||
|
||||
val dmsFLow = session.flow()
|
||||
.liveRoomSummaries(
|
||||
RoomSummaryQueryParams.Builder().also { builder ->
|
||||
builder.memberships = listOf(Membership.JOIN)
|
||||
builder.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
|
||||
}.build()
|
||||
)
|
||||
.map { it.isNotEmpty() }
|
||||
.distinctUntilChanged()
|
||||
|
||||
favouritesFlow.combine(dmsFLow) { hasFavourite, hasDm ->
|
||||
hasFavourite to hasDm
|
||||
}.onEach { (hasFavourite, hasDm) ->
|
||||
val filtersData = mutableListOf(
|
||||
HomeRoomFilter.ALL,
|
||||
HomeRoomFilter.UNREADS
|
||||
)
|
||||
if (hasFavourite) {
|
||||
filtersData.add(
|
||||
HomeRoomFilter.FAVOURITES
|
||||
)
|
||||
}
|
||||
if (hasDm) {
|
||||
filtersData.add(
|
||||
HomeRoomFilter.PEOPlE
|
||||
)
|
||||
}
|
||||
|
||||
flow.emit(filtersData)
|
||||
}.launchIn(viewModelScope)
|
||||
|
||||
return flow
|
||||
}
|
||||
|
||||
private fun getFilteredQueryParams(filter: HomeRoomFilter, currentParams: RoomSummaryQueryParams): RoomSummaryQueryParams {
|
||||
return when (filter) {
|
||||
HomeRoomFilter.ALL -> currentParams.copy(
|
||||
roomCategoryFilter = null,
|
||||
roomTagQueryFilter = null
|
||||
)
|
||||
HomeRoomFilter.UNREADS -> currentParams.copy(
|
||||
roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS,
|
||||
roomTagQueryFilter = RoomTagQueryFilter(null, false, null)
|
||||
)
|
||||
HomeRoomFilter.FAVOURITES ->
|
||||
currentParams.copy(
|
||||
roomCategoryFilter = null,
|
||||
roomTagQueryFilter = RoomTagQueryFilter(true, null, null)
|
||||
)
|
||||
HomeRoomFilter.PEOPlE -> currentParams.copy(
|
||||
roomCategoryFilter = RoomCategoryFilter.ONLY_DM,
|
||||
roomTagQueryFilter = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,6 +220,13 @@ class HomeRoomListViewModel @AssistedInject constructor(
|
|||
is HomeRoomListAction.LeaveRoom -> handleLeaveRoom(action)
|
||||
is HomeRoomListAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action)
|
||||
is HomeRoomListAction.ToggleTag -> handleToggleTag(action)
|
||||
is HomeRoomListAction.ChangeRoomFilter -> handleChangeRoomFilter(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleChangeRoomFilter(action: HomeRoomListAction.ChangeRoomFilter) {
|
||||
filteredPagedRoomSummariesLive?.let { liveResults ->
|
||||
liveResults.queryParams = getFilteredQueryParams(action.filter, liveResults.queryParams)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,10 +18,18 @@ package im.vector.app.features.home.room.list.home
|
|||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.paging.PagedList
|
||||
import im.vector.app.features.home.room.list.home.filter.HomeRoomFilter
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
|
||||
sealed class HomeRoomSection {
|
||||
data class RoomSummaryData(
|
||||
val list: LiveData<PagedList<RoomSummary>>
|
||||
val list: LiveData<PagedList<RoomSummary>>,
|
||||
val showFilters: Boolean,
|
||||
val filtersData: SharedFlow<List<HomeRoomFilter>>
|
||||
) : HomeRoomSection()
|
||||
|
||||
data class RecentRoomsData(
|
||||
val list: LiveData<List<RoomSummary>>
|
||||
) : HomeRoomSection()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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.recent
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.util.TypedValue
|
||||
import com.airbnb.epoxy.Carousel
|
||||
import com.airbnb.epoxy.CarouselModelBuilder
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
import com.airbnb.epoxy.EpoxyModel
|
||||
import com.airbnb.epoxy.carousel
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.home.room.list.RoomListListener
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import javax.inject.Inject
|
||||
|
||||
class RecentRoomCarouselController @Inject constructor(
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val resources: Resources,
|
||||
) : EpoxyController() {
|
||||
|
||||
private var data: List<RoomSummary>? = null
|
||||
var listener: RoomListListener? = null
|
||||
|
||||
private val hPadding = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
16f,
|
||||
resources.displayMetrics
|
||||
).toInt()
|
||||
|
||||
private val itemSpacing = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
24f,
|
||||
resources.displayMetrics
|
||||
).toInt()
|
||||
|
||||
fun submitList(recentList: List<RoomSummary>) {
|
||||
this.data = recentList
|
||||
requestModelBuild()
|
||||
}
|
||||
|
||||
override fun buildModels() {
|
||||
val host = this
|
||||
data?.let { data ->
|
||||
carousel {
|
||||
id("recents_carousel")
|
||||
padding(Carousel.Padding(host.hPadding, host.itemSpacing))
|
||||
withModelsFrom(data) { roomSummary ->
|
||||
val onClick = host.listener?.let { it::onRoomClicked }
|
||||
val onLongClick = host.listener?.let { it::onRoomLongClicked }
|
||||
|
||||
RecentRoomItem_()
|
||||
.id(roomSummary.roomId)
|
||||
.avatarRenderer(host.avatarRenderer)
|
||||
.matrixItem(roomSummary.toMatrixItem())
|
||||
.unreadNotificationCount(roomSummary.notificationCount)
|
||||
.showHighlighted(roomSummary.highlightCount > 0)
|
||||
.itemLongClickListener { _ -> onLongClick?.invoke(roomSummary) ?: false }
|
||||
.itemClickListener { onClick?.invoke(roomSummary) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <T> CarouselModelBuilder.withModelsFrom(
|
||||
items: List<T>,
|
||||
modelBuilder: (T) -> EpoxyModel<*>
|
||||
) {
|
||||
models(items.map { modelBuilder(it) })
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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.recent
|
||||
|
||||
import android.view.HapticFeedbackConstants
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.ClickListener
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.core.epoxy.onClick
|
||||
import im.vector.app.features.displayname.getBestName
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.home.room.list.UnreadCounterBadgeView
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
|
||||
@EpoxyModelClass
|
||||
abstract class RecentRoomItem : VectorEpoxyModel<RecentRoomItem.Holder>(R.layout.item_recent_room) {
|
||||
|
||||
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
||||
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
||||
@EpoxyAttribute var unreadNotificationCount: Int = 0
|
||||
@EpoxyAttribute var showHighlighted: Boolean = false
|
||||
|
||||
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
|
||||
var itemLongClickListener: View.OnLongClickListener? = null
|
||||
|
||||
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
|
||||
var itemClickListener: ClickListener? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
|
||||
holder.rootView.onClick(itemClickListener)
|
||||
holder.rootView.setOnLongClickListener {
|
||||
it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||
itemLongClickListener?.onLongClick(it) ?: false
|
||||
}
|
||||
|
||||
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
||||
holder.avatarImageView.contentDescription = matrixItem.getBestName()
|
||||
holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted))
|
||||
holder.title.text = matrixItem.getBestName()
|
||||
}
|
||||
|
||||
override fun unbind(holder: Holder) {
|
||||
holder.rootView.setOnClickListener(null)
|
||||
holder.rootView.setOnLongClickListener(null)
|
||||
avatarRenderer.clear(holder.avatarImageView)
|
||||
super.unbind(holder)
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val unreadCounterBadgeView by bind<UnreadCounterBadgeView>(R.id.recentUnreadCounterBadgeView)
|
||||
val avatarImageView by bind<ImageView>(R.id.recentImageView)
|
||||
val title by bind<TextView>(R.id.recentTitle)
|
||||
val rootView by bind<ViewGroup>(R.id.recentRoot)
|
||||
}
|
||||
}
|
|
@ -34,6 +34,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|||
import im.vector.app.R
|
||||
import im.vector.app.SpaceStateHandler
|
||||
import im.vector.app.config.OnboardingVariant
|
||||
import im.vector.app.core.debug.DebugNavigator
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.error.fatalError
|
||||
import im.vector.app.features.VectorFeatures
|
||||
|
@ -51,7 +52,6 @@ import im.vector.app.features.crypto.recover.BootstrapBottomSheet
|
|||
import im.vector.app.features.crypto.recover.SetupMode
|
||||
import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider
|
||||
import im.vector.app.features.crypto.verification.VerificationBottomSheet
|
||||
import im.vector.app.features.debug.DebugMenuActivity
|
||||
import im.vector.app.features.devtools.RoomDevToolActivity
|
||||
import im.vector.app.features.home.room.detail.RoomDetailActivity
|
||||
import im.vector.app.features.home.room.detail.arguments.TimelineArgs
|
||||
|
@ -123,7 +123,8 @@ class DefaultNavigator @Inject constructor(
|
|||
private val spaceStateHandler: SpaceStateHandler,
|
||||
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider,
|
||||
private val features: VectorFeatures,
|
||||
private val analyticsTracker: AnalyticsTracker
|
||||
private val analyticsTracker: AnalyticsTracker,
|
||||
private val debugNavigator: DebugNavigator,
|
||||
) : Navigator {
|
||||
|
||||
override fun openLogin(context: Context, loginConfig: LoginConfig?, flags: Int) {
|
||||
|
@ -367,7 +368,7 @@ class DefaultNavigator @Inject constructor(
|
|||
}
|
||||
|
||||
override fun openDebug(context: Context) {
|
||||
context.startActivity(Intent(context, DebugMenuActivity::class.java))
|
||||
debugNavigator.openDebugMenu(context)
|
||||
}
|
||||
|
||||
override fun openKeysBackupSetup(context: Context, showManualExport: Boolean) {
|
||||
|
|
|
@ -65,9 +65,10 @@ class NotifiableEventResolver @Inject constructor(
|
|||
private val buildMeta: BuildMeta,
|
||||
) {
|
||||
|
||||
// private val eventDisplay = RiotEventDisplay(context)
|
||||
private val nonEncryptedNotifiableEventTypes: List<String> =
|
||||
listOf(EventType.MESSAGE) + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO
|
||||
|
||||
suspend fun resolveEvent(event: Event/*, roomState: RoomState?, bingRule: PushRule?*/, session: Session, isNoisy: Boolean): NotifiableEvent? {
|
||||
suspend fun resolveEvent(event: Event, session: Session, isNoisy: Boolean): NotifiableEvent? {
|
||||
val roomID = event.roomId ?: return null
|
||||
val eventId = event.eventId ?: return null
|
||||
if (event.getClearType() == EventType.STATE_ROOM_MEMBER) {
|
||||
|
@ -75,7 +76,7 @@ class NotifiableEventResolver @Inject constructor(
|
|||
}
|
||||
val timelineEvent = session.getRoom(roomID)?.getTimelineEvent(eventId) ?: return null
|
||||
return when (event.getClearType()) {
|
||||
EventType.MESSAGE,
|
||||
in nonEncryptedNotifiableEventTypes,
|
||||
EventType.ENCRYPTED -> {
|
||||
resolveMessageEvent(timelineEvent, session, canBeReplaced = false, isNoisy = isNoisy)
|
||||
}
|
||||
|
@ -161,9 +162,7 @@ class NotifiableEventResolver @Inject constructor(
|
|||
event.attemptToDecryptIfNeeded(session)
|
||||
// only convert encrypted messages to NotifiableMessageEvents
|
||||
when (event.root.getClearType()) {
|
||||
EventType.MESSAGE,
|
||||
in EventType.POLL_START,
|
||||
in EventType.STATE_ROOM_BEACON_INFO -> {
|
||||
in nonEncryptedNotifiableEventTypes -> {
|
||||
val body = displayableEventFormatter.format(event, isDm = room.roomSummary()?.isDirect.orFalse(), appendAuthor = false).toString()
|
||||
val roomName = room.roomSummary()?.displayName ?: ""
|
||||
val senderDisplayName = event.senderInfo.disambiguatedDisplayName
|
||||
|
|
|
@ -194,25 +194,32 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private suspend fun checkUserNameAvailability(userName: String) {
|
||||
when (val result = registrationWizard.registrationAvailable(userName)) {
|
||||
RegistrationAvailability.Available -> {
|
||||
setState {
|
||||
copy(
|
||||
registrationState = RegistrationState(
|
||||
isUserNameAvailable = true,
|
||||
selectedMatrixId = when {
|
||||
userName.isMatrixId() -> userName
|
||||
else -> "@$userName:${selectedHomeserver.userFacingUrl.toReducedUrl()}"
|
||||
},
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
runCatching { registrationWizard.registrationAvailable(userName) }.fold(
|
||||
onSuccess = { result ->
|
||||
when (result) {
|
||||
RegistrationAvailability.Available -> {
|
||||
setState {
|
||||
copy(
|
||||
registrationState = RegistrationState(
|
||||
isUserNameAvailable = true,
|
||||
selectedMatrixId = when {
|
||||
userName.isMatrixId() -> userName
|
||||
else -> "@$userName:${selectedHomeserver.userFacingUrl.toReducedUrl()}"
|
||||
},
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is RegistrationAvailability.NotAvailable -> {
|
||||
_viewEvents.post(OnboardingViewEvents.Failure(result.failure))
|
||||
}
|
||||
}
|
||||
is RegistrationAvailability.NotAvailable -> {
|
||||
_viewEvents.post(OnboardingViewEvents.Failure(result.failure))
|
||||
}
|
||||
}
|
||||
},
|
||||
onFailure = {
|
||||
_viewEvents.post(OnboardingViewEvents.Failure(it))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun withAction(action: OnboardingAction, block: (OnboardingAction) -> Unit) {
|
||||
|
|
|
@ -24,6 +24,7 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import com.airbnb.mvrx.args
|
||||
import com.airbnb.mvrx.asMavericksArgs
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.replaceFragment
|
||||
|
@ -33,7 +34,7 @@ import im.vector.app.databinding.FragmentPinBinding
|
|||
import im.vector.app.features.MainActivity
|
||||
import im.vector.app.features.MainActivityArgs
|
||||
import im.vector.app.features.pin.lockscreen.biometrics.BiometricAuthError
|
||||
import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguratorProvider
|
||||
import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguration
|
||||
import im.vector.app.features.pin.lockscreen.configuration.LockScreenMode
|
||||
import im.vector.app.features.pin.lockscreen.ui.AuthMethod
|
||||
import im.vector.app.features.pin.lockscreen.ui.LockScreenFragment
|
||||
|
@ -51,7 +52,7 @@ data class PinArgs(
|
|||
class PinFragment @Inject constructor(
|
||||
private val pinCodeStore: PinCodeStore,
|
||||
private val vectorPreferences: VectorPreferences,
|
||||
private val configuratorProvider: LockScreenConfiguratorProvider,
|
||||
private val defaultConfiguration: LockScreenConfiguration,
|
||||
) : VectorBaseFragment<FragmentPinBinding>() {
|
||||
|
||||
private val fragmentArgs: PinArgs by args()
|
||||
|
@ -81,21 +82,17 @@ class PinFragment @Inject constructor(
|
|||
vectorBaseActivity.finish()
|
||||
}
|
||||
}
|
||||
|
||||
configuratorProvider.updateDefaultConfiguration {
|
||||
copy(
|
||||
mode = LockScreenMode.CREATE,
|
||||
title = getString(R.string.create_pin_title),
|
||||
needsNewCodeValidation = true,
|
||||
newCodeConfirmationTitle = getString(R.string.create_pin_confirm_title),
|
||||
)
|
||||
}
|
||||
createFragment.arguments = defaultConfiguration.copy(
|
||||
mode = LockScreenMode.CREATE,
|
||||
title = getString(R.string.create_pin_title),
|
||||
needsNewCodeValidation = true,
|
||||
newCodeConfirmationTitle = getString(R.string.create_pin_confirm_title),
|
||||
).asMavericksArgs()
|
||||
replaceFragment(R.id.pinFragmentContainer, createFragment)
|
||||
}
|
||||
|
||||
private fun showAuthFragment() {
|
||||
val authFragment = LockScreenFragment()
|
||||
val canUseBiometrics = vectorPreferences.useBiometricsToUnlock()
|
||||
authFragment.onLeftButtonClickedListener = View.OnClickListener { displayForgotPinWarningDialog() }
|
||||
authFragment.lockScreenListener = object : LockScreenListener {
|
||||
override fun onAuthenticationFailure(authMethod: AuthMethod) {
|
||||
|
@ -133,18 +130,12 @@ class PinFragment @Inject constructor(
|
|||
.show()
|
||||
}
|
||||
}
|
||||
configuratorProvider.updateDefaultConfiguration {
|
||||
copy(
|
||||
mode = LockScreenMode.VERIFY,
|
||||
title = getString(R.string.auth_pin_title),
|
||||
isStrongBiometricsEnabled = isStrongBiometricsEnabled && canUseBiometrics,
|
||||
isWeakBiometricsEnabled = isWeakBiometricsEnabled && canUseBiometrics,
|
||||
isDeviceCredentialUnlockEnabled = isDeviceCredentialUnlockEnabled && canUseBiometrics,
|
||||
autoStartBiometric = canUseBiometrics,
|
||||
leftButtonTitle = getString(R.string.auth_pin_forgot),
|
||||
clearCodeOnError = true,
|
||||
)
|
||||
}
|
||||
authFragment.arguments = defaultConfiguration.copy(
|
||||
mode = LockScreenMode.VERIFY,
|
||||
title = getString(R.string.auth_pin_title),
|
||||
leftButtonTitle = getString(R.string.auth_pin_forgot),
|
||||
clearCodeOnError = true,
|
||||
).asMavericksArgs()
|
||||
replaceFragment(R.id.pinFragmentContainer, authFragment)
|
||||
}
|
||||
|
||||
|
|
|
@ -31,10 +31,12 @@ import androidx.biometric.BiometricPrompt
|
|||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import im.vector.app.R
|
||||
import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguration
|
||||
import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguratorProvider
|
||||
import im.vector.app.features.pin.lockscreen.crypto.LockScreenKeyRepository
|
||||
import im.vector.app.features.pin.lockscreen.ui.fallbackprompt.FallbackBiometricDialogFragment
|
||||
import im.vector.app.features.pin.lockscreen.utils.DevicePromptCheck
|
||||
|
@ -54,22 +56,24 @@ import kotlinx.coroutines.launch
|
|||
import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider
|
||||
import java.security.KeyStore
|
||||
import javax.crypto.Cipher
|
||||
import javax.inject.Inject
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
/**
|
||||
* This is a helper to manage system authentication (biometric and other types) and the system key.
|
||||
*/
|
||||
class BiometricHelper @Inject constructor(
|
||||
class BiometricHelper @AssistedInject constructor(
|
||||
@Assisted private val configuration: LockScreenConfiguration,
|
||||
@ApplicationContext private val context: Context,
|
||||
private val lockScreenKeyRepository: LockScreenKeyRepository,
|
||||
private val configurationProvider: LockScreenConfiguratorProvider,
|
||||
private val biometricManager: BiometricManager,
|
||||
private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider,
|
||||
) {
|
||||
private var prompt: BiometricPrompt? = null
|
||||
|
||||
private val configuration: LockScreenConfiguration get() = configurationProvider.currentConfiguration
|
||||
@AssistedFactory
|
||||
interface BiometricHelperFactory {
|
||||
fun create(configuration: LockScreenConfiguration): BiometricHelper
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a weak biometric method (i.e.: some face or iris unlock implementations) can be used.
|
||||
|
@ -174,16 +178,18 @@ class BiometricHelper @Inject constructor(
|
|||
when (val exception = result.exceptionOrNull()) {
|
||||
null -> result.getOrNull()?.let { emit(it) }
|
||||
else -> {
|
||||
// Exception found, stop collecting, throw it and remove the prompt reference
|
||||
// Exception found:
|
||||
// 1. Stop collecting.
|
||||
// 2. Remove the system key if we were creating it.
|
||||
// 3. Throw the exception and remove the prompt reference
|
||||
if (!checkSystemKeyExists) {
|
||||
lockScreenKeyRepository.deleteSystemKey()
|
||||
}
|
||||
prompt = null
|
||||
throw exception
|
||||
}
|
||||
}
|
||||
}
|
||||
// Generates the system key on successful authentication
|
||||
if (buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M) {
|
||||
lockScreenKeyRepository.ensureSystemKey()
|
||||
}
|
||||
// Channel is closed, remove prompt reference
|
||||
prompt = null
|
||||
}
|
||||
|
@ -213,11 +219,11 @@ class BiometricHelper @Inject constructor(
|
|||
.setAllowedAuthenticators(authenticators)
|
||||
.build()
|
||||
|
||||
return BiometricPrompt(activity, executor, callback).also {
|
||||
return BiometricPrompt(activity, executor, callback).also { prompt ->
|
||||
showFallbackFragmentIfNeeded(activity, channel.receiveAsFlow(), executor.asCoroutineDispatcher()) {
|
||||
// For some reason this seems to be needed unless we want to receive a fragment transaction exception
|
||||
delay(1L)
|
||||
it.authenticate(promptInfo, cryptoObject)
|
||||
prompt.authenticate(promptInfo, cryptoObject)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -253,11 +259,9 @@ class BiometricHelper @Inject constructor(
|
|||
): BiometricPrompt.AuthenticationCallback = object : BiometricPrompt.AuthenticationCallback() {
|
||||
private val scope = CoroutineScope(coroutineContext)
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||
scope.launch {
|
||||
// Error is a terminal event, should close both the Channel and the CoroutineScope to free resources.
|
||||
channel.close(BiometricAuthError(errorCode, errString.toString()))
|
||||
scope.cancel()
|
||||
}
|
||||
// Error is a terminal event, should close both the Channel and the CoroutineScope to free resources.
|
||||
channel.close(BiometricAuthError(errorCode, errString.toString()))
|
||||
scope.cancel()
|
||||
}
|
||||
|
||||
override fun onAuthenticationFailed() {
|
||||
|
@ -274,10 +278,8 @@ class BiometricHelper @Inject constructor(
|
|||
scope.cancel()
|
||||
}
|
||||
} else {
|
||||
scope.launch {
|
||||
channel.close(IllegalStateException("System key was not valid after authentication."))
|
||||
scope.cancel()
|
||||
}
|
||||
channel.close(IllegalStateException("System key was not valid after authentication."))
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,9 +16,13 @@
|
|||
|
||||
package im.vector.app.features.pin.lockscreen.configuration
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
* Configuration to be used by the lockscreen feature.
|
||||
*/
|
||||
@Parcelize
|
||||
data class LockScreenConfiguration(
|
||||
/** Which mode should the UI display, [LockScreenMode.VERIFY] or [LockScreenMode.CREATE]. */
|
||||
val mode: LockScreenMode,
|
||||
|
@ -56,4 +60,4 @@ data class LockScreenConfiguration(
|
|||
val biometricSubtitle: String? = null,
|
||||
/** Text for the cancel button of the Biometric prompt dialog. Optional. */
|
||||
val biometricCancelButtonTitle: String? = null,
|
||||
)
|
||||
) : Parcelable
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue