Merge branch 'develop' into feature/bma/fixFdroidNotification
This commit is contained in:
@@ -20,11 +20,11 @@ import com.squareup.anvil.annotations.ContributesTo
|
||||
import io.element.android.features.api.MigrationEntryPoint
|
||||
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
|
||||
import io.element.android.features.lockscreen.api.LockScreenService
|
||||
import io.element.android.features.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.features.rageshake.api.reporter.BugReporter
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.tracing.TracingService
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
|
||||
@ContributesTo(AppScope::class)
|
||||
interface AppBindings {
|
||||
|
||||
@@ -44,8 +44,8 @@ import io.element.android.features.call.impl.DefaultElementCallEntryPoint
|
||||
import io.element.android.features.call.impl.di.CallBindings
|
||||
import io.element.android.features.call.impl.services.CallForegroundService
|
||||
import io.element.android.features.call.impl.utils.CallIntentDataParser
|
||||
import io.element.android.features.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.libraries.architecture.bindings
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import javax.inject.Inject
|
||||
|
||||
class ElementCallActivity : AppCompatActivity(), CallScreenNavigator {
|
||||
|
||||
@@ -18,12 +18,12 @@ package io.element.android.features.call.impl.utils
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.appconfig.ElementCallConfig
|
||||
import io.element.android.features.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.widget.CallWidgetSettingsProvider
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ package io.element.android.features.call.utils
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.call.impl.utils.DefaultCallWidgetProvider
|
||||
import io.element.android.features.call.impl.utils.ElementCallBaseUrlProvider
|
||||
import io.element.android.features.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.widget.CallWidgetSettingsProvider
|
||||
@@ -30,6 +29,7 @@ import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.test.widget.FakeCallWidgetSettingsProvider
|
||||
import io.element.android.libraries.matrix.test.widget.FakeMatrixWidgetDriver
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
|
||||
@@ -23,11 +23,11 @@ import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.features.ftue.api.state.FtueService
|
||||
import io.element.android.features.ftue.api.state.FtueState
|
||||
import io.element.android.features.lockscreen.api.LockScreenService
|
||||
import io.element.android.features.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
||||
import io.element.android.libraries.permissions.api.PermissionStateProvider
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
@@ -31,6 +31,8 @@ import io.element.android.libraries.preferences.test.InMemorySessionPreferencesS
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
@@ -44,9 +46,9 @@ class DefaultFtueServiceTest {
|
||||
givenVerifiedStatus(SessionVerifiedStatus.Unknown)
|
||||
}
|
||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val state = createState(coroutineScope, sessionVerificationService)
|
||||
val service = createDefaultFtueService(coroutineScope, sessionVerificationService)
|
||||
|
||||
state.state.test {
|
||||
service.state.test {
|
||||
// Verification state is unknown, we don't display the flow yet
|
||||
assertThat(awaitItem()).isEqualTo(FtueState.Unknown)
|
||||
|
||||
@@ -67,7 +69,7 @@ class DefaultFtueServiceTest {
|
||||
val lockScreenService = FakeLockScreenService()
|
||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
|
||||
val state = createState(
|
||||
val service = createDefaultFtueService(
|
||||
coroutineScope = coroutineScope,
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
analyticsService = analyticsService,
|
||||
@@ -79,9 +81,9 @@ class DefaultFtueServiceTest {
|
||||
analyticsService.setDidAskUserConsent()
|
||||
permissionStateProvider.setPermissionGranted()
|
||||
lockScreenService.setIsPinSetup(true)
|
||||
state.updateState()
|
||||
service.updateState()
|
||||
|
||||
assertThat(state.state.value).isEqualTo(FtueState.Complete)
|
||||
assertThat(service.state.value).isEqualTo(FtueState.Complete)
|
||||
|
||||
// Cleanup
|
||||
coroutineScope.cancel()
|
||||
@@ -97,7 +99,7 @@ class DefaultFtueServiceTest {
|
||||
val lockScreenService = FakeLockScreenService()
|
||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
|
||||
val state = createState(
|
||||
val service = createDefaultFtueService(
|
||||
coroutineScope = coroutineScope,
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
analyticsService = analyticsService,
|
||||
@@ -107,23 +109,23 @@ class DefaultFtueServiceTest {
|
||||
val steps = mutableListOf<FtueStep?>()
|
||||
|
||||
// Session verification
|
||||
steps.add(state.getNextStep(steps.lastOrNull()))
|
||||
steps.add(service.getNextStep(steps.lastOrNull()))
|
||||
sessionVerificationService.givenVerifiedStatus(SessionVerifiedStatus.NotVerified)
|
||||
|
||||
// Notifications opt in
|
||||
steps.add(state.getNextStep(steps.lastOrNull()))
|
||||
steps.add(service.getNextStep(steps.lastOrNull()))
|
||||
permissionStateProvider.setPermissionGranted()
|
||||
|
||||
// Entering PIN code
|
||||
steps.add(state.getNextStep(steps.lastOrNull()))
|
||||
steps.add(service.getNextStep(steps.lastOrNull()))
|
||||
lockScreenService.setIsPinSetup(true)
|
||||
|
||||
// Analytics opt in
|
||||
steps.add(state.getNextStep(steps.lastOrNull()))
|
||||
steps.add(service.getNextStep(steps.lastOrNull()))
|
||||
analyticsService.setDidAskUserConsent()
|
||||
|
||||
// Final step (null)
|
||||
steps.add(state.getNextStep(steps.lastOrNull()))
|
||||
steps.add(service.getNextStep(steps.lastOrNull()))
|
||||
|
||||
assertThat(steps).containsExactly(
|
||||
FtueStep.SessionVerification,
|
||||
@@ -145,7 +147,7 @@ class DefaultFtueServiceTest {
|
||||
val analyticsService = FakeAnalyticsService()
|
||||
val permissionStateProvider = FakePermissionStateProvider(permissionGranted = false)
|
||||
val lockScreenService = FakeLockScreenService()
|
||||
val state = createState(
|
||||
val service = createDefaultFtueService(
|
||||
coroutineScope = coroutineScope,
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
analyticsService = analyticsService,
|
||||
@@ -158,10 +160,10 @@ class DefaultFtueServiceTest {
|
||||
permissionStateProvider.setPermissionGranted()
|
||||
lockScreenService.setIsPinSetup(true)
|
||||
|
||||
assertThat(state.getNextStep()).isEqualTo(FtueStep.AnalyticsOptIn)
|
||||
assertThat(service.getNextStep()).isEqualTo(FtueStep.AnalyticsOptIn)
|
||||
|
||||
analyticsService.setDidAskUserConsent()
|
||||
assertThat(state.getNextStep(null)).isNull()
|
||||
assertThat(service.getNextStep(null)).isNull()
|
||||
|
||||
// Cleanup
|
||||
coroutineScope.cancel()
|
||||
@@ -174,7 +176,7 @@ class DefaultFtueServiceTest {
|
||||
val analyticsService = FakeAnalyticsService()
|
||||
val lockScreenService = FakeLockScreenService()
|
||||
|
||||
val state = createState(
|
||||
val service = createDefaultFtueService(
|
||||
sdkIntVersion = Build.VERSION_CODES.M,
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
coroutineScope = coroutineScope,
|
||||
@@ -185,16 +187,61 @@ class DefaultFtueServiceTest {
|
||||
sessionVerificationService.givenVerifiedStatus(SessionVerifiedStatus.Verified)
|
||||
lockScreenService.setIsPinSetup(true)
|
||||
|
||||
assertThat(state.getNextStep()).isEqualTo(FtueStep.AnalyticsOptIn)
|
||||
assertThat(service.getNextStep()).isEqualTo(FtueStep.AnalyticsOptIn)
|
||||
|
||||
analyticsService.setDidAskUserConsent()
|
||||
assertThat(state.getNextStep(null)).isNull()
|
||||
assertThat(service.getNextStep(null)).isNull()
|
||||
|
||||
// Cleanup
|
||||
coroutineScope.cancel()
|
||||
}
|
||||
|
||||
private fun createState(
|
||||
@Test
|
||||
fun `reset do the expected actions S`() = runTest {
|
||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val resetAnalyticsLambda = lambdaRecorder<Unit> { }
|
||||
val analyticsService = FakeAnalyticsService(
|
||||
resetLambda = resetAnalyticsLambda
|
||||
)
|
||||
val resetPermissionLambda = lambdaRecorder<String, Unit> { }
|
||||
val permissionStateProvider = FakePermissionStateProvider(
|
||||
resetPermissionLambda = resetPermissionLambda
|
||||
)
|
||||
val service = createDefaultFtueService(
|
||||
coroutineScope = coroutineScope,
|
||||
sdkIntVersion = Build.VERSION_CODES.S,
|
||||
permissionStateProvider = permissionStateProvider,
|
||||
analyticsService = analyticsService,
|
||||
)
|
||||
service.reset()
|
||||
resetAnalyticsLambda.assertions().isCalledOnce()
|
||||
resetPermissionLambda.assertions().isNeverCalled()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reset do the expected actions TIRAMISU`() = runTest {
|
||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val resetLambda = lambdaRecorder<Unit> { }
|
||||
val analyticsService = FakeAnalyticsService(
|
||||
resetLambda = resetLambda
|
||||
)
|
||||
val resetPermissionLambda = lambdaRecorder<String, Unit> { }
|
||||
val permissionStateProvider = FakePermissionStateProvider(
|
||||
resetPermissionLambda = resetPermissionLambda
|
||||
)
|
||||
val service = createDefaultFtueService(
|
||||
coroutineScope = coroutineScope,
|
||||
sdkIntVersion = Build.VERSION_CODES.TIRAMISU,
|
||||
permissionStateProvider = permissionStateProvider,
|
||||
analyticsService = analyticsService,
|
||||
)
|
||||
service.reset()
|
||||
resetLambda.assertions().isCalledOnce()
|
||||
resetPermissionLambda.assertions().isCalledOnce()
|
||||
.with(value("android.permission.POST_NOTIFICATIONS"))
|
||||
}
|
||||
|
||||
private fun createDefaultFtueService(
|
||||
coroutineScope: CoroutineScope,
|
||||
sessionVerificationService: FakeSessionVerificationService = FakeSessionVerificationService(),
|
||||
analyticsService: AnalyticsService = FakeAnalyticsService(),
|
||||
|
||||
31
features/ftue/test/build.gradle.kts
Normal file
31
features/ftue/test/build.gradle.kts
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
alias(libs.plugins.anvil)
|
||||
alias(libs.plugins.ksp)
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.ftue.test"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.features.ftue.api)
|
||||
implementation(projects.tests.testutils)
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 io.element.android.features.ftue.test
|
||||
|
||||
import io.element.android.features.ftue.api.state.FtueService
|
||||
import io.element.android.features.ftue.api.state.FtueState
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
class FakeFtueService(
|
||||
private val resetLambda: () -> Unit = { lambdaError() },
|
||||
) : FtueService {
|
||||
override val state: MutableStateFlow<FtueState> = MutableStateFlow(FtueState.Unknown)
|
||||
|
||||
override suspend fun reset() {
|
||||
resetLambda()
|
||||
}
|
||||
|
||||
suspend fun emitState(newState: FtueState) {
|
||||
state.emit(newState)
|
||||
}
|
||||
}
|
||||
@@ -33,8 +33,8 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.canBeCopied
|
||||
import io.element.android.features.messages.impl.timeline.model.event.canReact
|
||||
import io.element.android.features.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -41,7 +41,6 @@ import io.element.android.features.messages.impl.attachments.Attachment
|
||||
import io.element.android.features.messages.impl.attachments.preview.error.sendAttachmentError
|
||||
import io.element.android.features.messages.impl.mentions.MentionSuggestionsProcessor
|
||||
import io.element.android.features.messages.impl.timeline.TimelineController
|
||||
import io.element.android.features.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||
@@ -61,6 +60,7 @@ import io.element.android.libraries.mediaupload.api.MediaSender
|
||||
import io.element.android.libraries.mediaviewer.api.local.LocalMediaFactory
|
||||
import io.element.android.libraries.permissions.api.PermissionsEvents
|
||||
import io.element.android.libraries.permissions.api.PermissionsPresenter
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion
|
||||
import io.element.android.libraries.textcomposer.mentions.rememberMentionSpanProvider
|
||||
import io.element.android.libraries.textcomposer.model.Message
|
||||
|
||||
@@ -36,7 +36,6 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.impl.voicemessages.timeline.RedactedVoiceMessageManager
|
||||
import io.element.android.features.poll.api.actions.EndPollAction
|
||||
import io.element.android.features.poll.api.actions.SendPollResponseAction
|
||||
import io.element.android.features.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.bool.orFalse
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
@@ -47,6 +46,7 @@ import io.element.android.libraries.matrix.api.room.roomMembers
|
||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin
|
||||
import io.element.android.libraries.matrix.ui.room.canSendMessageAsState
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.combine
|
||||
|
||||
@@ -24,13 +24,13 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import io.element.android.features.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||
import io.element.android.libraries.matrix.api.room.roomMembers
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.combine
|
||||
|
||||
@@ -33,7 +33,6 @@ import io.element.android.features.messages.impl.messagecomposer.MessageComposer
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerState
|
||||
import io.element.android.features.messages.impl.timeline.TimelineController
|
||||
import io.element.android.features.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.core.mimetype.MimeTypes
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
@@ -76,6 +75,7 @@ import io.element.android.libraries.mediaviewer.test.FakeLocalMediaFactory
|
||||
import io.element.android.libraries.permissions.api.PermissionsPresenter
|
||||
import io.element.android.libraries.permissions.test.FakePermissionsPresenter
|
||||
import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
|
||||
import io.element.android.libraries.textcomposer.mentions.ResolvedMentionSuggestion
|
||||
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
||||
|
||||
@@ -21,7 +21,6 @@ import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.Event
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
||||
@@ -31,6 +30,7 @@ import io.element.android.libraries.matrix.test.A_USER_ID_3
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID_4
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
package io.element.android.features.migration.impl.migrations
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesMultibinding
|
||||
import io.element.android.features.preferences.api.store.SessionPreferencesStoreFactory
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStoreFactory
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -58,6 +58,7 @@ dependencies {
|
||||
implementation(projects.libraries.permissions.api)
|
||||
implementation(projects.libraries.push.api)
|
||||
implementation(projects.libraries.pushproviders.api)
|
||||
implementation(projects.libraries.fullscreenintent.api)
|
||||
implementation(projects.features.rageshake.api)
|
||||
implementation(projects.features.lockscreen.api)
|
||||
implementation(projects.features.analytics.api)
|
||||
@@ -88,10 +89,13 @@ dependencies {
|
||||
testImplementation(projects.libraries.preferences.test)
|
||||
testImplementation(projects.libraries.push.test)
|
||||
testImplementation(projects.libraries.pushstore.test)
|
||||
testImplementation(projects.features.ftue.test)
|
||||
testImplementation(projects.features.rageshake.test)
|
||||
testImplementation(projects.features.rageshake.impl)
|
||||
testImplementation(projects.features.roomlist.test)
|
||||
testImplementation(projects.libraries.indicator.impl)
|
||||
testImplementation(projects.libraries.pushproviders.test)
|
||||
testImplementation(projects.libraries.fullscreenintent.test)
|
||||
testImplementation(projects.features.logout.impl)
|
||||
testImplementation(projects.services.analytics.test)
|
||||
testImplementation(projects.services.toolbox.test)
|
||||
|
||||
@@ -25,9 +25,9 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import io.element.android.compound.theme.Theme
|
||||
import io.element.android.compound.theme.mapToTheme
|
||||
import io.element.android.features.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.features.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
||||
import io.element.android.appconfig.ElementCallConfig
|
||||
import io.element.android.features.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.features.preferences.impl.tasks.ClearCacheUseCase
|
||||
import io.element.android.features.preferences.impl.tasks.ComputeCacheSizeUseCase
|
||||
import io.element.android.features.rageshake.api.preferences.RageshakePreferencesPresenter
|
||||
@@ -42,6 +41,7 @@ import io.element.android.libraries.featureflag.api.Feature
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.featureflag.ui.model.FeatureUiModel
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -21,6 +21,7 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -30,6 +31,7 @@ import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runUpdatingStateNoSuccess
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsPresenter
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
@@ -54,6 +56,7 @@ class NotificationSettingsPresenter @Inject constructor(
|
||||
private val matrixClient: MatrixClient,
|
||||
private val pushService: PushService,
|
||||
private val systemNotificationsEnabledProvider: SystemNotificationsEnabledProvider,
|
||||
private val fullScreenIntentPermissionsPresenter: FullScreenIntentPermissionsPresenter,
|
||||
) : Presenter<NotificationSettingsState> {
|
||||
@Composable
|
||||
override fun present(): NotificationSettingsState {
|
||||
@@ -72,6 +75,9 @@ class NotificationSettingsPresenter @Inject constructor(
|
||||
mutableStateOf(NotificationSettingsState.MatrixSettings.Uninitialized)
|
||||
}
|
||||
|
||||
// Used to force a recomposition
|
||||
var refreshFullScreenIntentSettings by remember { mutableIntStateOf(0) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
fetchSettings(matrixSettings)
|
||||
observeNotificationSettings(matrixSettings, changeNotificationSettingAction)
|
||||
@@ -149,6 +155,7 @@ class NotificationSettingsPresenter @Inject constructor(
|
||||
NotificationSettingsEvents.FixConfigurationMismatch -> localCoroutineScope.fixConfigurationMismatch(matrixSettings)
|
||||
NotificationSettingsEvents.RefreshSystemNotificationsEnabled -> {
|
||||
systemNotificationsEnabled.value = systemNotificationsEnabledProvider.notificationsEnabled()
|
||||
refreshFullScreenIntentSettings++
|
||||
}
|
||||
NotificationSettingsEvents.ClearNotificationChangeError -> changeNotificationSettingAction.value = AsyncAction.Uninitialized
|
||||
NotificationSettingsEvents.ChangePushProvider -> showChangePushProviderDialog = true
|
||||
@@ -167,6 +174,7 @@ class NotificationSettingsPresenter @Inject constructor(
|
||||
currentPushDistributor = currentDistributorName,
|
||||
availablePushDistributors = distributorNames,
|
||||
showChangePushProviderDialog = showChangePushProviderDialog,
|
||||
fullScreenIntentPermissionsState = key(refreshFullScreenIntentSettings) { fullScreenIntentPermissionsPresenter.present() },
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package io.element.android.features.preferences.impl.notifications
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@@ -30,6 +31,7 @@ data class NotificationSettingsState(
|
||||
val currentPushDistributor: AsyncData<String>,
|
||||
val availablePushDistributors: ImmutableList<String>,
|
||||
val showChangePushProviderDialog: Boolean,
|
||||
val fullScreenIntentPermissionsState: FullScreenIntentPermissionsState,
|
||||
val eventSink: (NotificationSettingsEvents) -> Unit,
|
||||
) {
|
||||
sealed interface MatrixSettings {
|
||||
|
||||
@@ -19,6 +19,7 @@ package io.element.android.features.preferences.impl.notifications
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
@@ -40,6 +41,7 @@ open class NotificationSettingsStateProvider : PreviewParameterProvider<Notifica
|
||||
aValidNotificationSettingsState(currentPushDistributor = AsyncData.Failure(Exception("Failed to change distributor"))),
|
||||
aInvalidNotificationSettingsState(),
|
||||
aInvalidNotificationSettingsState(fixFailed = true),
|
||||
aValidNotificationSettingsState(fullScreenIntentPermissionsState = aFullScreenIntentPermissionsState(permissionGranted = false)),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -53,6 +55,7 @@ fun aValidNotificationSettingsState(
|
||||
currentPushDistributor: AsyncData<String> = AsyncData.Success("Firebase"),
|
||||
availablePushDistributors: List<String> = listOf("Firebase", "ntfy"),
|
||||
showChangePushProviderDialog: Boolean = false,
|
||||
fullScreenIntentPermissionsState: FullScreenIntentPermissionsState = aFullScreenIntentPermissionsState(),
|
||||
eventSink: (NotificationSettingsEvents) -> Unit = {},
|
||||
) = NotificationSettingsState(
|
||||
matrixSettings = NotificationSettingsState.MatrixSettings.Valid(
|
||||
@@ -70,6 +73,7 @@ fun aValidNotificationSettingsState(
|
||||
currentPushDistributor = currentPushDistributor,
|
||||
availablePushDistributors = availablePushDistributors.toImmutableList(),
|
||||
showChangePushProviderDialog = showChangePushProviderDialog,
|
||||
fullScreenIntentPermissionsState = fullScreenIntentPermissionsState,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
@@ -88,5 +92,18 @@ fun aInvalidNotificationSettingsState(
|
||||
currentPushDistributor = AsyncData.Uninitialized,
|
||||
availablePushDistributors = persistentListOf(),
|
||||
showChangePushProviderDialog = false,
|
||||
fullScreenIntentPermissionsState = aFullScreenIntentPermissionsState(),
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
internal fun aFullScreenIntentPermissionsState(
|
||||
permissionGranted: Boolean = true,
|
||||
shouldDisplay: Boolean = false,
|
||||
openFullScreenIntentSettings: () -> Unit = {},
|
||||
dismissFullScreenIntentBanner: () -> Unit = {},
|
||||
) = FullScreenIntentPermissionsState(
|
||||
permissionGranted = permissionGranted,
|
||||
shouldDisplayBanner = shouldDisplay,
|
||||
openFullScreenIntentSettings = openFullScreenIntentSettings,
|
||||
dismissFullScreenIntentBanner = dismissFullScreenIntentBanner,
|
||||
)
|
||||
|
||||
@@ -136,6 +136,18 @@ private fun NotificationSettingsContentView(
|
||||
)
|
||||
|
||||
if (systemSettings.appNotificationsEnabled) {
|
||||
if (!state.fullScreenIntentPermissionsState.permissionGranted) {
|
||||
PreferenceCategory {
|
||||
PreferenceText(
|
||||
icon = CompoundIcons.VoiceCall(),
|
||||
title = stringResource(id = R.string.full_screen_intent_banner_title),
|
||||
subtitle = stringResource(R.string.full_screen_intent_banner_message,),
|
||||
onClick = {
|
||||
state.fullScreenIntentPermissionsState.openFullScreenIntentSettings()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
PreferenceCategory(title = stringResource(id = R.string.screen_notification_settings_notification_section_title)) {
|
||||
PreferenceText(
|
||||
title = stringResource(id = R.string.screen_notification_settings_group_chats),
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="full_screen_intent_banner_message">"To ensure you never miss an important call, please change your settings to allow full-screen notifications when your phone is locked."</string>
|
||||
<string name="full_screen_intent_banner_title">"Enhance your call experience"</string>
|
||||
<string name="screen_advanced_settings_choose_distributor_dialog_title_android">"Choose how to receive notifications"</string>
|
||||
<string name="screen_advanced_settings_developer_mode">"Developer mode"</string>
|
||||
<string name="screen_advanced_settings_developer_mode_description">"Enable to have access to features and functionality for developers."</string>
|
||||
|
||||
@@ -21,6 +21,7 @@ import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.fullscreenintent.test.FakeFullScreenIntentPermissionsPresenter
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import io.element.android.libraries.matrix.test.A_THROWABLE
|
||||
@@ -269,6 +270,32 @@ class NotificationSettingsPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - RefreshSystemNotificationsEnabled also refreshes fullScreenIntentState`() = runTest {
|
||||
val fullScreenIntentPermissionsPresenter = FakeFullScreenIntentPermissionsPresenter().apply {
|
||||
state = state.copy(permissionGranted = false)
|
||||
}
|
||||
val presenter = createNotificationSettingsPresenter(
|
||||
pushService = createFakePushService(),
|
||||
fullScreenIntentPermissionsPresenter = fullScreenIntentPermissionsPresenter,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitLastSequentialItem()
|
||||
assertThat(initialState.fullScreenIntentPermissionsState.permissionGranted).isFalse()
|
||||
|
||||
// Change the notification settings
|
||||
fullScreenIntentPermissionsPresenter.state = fullScreenIntentPermissionsPresenter.state.copy(permissionGranted = true)
|
||||
// Check it's not changed unless we refresh
|
||||
expectNoEvents()
|
||||
|
||||
// Refresh
|
||||
initialState.eventSink.invoke(NotificationSettingsEvents.RefreshSystemNotificationsEnabled)
|
||||
assertThat(awaitItem().fullScreenIntentPermissionsState.permissionGranted).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - change push provider error`() = runTest {
|
||||
val presenter = createNotificationSettingsPresenter(
|
||||
@@ -318,6 +345,7 @@ class NotificationSettingsPresenterTest {
|
||||
private fun createNotificationSettingsPresenter(
|
||||
notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(),
|
||||
pushService: PushService = FakePushService(),
|
||||
fullScreenIntentPermissionsPresenter: FakeFullScreenIntentPermissionsPresenter = FakeFullScreenIntentPermissionsPresenter()
|
||||
): NotificationSettingsPresenter {
|
||||
val matrixClient = FakeMatrixClient(notificationSettingsService = notificationSettingsService)
|
||||
return NotificationSettingsPresenter(
|
||||
@@ -326,6 +354,7 @@ class NotificationSettingsPresenterTest {
|
||||
matrixClient = matrixClient,
|
||||
pushService = pushService,
|
||||
systemNotificationsEnabledProvider = FakeSystemNotificationsEnabledProvider(),
|
||||
fullScreenIntentPermissionsPresenter = fullScreenIntentPermissionsPresenter,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 io.element.android.features.preferences.impl.tasks
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.ftue.test.FakeFtueService
|
||||
import io.element.android.features.preferences.impl.DefaultCacheService
|
||||
import io.element.android.features.roomlist.impl.migration.InMemoryMigrationScreenStore
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import okhttp3.OkHttpClient
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class DefaultClearCacheUseCaseTest {
|
||||
@Test
|
||||
fun `execute clear cache should do all the expected tasks`() = runTest {
|
||||
val clearCacheLambda = lambdaRecorder<Unit> { }
|
||||
val matrixClient = FakeMatrixClient(
|
||||
clearCacheLambda = clearCacheLambda,
|
||||
)
|
||||
val defaultCacheService = DefaultCacheService()
|
||||
val resetFtueLambda = lambdaRecorder<Unit> { }
|
||||
val ftueService = FakeFtueService(
|
||||
resetLambda = resetFtueLambda,
|
||||
)
|
||||
val resetMigrationLambda = lambdaRecorder<Unit> { }
|
||||
val migrationScreenStore = InMemoryMigrationScreenStore(
|
||||
resetLambda = resetMigrationLambda,
|
||||
)
|
||||
val sut = DefaultClearCacheUseCase(
|
||||
context = InstrumentationRegistry.getInstrumentation().context,
|
||||
matrixClient = matrixClient,
|
||||
coroutineDispatchers = testCoroutineDispatchers(),
|
||||
defaultCacheService = defaultCacheService,
|
||||
okHttpClient = { OkHttpClient.Builder().build() },
|
||||
ftueService = ftueService,
|
||||
migrationScreenStore = migrationScreenStore
|
||||
)
|
||||
defaultCacheService.clearedCacheEventFlow.test {
|
||||
sut.invoke()
|
||||
clearCacheLambda.assertions().isCalledOnce()
|
||||
resetFtueLambda.assertions().isCalledOnce()
|
||||
resetMigrationLambda.assertions().isCalledOnce()
|
||||
assertThat(awaitItem()).isEqualTo(matrixClient.sessionId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,7 +130,7 @@ class RoomNotificationSettingsPresenter @AssistedInject constructor(
|
||||
setNotificationSettingAction = setNotificationSettingAction.value,
|
||||
restoreDefaultAction = restoreDefaultAction.value,
|
||||
displayMentionsOnlyDisclaimer = shouldDisplayMentionsOnlyDisclaimer,
|
||||
eventSink = ::handleEvents,
|
||||
eventSink = { handleEvents(it) },
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -118,6 +118,7 @@ class RoomNotificationSettingsPresenterTest {
|
||||
skipItems(3)
|
||||
val defaultState = awaitItem()
|
||||
assertThat(defaultState.roomNotificationSettings.dataOrNull()?.isDefault).isFalse()
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,11 +52,15 @@ dependencies {
|
||||
implementation(projects.libraries.eventformatter.api)
|
||||
implementation(projects.libraries.indicator.api)
|
||||
implementation(projects.libraries.deeplink)
|
||||
implementation(projects.libraries.fullscreenintent.api)
|
||||
implementation(projects.libraries.permissions.api)
|
||||
implementation(projects.libraries.permissions.noop)
|
||||
implementation(projects.libraries.preferences.api)
|
||||
implementation(projects.features.invite.api)
|
||||
implementation(projects.features.networkmonitor.api)
|
||||
implementation(projects.features.leaveroom.api)
|
||||
implementation(projects.services.analytics.api)
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
api(projects.features.roomlist.api)
|
||||
ksp(libs.showkase.processor)
|
||||
|
||||
@@ -73,10 +77,14 @@ dependencies {
|
||||
testImplementation(projects.libraries.dateformatter.test)
|
||||
testImplementation(projects.libraries.eventformatter.test)
|
||||
testImplementation(projects.libraries.indicator.impl)
|
||||
testImplementation(projects.libraries.fullscreenintent.test)
|
||||
testImplementation(projects.libraries.permissions.noop)
|
||||
testImplementation(projects.libraries.permissions.test)
|
||||
testImplementation(projects.libraries.preferences.test)
|
||||
testImplementation(projects.services.analytics.test)
|
||||
testImplementation(projects.services.toolbox.test)
|
||||
testImplementation(projects.features.networkmonitor.test)
|
||||
testImplementation(projects.features.roomlist.test)
|
||||
testImplementation(projects.tests.testutils)
|
||||
testImplementation(projects.features.leaveroom.test)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package io.element.android.features.roomlist.impl
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@@ -35,8 +36,10 @@ open class RoomListContentStateProvider : PreviewParameterProvider<RoomListConte
|
||||
internal fun aRoomsContentState(
|
||||
securityBannerState: SecurityBannerState = SecurityBannerState.None,
|
||||
summaries: ImmutableList<RoomListRoomSummary> = aRoomListRoomSummaryList(),
|
||||
fullScreenIntentPermissionsState: FullScreenIntentPermissionsState = aFullScreenIntentPermissionsState(),
|
||||
) = RoomListContentState.Rooms(
|
||||
securityBannerState = securityBannerState,
|
||||
fullScreenIntentPermissionsState = fullScreenIntentPermissionsState,
|
||||
summaries = summaries,
|
||||
)
|
||||
|
||||
@@ -45,3 +48,15 @@ internal fun aMigrationContentState() = RoomListContentState.Migration
|
||||
internal fun aSkeletonContentState() = RoomListContentState.Skeleton(16)
|
||||
|
||||
internal fun anEmptyContentState() = RoomListContentState.Empty
|
||||
|
||||
internal fun aFullScreenIntentPermissionsState(
|
||||
permissionGranted: Boolean = true,
|
||||
shouldDisplay: Boolean = false,
|
||||
openFullScreenIntentSettings: () -> Unit = {},
|
||||
dismissFullScreenIntentBanner: () -> Unit = {},
|
||||
) = FullScreenIntentPermissionsState(
|
||||
permissionGranted = permissionGranted,
|
||||
shouldDisplayBanner = shouldDisplay,
|
||||
openFullScreenIntentSettings = openFullScreenIntentSettings,
|
||||
dismissFullScreenIntentBanner = dismissFullScreenIntentBanner,
|
||||
)
|
||||
|
||||
@@ -40,7 +40,6 @@ import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
|
||||
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
||||
import io.element.android.features.networkmonitor.api.NetworkStatus
|
||||
import io.element.android.features.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.features.roomlist.impl.datasource.RoomListDataSource
|
||||
import io.element.android.features.roomlist.impl.filters.RoomListFiltersState
|
||||
import io.element.android.features.roomlist.impl.migration.MigrationScreenState
|
||||
@@ -53,6 +52,7 @@ import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatch
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsPresenter
|
||||
import io.element.android.libraries.indicator.api.IndicatorService
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
@@ -62,6 +62,7 @@ import io.element.android.libraries.matrix.api.roomlist.RoomList
|
||||
import io.element.android.libraries.matrix.api.sync.SyncService
|
||||
import io.element.android.libraries.matrix.api.sync.SyncState
|
||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
@@ -93,6 +94,7 @@ class RoomListPresenter @Inject constructor(
|
||||
private val sessionPreferencesStore: SessionPreferencesStore,
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
|
||||
private val fullScreenIntentPermissionsPresenter: FullScreenIntentPermissionsPresenter,
|
||||
) : Presenter<RoomListState> {
|
||||
private val encryptionService: EncryptionService = client.encryptionService()
|
||||
private val syncService: SyncService = client.syncService()
|
||||
@@ -214,6 +216,7 @@ class RoomListPresenter @Inject constructor(
|
||||
val securityBannerState by securityBannerState(securityBannerDismissed)
|
||||
RoomListContentState.Rooms(
|
||||
securityBannerState = securityBannerState,
|
||||
fullScreenIntentPermissionsState = fullScreenIntentPermissionsPresenter.present(),
|
||||
summaries = roomSummaries.dataOrNull().orEmpty().toPersistentList()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import io.element.android.features.roomlist.impl.filters.RoomListFiltersState
|
||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||
import io.element.android.features.roomlist.impl.search.RoomListSearchState
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
@@ -75,6 +76,7 @@ sealed interface RoomListContentState {
|
||||
data object Empty : RoomListContentState
|
||||
data class Rooms(
|
||||
val securityBannerState: SecurityBannerState,
|
||||
val fullScreenIntentPermissionsState: FullScreenIntentPermissionsState,
|
||||
val summaries: ImmutableList<RoomListRoomSummary>,
|
||||
) : RoomListContentState
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 io.element.android.features.roomlist.impl.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import io.element.android.features.roomlist.impl.R
|
||||
import io.element.android.features.roomlist.impl.aFullScreenIntentPermissionsState
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.DialogLikeBannerMolecule
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
|
||||
|
||||
@Composable
|
||||
fun FullScreenIntentPermissionBanner(state: FullScreenIntentPermissionsState) {
|
||||
DialogLikeBannerMolecule(
|
||||
title = stringResource(R.string.full_screen_intent_banner_title),
|
||||
content = stringResource(R.string.full_screen_intent_banner_message),
|
||||
onDismissClick = state.dismissFullScreenIntentBanner,
|
||||
onSubmitClick = state.openFullScreenIntentSettings,
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun FullScreenIntentPermissionBannerPreview() {
|
||||
ElementPreview {
|
||||
FullScreenIntentPermissionBanner(aFullScreenIntentPermissionsState())
|
||||
}
|
||||
}
|
||||
@@ -193,16 +193,22 @@ private fun RoomsViewList(
|
||||
// FAB height is 56dp, bottom padding is 16dp, we add 8dp as extra margin -> 56+16+8 = 80
|
||||
contentPadding = PaddingValues(bottom = 80.dp)
|
||||
) {
|
||||
when (state.securityBannerState) {
|
||||
SecurityBannerState.RecoveryKeyConfirmation -> {
|
||||
item {
|
||||
ConfirmRecoveryKeyBanner(
|
||||
onContinueClick = onConfirmRecoveryKeyClick,
|
||||
onDismissClick = { eventSink(RoomListEvents.DismissRecoveryKeyPrompt) }
|
||||
)
|
||||
if (state.securityBannerState != SecurityBannerState.None) {
|
||||
when (state.securityBannerState) {
|
||||
SecurityBannerState.RecoveryKeyConfirmation -> {
|
||||
item {
|
||||
ConfirmRecoveryKeyBanner(
|
||||
onContinueClick = onConfirmRecoveryKeyClick,
|
||||
onDismissClick = { eventSink(RoomListEvents.DismissRecoveryKeyPrompt) }
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
} else if (state.fullScreenIntentPermissionsState.shouldDisplayBanner) {
|
||||
item {
|
||||
FullScreenIntentPermissionBanner(state = state.fullScreenIntentPermissionsState)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
// Note: do not use a key for the LazyColumn, or the scroll will not behave as expected if a room
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="confirm_recovery_key_banner_message">"Your chat backup is currently out of sync. You need to enter your recovery key to maintain access to your chat backup."</string>
|
||||
<string name="confirm_recovery_key_banner_title">"Enter your recovery key"</string>
|
||||
<string name="full_screen_intent_banner_message">"To ensure you never miss an important call, please change your settings to allow full-screen notifications when your phone is locked."</string>
|
||||
<string name="full_screen_intent_banner_title">"Enhance your call experience"</string>
|
||||
<string name="screen_invites_decline_chat_message">"Are you sure you want to decline the invitation to join %1$s?"</string>
|
||||
<string name="screen_invites_decline_chat_title">"Decline invite"</string>
|
||||
<string name="screen_invites_decline_direct_chat_message">"Are you sure you want to decline this private chat with %1$s?"</string>
|
||||
|
||||
@@ -29,7 +29,6 @@ import io.element.android.features.leaveroom.api.LeaveRoomPresenter
|
||||
import io.element.android.features.leaveroom.fake.FakeLeaveRoomPresenter
|
||||
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
||||
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
|
||||
import io.element.android.features.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.features.roomlist.impl.datasource.RoomListDataSource
|
||||
import io.element.android.features.roomlist.impl.datasource.RoomListRoomSummaryFactory
|
||||
import io.element.android.features.roomlist.impl.filters.RoomListFiltersState
|
||||
@@ -48,6 +47,7 @@ import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
|
||||
import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.fullscreenintent.test.FakeFullScreenIntentPermissionsPresenter
|
||||
import io.element.android.libraries.indicator.impl.DefaultIndicatorService
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.encryption.BackupState
|
||||
@@ -72,6 +72,7 @@ import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled
|
||||
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||
import io.element.android.libraries.matrix.test.sync.FakeSyncService
|
||||
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
@@ -668,5 +669,6 @@ class RoomListPresenterTest {
|
||||
filtersPresenter = filtersPresenter,
|
||||
analyticsService = analyticsService,
|
||||
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter,
|
||||
fullScreenIntentPermissionsPresenter = FakeFullScreenIntentPermissionsPresenter(),
|
||||
)
|
||||
}
|
||||
|
||||
28
features/roomlist/test/build.gradle.kts
Normal file
28
features/roomlist/test/build.gradle.kts
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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.
|
||||
*/
|
||||
plugins {
|
||||
id("io.element.android-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.roomlist.test"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.features.roomlist.api)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.tests.testutils)
|
||||
}
|
||||
@@ -19,7 +19,9 @@ package io.element.android.features.roomlist.impl.migration
|
||||
import io.element.android.features.roomlist.api.migration.MigrationScreenStore
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
|
||||
class InMemoryMigrationScreenStore : MigrationScreenStore {
|
||||
class InMemoryMigrationScreenStore(
|
||||
private val resetLambda: () -> Unit = { }
|
||||
) : MigrationScreenStore {
|
||||
private val store = mutableMapOf<SessionId, Boolean>()
|
||||
|
||||
override fun isMigrationScreenNeeded(sessionId: SessionId): Boolean {
|
||||
@@ -33,5 +35,6 @@ class InMemoryMigrationScreenStore : MigrationScreenStore {
|
||||
|
||||
override fun reset() {
|
||||
store.clear()
|
||||
resetLambda()
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,6 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import com.freeletics.flowredux.compose.rememberStateAndDispatch
|
||||
import io.element.android.features.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
@@ -34,6 +33,7 @@ import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||
import io.element.android.libraries.matrix.api.verification.VerificationFlowState
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
|
||||
29
libraries/fullscreenintent/api/build.gradle.kts
Normal file
29
libraries/fullscreenintent/api/build.gradle.kts
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.libraries.fullscreenintent.api"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.permissions.api)
|
||||
implementation(projects.libraries.preferences.api)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 io.element.android.libraries.fullscreenintent.api
|
||||
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
|
||||
interface FullScreenIntentPermissionsPresenter : Presenter<FullScreenIntentPermissionsState>
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 io.element.android.libraries.fullscreenintent.api
|
||||
|
||||
data class FullScreenIntentPermissionsState(
|
||||
val permissionGranted: Boolean,
|
||||
val shouldDisplayBanner: Boolean,
|
||||
val dismissFullScreenIntentBanner: () -> Unit,
|
||||
val openFullScreenIntentSettings: () -> Unit,
|
||||
)
|
||||
58
libraries/fullscreenintent/impl/build.gradle.kts
Normal file
58
libraries/fullscreenintent/impl/build.gradle.kts
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
alias(libs.plugins.anvil)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.libraries.fullscreenintent.impl"
|
||||
}
|
||||
|
||||
anvil {
|
||||
generateDaggerFactories.set(true)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(projects.libraries.fullscreenintent.api)
|
||||
implementation(projects.anvilannotations)
|
||||
anvil(projects.anvilcodegen)
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.permissions.api)
|
||||
implementation(projects.libraries.permissions.noop)
|
||||
implementation(projects.libraries.preferences.api)
|
||||
implementation(projects.services.toolbox.api)
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
|
||||
testImplementation(projects.libraries.fullscreenintent.test)
|
||||
testImplementation(libs.test.junit)
|
||||
testImplementation(libs.coroutines.test)
|
||||
testImplementation(libs.molecule.runtime)
|
||||
testImplementation(libs.test.truth)
|
||||
testImplementation(libs.test.turbine)
|
||||
testImplementation(projects.tests.testutils)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.libraries.permissions.test)
|
||||
testImplementation(projects.libraries.preferences.test)
|
||||
testImplementation(projects.libraries.testtags)
|
||||
testImplementation(libs.test.robolectric)
|
||||
testImplementation(libs.test.mockk)
|
||||
testImplementation(libs.androidx.compose.ui.test.junit)
|
||||
testImplementation(projects.services.toolbox.test)
|
||||
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 io.element.android.libraries.fullscreenintent.impl
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.SingleIn
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsPresenter
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
|
||||
import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory
|
||||
import io.element.android.services.toolbox.api.intent.ExternalIntentLauncher
|
||||
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@SingleIn(AppScope::class)
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultFullScreenIntentPermissionsPresenter @Inject constructor(
|
||||
private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider,
|
||||
private val externalIntentLauncher: ExternalIntentLauncher,
|
||||
private val buildMeta: BuildMeta,
|
||||
private val notificationManagerCompat: NotificationManagerCompat,
|
||||
preferencesDataStoreFactory: PreferenceDataStoreFactory,
|
||||
) : FullScreenIntentPermissionsPresenter {
|
||||
companion object {
|
||||
private const val PREF_KEY_FULL_SCREEN_INTENT_BANNER_DISMISSED = "PREF_KEY_FULL_SCREEN_INTENT_BANNER_DISMISSED"
|
||||
}
|
||||
|
||||
private val dataStore = preferencesDataStoreFactory.create("full_screen_intent_permissions")
|
||||
|
||||
private val isFullScreenIntentBannerDismissed = dataStore.data.map { prefs ->
|
||||
prefs[booleanPreferencesKey(PREF_KEY_FULL_SCREEN_INTENT_BANNER_DISMISSED)] ?: false
|
||||
}
|
||||
|
||||
private suspend fun dismissFullScreenIntentBanner() {
|
||||
dataStore.edit { prefs ->
|
||||
prefs[booleanPreferencesKey(PREF_KEY_FULL_SCREEN_INTENT_BANNER_DISMISSED)] = true
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun present(): FullScreenIntentPermissionsState {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val isGranted = notificationManagerCompat.canUseFullScreenIntent()
|
||||
val isBannerDismissed by isFullScreenIntentBannerDismissed.collectAsState(initial = true)
|
||||
return FullScreenIntentPermissionsState(
|
||||
permissionGranted = isGranted,
|
||||
shouldDisplayBanner = !isBannerDismissed && !isGranted,
|
||||
dismissFullScreenIntentBanner = {
|
||||
coroutineScope.launch {
|
||||
dismissFullScreenIntentBanner()
|
||||
}
|
||||
},
|
||||
openFullScreenIntentSettings = ::openFullScreenIntentSettings,
|
||||
)
|
||||
}
|
||||
|
||||
private fun openFullScreenIntentSettings() {
|
||||
if (buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)) {
|
||||
try {
|
||||
val intent = Intent(
|
||||
Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT,
|
||||
Uri.parse("package:${buildMeta.applicationId}")
|
||||
)
|
||||
externalIntentLauncher.launch(intent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
|
||||
.putExtra(Settings.EXTRA_APP_PACKAGE, buildMeta.applicationId)
|
||||
externalIntentLauncher.launch(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 io.element.android.libraries.fullscreenintent.test
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.fullscreenintent.impl.DefaultFullScreenIntentPermissionsPresenter
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.preferences.test.FakePreferenceDataStoreFactory
|
||||
import io.element.android.services.toolbox.api.intent.ExternalIntentLauncher
|
||||
import io.element.android.services.toolbox.test.intent.FakeExternalIntentLauncher
|
||||
import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class DefaultFullScreenIntentPermissionsPresenterTest {
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
fun `shouldDisplay - is true when permission is not granted and banner is not dismissed`() = runTest {
|
||||
val presenter = createPresenter(
|
||||
notificationManagerCompat = mockk {
|
||||
every { canUseFullScreenIntent() } returns false
|
||||
}
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialItem = awaitItem()
|
||||
assertThat(initialItem.shouldDisplayBanner).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `shouldDisplay - is false if permission is granted`() = runTest {
|
||||
val presenter = createPresenter(
|
||||
notificationManagerCompat = mockk {
|
||||
every { canUseFullScreenIntent() } returns true
|
||||
}
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialItem = awaitItem()
|
||||
assertThat(initialItem.shouldDisplayBanner).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `dismissFullScreenIntentBanner - makes shouldDisplay false`() = runTest {
|
||||
val presenter = createPresenter()
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val loadedItem = awaitItem()
|
||||
loadedItem.dismissFullScreenIntentBanner()
|
||||
|
||||
runCurrent()
|
||||
|
||||
assertThat(awaitItem().shouldDisplayBanner).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `openFullScreenIntentSettings - opens external screen using intent`() = runTest {
|
||||
val launchLambda = lambdaRecorder<Intent, Unit> { _ -> }
|
||||
val externalIntentLauncher = FakeExternalIntentLauncher(launchLambda)
|
||||
val presenter = createPresenter(externalIntentLauncher = externalIntentLauncher)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val loadedItem = awaitItem()
|
||||
loadedItem.openFullScreenIntentSettings()
|
||||
|
||||
launchLambda.assertions().isCalledOnce()
|
||||
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `openFullScreenIntentSettings - does nothing in old APIs`() = runTest {
|
||||
val launchLambda = lambdaRecorder<Intent, Unit> { _ -> }
|
||||
val externalIntentLauncher = FakeExternalIntentLauncher(launchLambda)
|
||||
val presenter = createPresenter(
|
||||
buildVersionSdkIntProvider = FakeBuildVersionSdkIntProvider(Build.VERSION_CODES.Q),
|
||||
externalIntentLauncher = externalIntentLauncher,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val loadedItem = awaitItem()
|
||||
loadedItem.openFullScreenIntentSettings()
|
||||
|
||||
launchLambda.assertions().isNeverCalled()
|
||||
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createPresenter(
|
||||
buildVersionSdkIntProvider: FakeBuildVersionSdkIntProvider = FakeBuildVersionSdkIntProvider(Build.VERSION_CODES.UPSIDE_DOWN_CAKE),
|
||||
dataStoreFactory: FakePreferenceDataStoreFactory = FakePreferenceDataStoreFactory(),
|
||||
externalIntentLauncher: ExternalIntentLauncher = FakeExternalIntentLauncher(),
|
||||
buildMeta: BuildMeta = aBuildMeta(),
|
||||
notificationManagerCompat: NotificationManagerCompat = mockk(relaxed = true)
|
||||
) = DefaultFullScreenIntentPermissionsPresenter(
|
||||
buildVersionSdkIntProvider = buildVersionSdkIntProvider,
|
||||
externalIntentLauncher = externalIntentLauncher,
|
||||
buildMeta = buildMeta,
|
||||
preferencesDataStoreFactory = dataStoreFactory,
|
||||
notificationManagerCompat = notificationManagerCompat,
|
||||
)
|
||||
}
|
||||
28
libraries/fullscreenintent/test/build.gradle.kts
Normal file
28
libraries/fullscreenintent/test/build.gradle.kts
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.libraries.fullscreenintent.test"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(projects.libraries.fullscreenintent.api)
|
||||
implementation(projects.libraries.architecture)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 io.element.android.libraries.fullscreenintent.test
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsPresenter
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
|
||||
|
||||
class FakeFullScreenIntentPermissionsPresenter : FullScreenIntentPermissionsPresenter {
|
||||
var state = FullScreenIntentPermissionsState(
|
||||
permissionGranted = true,
|
||||
shouldDisplayBanner = false,
|
||||
dismissFullScreenIntentBanner = {},
|
||||
openFullScreenIntentSettings = {},
|
||||
)
|
||||
@Composable
|
||||
override fun present(): FullScreenIntentPermissionsState {
|
||||
return state
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,7 @@ import io.element.android.libraries.matrix.test.roomdirectory.FakeRoomDirectoryS
|
||||
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||
import io.element.android.libraries.matrix.test.sync.FakeSyncService
|
||||
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.simulateLongTask
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
@@ -80,6 +81,7 @@ class FakeMatrixClient(
|
||||
private val accountManagementUrlString: Result<String?> = Result.success(null),
|
||||
private val resolveRoomAliasResult: (RoomAlias) -> Result<ResolvedRoomAlias> = { Result.success(ResolvedRoomAlias(A_ROOM_ID, emptyList())) },
|
||||
private val getRoomPreviewFromRoomIdResult: (RoomId, List<String>) -> Result<RoomPreview> = { _, _ -> Result.failure(AN_EXCEPTION) },
|
||||
private val clearCacheLambda: () -> Unit = { lambdaError() },
|
||||
) : MatrixClient {
|
||||
var setDisplayNameCalled: Boolean = false
|
||||
private set
|
||||
@@ -161,6 +163,7 @@ class FakeMatrixClient(
|
||||
}
|
||||
|
||||
override suspend fun clearCache() {
|
||||
clearCacheLambda()
|
||||
}
|
||||
|
||||
override suspend fun logout(ignoreSdkError: Boolean): String? = simulateLongTask {
|
||||
|
||||
@@ -24,6 +24,7 @@ class FakePermissionStateProvider(
|
||||
private var permissionGranted: Boolean = true,
|
||||
permissionDenied: Boolean = false,
|
||||
permissionAsked: Boolean = false,
|
||||
private val resetPermissionLambda: (String) -> Unit = {},
|
||||
) : PermissionStateProvider {
|
||||
private val permissionDeniedFlow = MutableStateFlow(permissionDenied)
|
||||
private val permissionAskedFlow = MutableStateFlow(permissionAsked)
|
||||
@@ -49,5 +50,6 @@ class FakePermissionStateProvider(
|
||||
override suspend fun resetPermission(permission: String) {
|
||||
setPermissionAsked(permission, false)
|
||||
setPermissionDenied(permission, false)
|
||||
resetPermissionLambda(permission)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,14 @@ import androidx.compose.runtime.Composable
|
||||
import io.element.android.libraries.permissions.api.PermissionsPresenter
|
||||
import io.element.android.libraries.permissions.api.PermissionsState
|
||||
|
||||
class NoopPermissionsPresenter : PermissionsPresenter {
|
||||
class NoopPermissionsPresenter(
|
||||
private val isGranted: Boolean = false,
|
||||
) : PermissionsPresenter {
|
||||
@Composable
|
||||
override fun present(): PermissionsState {
|
||||
return PermissionsState(
|
||||
permission = "",
|
||||
permissionGranted = false,
|
||||
permissionGranted = isGranted,
|
||||
shouldShowRationale = false,
|
||||
showDialog = false,
|
||||
permissionAlreadyAsked = false,
|
||||
|
||||
@@ -41,7 +41,7 @@ class FakePermissionsPresenter(
|
||||
}
|
||||
|
||||
fun setPermissionDenied() {
|
||||
state.value = state.value.copy(permissionAlreadyDenied = true)
|
||||
state.value = state.value.copy(permissionGranted = false, permissionAlreadyDenied = true)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -25,4 +25,5 @@ android {
|
||||
dependencies {
|
||||
implementation(libs.coroutines.core)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.preferences.api.store
|
||||
package io.element.android.libraries.preferences.api.store
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 io.element.android.libraries.preferences.api.store
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
|
||||
/**
|
||||
* Factory used to create a [DataStore] for preferences.
|
||||
*
|
||||
* It's a wrapper around AndroidX's `PreferenceDataStoreFactory` to make testing easier.
|
||||
*/
|
||||
interface PreferenceDataStoreFactory {
|
||||
fun create(name: String): DataStore<Preferences>
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.preferences.api.store
|
||||
package io.element.android.libraries.preferences.api.store
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.preferences.api.store
|
||||
package io.element.android.libraries.preferences.api.store
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -24,11 +24,11 @@ import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.features.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.core.meta.BuildType
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 io.element.android.libraries.preferences.impl.store
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultPreferencesDataStoreFactory @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
) : PreferenceDataStoreFactory {
|
||||
private class DataStoreHolder(name: String) {
|
||||
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = name)
|
||||
}
|
||||
override fun create(name: String): DataStore<Preferences> {
|
||||
return with(DataStoreHolder(name)) {
|
||||
context.dataStore
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,11 +22,11 @@ import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.preferencesDataStoreFile
|
||||
import io.element.android.features.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.androidutils.file.safeDelete
|
||||
import io.element.android.libraries.androidutils.hash.hash
|
||||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
@@ -18,12 +18,12 @@ package io.element.android.libraries.preferences.impl.store
|
||||
|
||||
import android.content.Context
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.features.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.features.preferences.api.store.SessionPreferencesStoreFactory
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
import io.element.android.libraries.di.SingleIn
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStoreFactory
|
||||
import io.element.android.libraries.sessionstorage.api.observer.SessionListener
|
||||
import io.element.android.libraries.sessionstorage.api.observer.SessionObserver
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
@@ -19,10 +19,10 @@ package io.element.android.libraries.preferences.impl.store
|
||||
import com.squareup.anvil.annotations.ContributesTo
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import io.element.android.features.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||
import io.element.android.libraries.matrix.api.user.CurrentSessionIdHolder
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
@Module
|
||||
|
||||
@@ -23,8 +23,9 @@ android {
|
||||
|
||||
dependencies {
|
||||
api(projects.libraries.preferences.api)
|
||||
implementation(libs.coroutines.core)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.tests.testutils)
|
||||
implementation(libs.coroutines.core)
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 io.element.android.libraries.preferences.test
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory
|
||||
import java.io.File
|
||||
import androidx.datastore.preferences.core.PreferenceDataStoreFactory as AndroidPreferenceDataStoreFactory
|
||||
|
||||
class FakePreferenceDataStoreFactory : PreferenceDataStoreFactory {
|
||||
override fun create(name: String): DataStore<Preferences> {
|
||||
return AndroidPreferenceDataStoreFactory.create { File.createTempFile("test", ".preferences_pb") }
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,9 @@
|
||||
|
||||
package io.element.android.libraries.preferences.test
|
||||
|
||||
import io.element.android.features.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.features.preferences.api.store.SessionPreferencesStoreFactory
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStoreFactory
|
||||
import io.element.android.tests.testutils.lambda.LambdaOneParamRecorder
|
||||
import io.element.android.tests.testutils.lambda.LambdaTwoParamsRecorder
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package io.element.android.libraries.preferences.test
|
||||
|
||||
import io.element.android.features.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package io.element.android.libraries.preferences.test
|
||||
|
||||
import io.element.android.features.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package io.element.android.libraries.push.impl.notifications
|
||||
|
||||
import android.content.Intent
|
||||
import io.element.android.features.preferences.api.store.SessionPreferencesStoreFactory
|
||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
@@ -27,6 +26,7 @@ import io.element.android.libraries.matrix.api.core.ThreadId
|
||||
import io.element.android.libraries.matrix.api.core.asEventId
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStoreFactory
|
||||
import io.element.android.libraries.push.api.notifications.NotificationDrawerManager
|
||||
import io.element.android.libraries.push.impl.R
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
|
||||
|
||||
@@ -18,8 +18,6 @@ package io.element.android.libraries.push.impl.notifications
|
||||
|
||||
import android.content.Intent
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.features.preferences.api.store.SessionPreferencesStoreFactory
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
@@ -38,6 +36,8 @@ import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStoreFactory
|
||||
import io.element.android.libraries.preferences.test.FakeSessionPreferencesStoreFactory
|
||||
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
|
||||
import io.element.android.libraries.push.api.notifications.NotificationDrawerManager
|
||||
|
||||
@@ -115,6 +115,7 @@ fun DependencyHandlerScope.allLibrariesImpl() {
|
||||
implementation(project(":libraries:mediaplayer:impl"))
|
||||
implementation(project(":libraries:mediaviewer:impl"))
|
||||
implementation(project(":libraries:troubleshoot:impl"))
|
||||
implementation(project(":libraries:fullscreenintent:impl"))
|
||||
}
|
||||
|
||||
fun DependencyHandlerScope.allServicesImpl() {
|
||||
|
||||
@@ -108,7 +108,7 @@ fun Project.setupKover() {
|
||||
"*Presenter\$present\$*",
|
||||
// Forked from compose
|
||||
"io.element.android.libraries.designsystem.theme.components.bottomsheet.*",
|
||||
// Test presenter
|
||||
// Test presenters
|
||||
"io.element.android.features.leaveroom.fake.FakeLeaveRoomPresenter",
|
||||
)
|
||||
annotatedBy(
|
||||
@@ -158,13 +158,15 @@ fun Project.setupKover() {
|
||||
}
|
||||
excludes {
|
||||
classes(
|
||||
"*Fake*Presenter",
|
||||
"*Fake*Presenter*",
|
||||
"io.element.android.appnav.loggedin.LoggedInPresenter$*",
|
||||
// Some options can't be tested at the moment
|
||||
"io.element.android.features.preferences.impl.developer.DeveloperSettingsPresenter$*",
|
||||
// Need an Activity to use rememberMultiplePermissionsState
|
||||
"io.element.android.features.location.impl.common.permissions.DefaultPermissionsPresenter",
|
||||
"*Presenter\$present\$*",
|
||||
// Too small to be > 85% tested
|
||||
"io.element.android.libraries.fullscreenintent.impl.DefaultFullScreenIntentPermissionsPresenter",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ dependencies {
|
||||
implementation(projects.libraries.network)
|
||||
implementation(projects.libraries.dateformatter.impl)
|
||||
implementation(projects.libraries.eventformatter.impl)
|
||||
implementation(projects.libraries.fullscreenintent.impl)
|
||||
implementation(projects.libraries.preferences.impl)
|
||||
implementation(projects.libraries.indicator.impl)
|
||||
implementation(projects.features.invite.impl)
|
||||
|
||||
@@ -45,6 +45,8 @@ import io.element.android.libraries.eventformatter.impl.RoomMembershipContentFor
|
||||
import io.element.android.libraries.eventformatter.impl.StateContentFormatter
|
||||
import io.element.android.libraries.featureflag.impl.DefaultFeatureFlagService
|
||||
import io.element.android.libraries.featureflag.impl.StaticFeatureFlagProvider
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsPresenter
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
|
||||
import io.element.android.libraries.indicator.impl.DefaultIndicatorService
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
@@ -139,6 +141,17 @@ class RoomListScreen(
|
||||
notificationDrawerManager = FakeNotificationDrawerManager(),
|
||||
),
|
||||
analyticsService = NoopAnalyticsService(),
|
||||
fullScreenIntentPermissionsPresenter = object : FullScreenIntentPermissionsPresenter {
|
||||
@Composable
|
||||
override fun present(): FullScreenIntentPermissionsState {
|
||||
return FullScreenIntentPermissionsState(
|
||||
permissionGranted = true,
|
||||
shouldDisplayBanner = false,
|
||||
dismissFullScreenIntentBanner = {},
|
||||
openFullScreenIntentSettings = {}
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -27,7 +27,8 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
class FakeAnalyticsService(
|
||||
isEnabled: Boolean = false,
|
||||
didAskUserConsent: Boolean = false
|
||||
didAskUserConsent: Boolean = false,
|
||||
private val resetLambda: () -> Unit = {},
|
||||
) : AnalyticsService {
|
||||
private val isEnabledFlow = MutableStateFlow(isEnabled)
|
||||
private val didAskUserConsentFlow = MutableStateFlow(didAskUserConsent)
|
||||
@@ -77,5 +78,6 @@ class FakeAnalyticsService(
|
||||
|
||||
override suspend fun reset() {
|
||||
didAskUserConsentFlow.value = false
|
||||
resetLambda()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 io.element.android.services.toolbox.api.intent
|
||||
|
||||
import android.content.Intent
|
||||
|
||||
/**
|
||||
* Used to launch external intents from anywhere in the app.
|
||||
*/
|
||||
interface ExternalIntentLauncher {
|
||||
fun launch(intent: Intent)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 io.element.android.services.toolbox.impl.intent
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
import io.element.android.services.toolbox.api.intent.ExternalIntentLauncher
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultExternalIntentLauncher @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
) : ExternalIntentLauncher {
|
||||
override fun launch(intent: Intent) {
|
||||
context.startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 io.element.android.services.toolbox.test.intent
|
||||
|
||||
import android.content.Intent
|
||||
import io.element.android.services.toolbox.api.intent.ExternalIntentLauncher
|
||||
|
||||
class FakeExternalIntentLauncher(
|
||||
var launchLambda: (Intent) -> Unit = {},
|
||||
) : ExternalIntentLauncher {
|
||||
override fun launch(intent: Intent) {
|
||||
launchLambda(intent)
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user