From 9ded4284b2b8a4b1124a27a7eb73d24366485bec Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 10 Oct 2023 18:09:46 +0200 Subject: [PATCH 01/10] Setup the pin feature module --- features/pin/api/build.gradle.kts | 27 +++++++ .../android/features/pin/api/PinEntryPoint.kt | 37 +++++++++ features/pin/impl/build.gradle.kts | 52 +++++++++++++ .../features/pin/impl/DefaultPinEntryPoint.kt | 46 +++++++++++ .../android/features/pin/impl/PinFlowNode.kt | 78 +++++++++++++++++++ .../pin/impl/auth/PinAuthenticationEvents.kt | 21 +++++ .../pin/impl/auth/PinAuthenticationNode.kt | 44 +++++++++++ .../impl/auth/PinAuthenticationPresenter.kt | 38 +++++++++ .../pin/impl/auth/PinAuthenticationState.kt | 21 +++++ .../auth/PinAuthenticationStateProvider.kt | 31 ++++++++ .../pin/impl/auth/PinAuthenticationView.kt | 50 ++++++++++++ .../pin/impl/create/CreatePinEvents.kt | 21 +++++ .../features/pin/impl/create/CreatePinNode.kt | 44 +++++++++++ .../pin/impl/create/CreatePinPresenter.kt | 38 +++++++++ .../pin/impl/create/CreatePinState.kt | 21 +++++ .../pin/impl/create/CreatePinStateProvider.kt | 31 ++++++++ .../features/pin/impl/create/CreatePinView.kt | 49 ++++++++++++ 17 files changed, 649 insertions(+) create mode 100644 features/pin/api/build.gradle.kts create mode 100644 features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinEntryPoint.kt create mode 100644 features/pin/impl/build.gradle.kts create mode 100644 features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/DefaultPinEntryPoint.kt create mode 100644 features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/PinFlowNode.kt create mode 100644 features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationEvents.kt create mode 100644 features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationNode.kt create mode 100644 features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationPresenter.kt create mode 100644 features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationState.kt create mode 100644 features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationStateProvider.kt create mode 100644 features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationView.kt create mode 100644 features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/create/CreatePinEvents.kt create mode 100644 features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/create/CreatePinNode.kt create mode 100644 features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/create/CreatePinPresenter.kt create mode 100644 features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/create/CreatePinState.kt create mode 100644 features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/create/CreatePinStateProvider.kt create mode 100644 features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/create/CreatePinView.kt diff --git a/features/pin/api/build.gradle.kts b/features/pin/api/build.gradle.kts new file mode 100644 index 0000000000..95b062b0c8 --- /dev/null +++ b/features/pin/api/build.gradle.kts @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 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.pin.api" +} + +dependencies { + implementation(projects.libraries.architecture) +} diff --git a/features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinEntryPoint.kt b/features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinEntryPoint.kt new file mode 100644 index 0000000000..df0bf255f4 --- /dev/null +++ b/features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinEntryPoint.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2023 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.pin.api + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import io.element.android.libraries.architecture.FeatureEntryPoint + +interface PinEntryPoint : FeatureEntryPoint { + + fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder + + interface NodeBuilder { + fun callback(callback: Callback): NodeBuilder + fun build(): Node + } + + interface Callback : Plugin { + // Add your callbacks + } +} + diff --git a/features/pin/impl/build.gradle.kts b/features/pin/impl/build.gradle.kts new file mode 100644 index 0000000000..6bf14646e3 --- /dev/null +++ b/features/pin/impl/build.gradle.kts @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023 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. + */ + +// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed +@Suppress("DSL_SCOPE_VIOLATION") +plugins { + id("io.element.android-compose-library") + alias(libs.plugins.anvil) + alias(libs.plugins.ksp) + id("kotlin-parcelize") +} + +android { + namespace = "io.element.android.features.pin.impl" +} + +anvil { + generateDaggerFactories.set(true) +} + +dependencies { + implementation(projects.anvilannotations) + anvil(projects.anvilcodegen) + api(projects.features.pin.api) + implementation(projects.libraries.core) + implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) + implementation(projects.libraries.matrixui) + implementation(projects.libraries.designsystem) + + testImplementation(libs.test.junit) + testImplementation(libs.coroutines.test) + testImplementation(libs.molecule.runtime) + testImplementation(libs.test.truth) + testImplementation(libs.test.turbine) + testImplementation(projects.libraries.matrix.test) + + ksp(libs.showkase.processor) +} diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/DefaultPinEntryPoint.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/DefaultPinEntryPoint.kt new file mode 100644 index 0000000000..1dfba1a4cf --- /dev/null +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/DefaultPinEntryPoint.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 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.pin.impl + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.pin.api.PinEntryPoint +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.di.AppScope +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultPinEntryPoint @Inject constructor() : PinEntryPoint { + + override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): PinEntryPoint.NodeBuilder { + val plugins = ArrayList() + + return object : PinEntryPoint.NodeBuilder { + + override fun callback(callback: PinEntryPoint.Callback): PinEntryPoint.NodeBuilder { + plugins += callback + return this + } + + override fun build(): Node { + return parentNode.createNode(buildContext, plugins) + } + } + } +} diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/PinFlowNode.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/PinFlowNode.kt new file mode 100644 index 0000000000..a76504ce8a --- /dev/null +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/PinFlowNode.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023 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.pin.impl + +import android.os.Parcelable +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.composable.Children +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.navmodel.backstack.BackStack +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.features.pin.impl.auth.PinAuthenticationNode +import io.element.android.features.pin.impl.create.CreatePinNode +import io.element.android.libraries.architecture.BackstackNode +import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.di.AppScope +import kotlinx.parcelize.Parcelize + +@ContributesNode(AppScope::class) +class PinFlowNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, +) : BackstackNode( + backstack = BackStack( + initialElement = NavTarget.Auth, + savedStateMap = buildContext.savedStateMap, + ), + buildContext = buildContext, + plugins = plugins, +) { + + sealed interface NavTarget : Parcelable { + @Parcelize + data object Auth : NavTarget + + @Parcelize + data object Create : NavTarget + } + + override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { + return when (navTarget) { + NavTarget.Auth -> { + createNode(buildContext) + } + NavTarget.Create -> { + createNode(buildContext) + } + } + } + + @Composable + override fun View(modifier: Modifier) { + Children( + navModel = backstack, + modifier = modifier, + transitionHandler = rememberDefaultTransitionHandler(), + ) + } +} diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationEvents.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationEvents.kt new file mode 100644 index 0000000000..a56412aa6e --- /dev/null +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationEvents.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 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.pin.impl.auth + +sealed interface PinAuthenticationEvents { + object MyEvent : PinAuthenticationEvents +} diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationNode.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationNode.kt new file mode 100644 index 0000000000..b5dab44c96 --- /dev/null +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationNode.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 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.pin.impl.auth + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.di.AppScope + +@ContributesNode(AppScope::class) +class PinAuthenticationNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: PinAuthenticationPresenter, +) : Node(buildContext, plugins = plugins) { + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + PinAuthenticationView( + state = state, + modifier = modifier + ) + } +} diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationPresenter.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationPresenter.kt new file mode 100644 index 0000000000..79eace0072 --- /dev/null +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationPresenter.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023 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.pin.impl.auth + +import androidx.compose.runtime.Composable +import io.element.android.libraries.architecture.Presenter +import javax.inject.Inject + +class PinAuthenticationPresenter @Inject constructor() : Presenter { + + @Composable + override fun present(): PinAuthenticationState { + + fun handleEvents(event: PinAuthenticationEvents) { + when (event) { + PinAuthenticationEvents.MyEvent -> Unit + } + } + + return PinAuthenticationState( + eventSink = ::handleEvents + ) + } +} diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationState.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationState.kt new file mode 100644 index 0000000000..2df1e50f83 --- /dev/null +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationState.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 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.pin.impl.auth + +data class PinAuthenticationState( + val eventSink: (PinAuthenticationEvents) -> Unit +) diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationStateProvider.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationStateProvider.kt new file mode 100644 index 0000000000..7aea002ee9 --- /dev/null +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationStateProvider.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 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.pin.impl.auth + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider + +open class PinAuthenticationStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aPinAuthenticationState(), + // Add other states here + ) +} + +fun aPinAuthenticationState() = PinAuthenticationState( + eventSink = {} +) diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationView.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationView.kt new file mode 100644 index 0000000000..b318eebe79 --- /dev/null +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationView.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 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.pin.impl.auth + +import androidx.compose.foundation.layout.Box +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.PreviewParameter +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Text + +@Composable +fun PinAuthenticationView( + state: PinAuthenticationState, + modifier: Modifier = Modifier, +) { + Box(modifier, contentAlignment = Alignment.Center) { + Text( + "PinAuthentication feature view", + color = MaterialTheme.colorScheme.primary, + ) + } +} + +@Composable +@PreviewsDayNight +fun PinAuthenticationViewLightPreview(@PreviewParameter(PinAuthenticationStateProvider::class) state: PinAuthenticationState) = + ElementPreview { + PinAuthenticationView( + state = state, + ) + } + diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/create/CreatePinEvents.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/create/CreatePinEvents.kt new file mode 100644 index 0000000000..280856b5c8 --- /dev/null +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/create/CreatePinEvents.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 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.pin.impl.create + +sealed interface CreatePinEvents { + object MyEvent : CreatePinEvents +} diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/create/CreatePinNode.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/create/CreatePinNode.kt new file mode 100644 index 0000000000..0ed0343a5b --- /dev/null +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/create/CreatePinNode.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 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.pin.impl.create + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.di.AppScope + +@ContributesNode(AppScope::class) +class CreatePinNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: CreatePinPresenter, +) : Node(buildContext, plugins = plugins) { + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + CreatePinView( + state = state, + modifier = modifier + ) + } +} diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/create/CreatePinPresenter.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/create/CreatePinPresenter.kt new file mode 100644 index 0000000000..d45257b4bd --- /dev/null +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/create/CreatePinPresenter.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023 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.pin.impl.create + +import androidx.compose.runtime.Composable +import io.element.android.libraries.architecture.Presenter +import javax.inject.Inject + +class CreatePinPresenter @Inject constructor() : Presenter { + + @Composable + override fun present(): CreatePinState { + + fun handleEvents(event: CreatePinEvents) { + when (event) { + CreatePinEvents.MyEvent -> Unit + } + } + + return CreatePinState( + eventSink = ::handleEvents + ) + } +} diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/create/CreatePinState.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/create/CreatePinState.kt new file mode 100644 index 0000000000..c405db82ec --- /dev/null +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/create/CreatePinState.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 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.pin.impl.create + +data class CreatePinState( + val eventSink: (CreatePinEvents) -> Unit +) diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/create/CreatePinStateProvider.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/create/CreatePinStateProvider.kt new file mode 100644 index 0000000000..4bff72023e --- /dev/null +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/create/CreatePinStateProvider.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 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.pin.impl.create + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider + +open class CreatePinStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aCreatePinState(), + // Add other states here + ) +} + +fun aCreatePinState() = CreatePinState( + eventSink = {} +) diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/create/CreatePinView.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/create/CreatePinView.kt new file mode 100644 index 0000000000..871d440a89 --- /dev/null +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/create/CreatePinView.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2023 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.pin.impl.create + +import androidx.compose.foundation.layout.Box +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.PreviewParameter +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Text + +@Composable +fun CreatePinView( + state: CreatePinState, + modifier: Modifier = Modifier, +) { + Box(modifier, contentAlignment = Alignment.Center) { + Text( + "CreatePin feature view", + color = MaterialTheme.colorScheme.primary, + ) + } +} + +@Composable +@PreviewsDayNight +fun CreatePinViewLightPreview(@PreviewParameter(CreatePinStateProvider::class) state: CreatePinState) = + ElementPreview { + CreatePinView( + state = state, + ) + } From 2d5a3a473cb00edad6bd0e5f8028638ff81a8b47 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 11 Oct 2023 16:25:27 +0200 Subject: [PATCH 02/10] Pin setup with fake lock --- .../io/element/android/appnav/BackstackExt.kt | 21 ++++++++ .../android/appnav/LoggedInFlowNode.kt | 47 ++++++++++++----- .../io/element/android/appnav/RootFlowNode.kt | 1 + .../android/appnav/loggedin/LoggedInNode.kt | 5 +- .../android/features/pin/api/PinState.kt | 22 ++++++++ .../features/pin/api/PinStateDataSource.kt | 26 ++++++++++ .../pin/impl/auth/PinAuthenticationEvents.kt | 2 +- .../impl/auth/PinAuthenticationPresenter.kt | 9 ++-- .../auth/PinAuthenticationStateProvider.kt | 1 - .../pin/impl/auth/PinAuthenticationView.kt | 51 ++++++++++++++++--- .../impl/state/DefaultPinStateDataSource.kt | 42 +++++++++++++++ .../molecules/IconTitleSubtitleMolecule.kt | 24 +++++---- 12 files changed, 211 insertions(+), 40 deletions(-) create mode 100644 features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinState.kt create mode 100644 features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinStateDataSource.kt create mode 100644 features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/state/DefaultPinStateDataSource.kt diff --git a/appnav/src/main/kotlin/io/element/android/appnav/BackstackExt.kt b/appnav/src/main/kotlin/io/element/android/appnav/BackstackExt.kt index 36b267debb..73bb9b9b85 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/BackstackExt.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/BackstackExt.kt @@ -16,6 +16,12 @@ package io.element.android.appnav +import android.content.Context +import android.content.ContextWrapper +import androidx.activity.ComponentActivity +import androidx.activity.compose.BackHandler +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.NewRoot import com.bumble.appyx.navmodel.backstack.operation.Remove @@ -41,3 +47,18 @@ fun BackStack.removeLast(element: T) { accept(Remove(lastExpectedNavElement.key)) } +@Composable +fun FinishActivityBackHandler(enabled: Boolean = true) { + + fun Context.findActivity(): ComponentActivity? = when (this) { + is ComponentActivity -> this + is ContextWrapper -> baseContext.findActivity() + else -> null + } + + val context = LocalContext.current + BackHandler(enabled = enabled) { + context.findActivity()?.finish() + } +} + diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index cf67a765d7..276f0ebbde 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -50,6 +50,9 @@ import io.element.android.features.ftue.api.state.FtueState import io.element.android.features.invitelist.api.InviteListEntryPoint import io.element.android.features.networkmonitor.api.NetworkMonitor import io.element.android.features.networkmonitor.api.NetworkStatus +import io.element.android.features.pin.api.PinEntryPoint +import io.element.android.features.pin.api.PinState +import io.element.android.features.pin.api.PinStateDataSource import io.element.android.features.preferences.api.PreferencesEntryPoint import io.element.android.features.roomlist.api.RoomListEntryPoint import io.element.android.features.verifysession.api.VerifySessionEntryPoint @@ -90,6 +93,8 @@ class LoggedInFlowNode @AssistedInject constructor( private val networkMonitor: NetworkMonitor, private val notificationDrawerManager: NotificationDrawerManager, private val ftueState: FtueState, + private val pinEntryPoint: PinEntryPoint, + private val pinStateDataSource: PinStateDataSource, private val matrixClient: MatrixClient, snackbarDispatcher: SnackbarDispatcher, ) : BackstackNode( @@ -98,7 +103,7 @@ class LoggedInFlowNode @AssistedInject constructor( savedStateMap = buildContext.savedStateMap, ), permanentNavModel = PermanentNavModel( - NavTarget.Permanent, + navTargets = setOf(NavTarget.LoggedInPermanent, NavTarget.LockPermanent), savedStateMap = buildContext.savedStateMap, ), buildContext = buildContext, @@ -130,6 +135,7 @@ class LoggedInFlowNode @AssistedInject constructor( } }, onStop = { + pinStateDataSource.lock() //Counterpart startSync is done in observeSyncStateAndNetworkStatus method. coroutineScope.launch { syncService.stopSync() @@ -167,7 +173,10 @@ class LoggedInFlowNode @AssistedInject constructor( sealed interface NavTarget : Parcelable { @Parcelize - data object Permanent : NavTarget + data object LoggedInPermanent : NavTarget + + @Parcelize + data object LockPermanent : NavTarget @Parcelize data object RoomList : NavTarget @@ -196,9 +205,12 @@ class LoggedInFlowNode @AssistedInject constructor( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { - NavTarget.Permanent -> { + NavTarget.LoggedInPermanent -> { createNode(buildContext) } + NavTarget.LockPermanent -> { + pinEntryPoint.nodeBuilder(this, buildContext).build() + } NavTarget.RoomList -> { val callback = object : RoomListEntryPoint.Callback { override fun onRoomClicked(roomId: RoomId) { @@ -324,17 +336,24 @@ class LoggedInFlowNode @AssistedInject constructor( @Composable override fun View(modifier: Modifier) { Box(modifier = modifier) { - Children( - navModel = backstack, - modifier = Modifier, - // Animate navigation to settings and to a room - transitionHandler = rememberDefaultTransitionHandler(), - ) - - val isFtueDisplayed by ftueState.shouldDisplayFlow.collectAsState() - - if (!isFtueDisplayed) { - PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.Permanent) + val pinState by pinStateDataSource.pinState.collectAsState() + when (pinState) { + PinState.Unlocked -> { + Children( + navModel = backstack, + modifier = Modifier, + // Animate navigation to settings and to a room + transitionHandler = rememberDefaultTransitionHandler(), + ) + val isFtueDisplayed by ftueState.shouldDisplayFlow.collectAsState() + if (!isFtueDisplayed) { + PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LoggedInPermanent) + } + } + PinState.Locked -> { + FinishActivityBackHandler() + PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LockPermanent) + } } } } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt index 94f344be7e..403b53f0e1 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -44,6 +44,7 @@ import io.element.android.appnav.root.RootPresenter import io.element.android.appnav.root.RootView import io.element.android.features.login.api.oidc.OidcAction import io.element.android.features.login.api.oidc.OidcActionFlow +import io.element.android.features.pin.api.PinEntryPoint import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint import io.element.android.libraries.architecture.BackstackNode import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInNode.kt index 6950b9b699..5ddbb164d8 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInNode.kt @@ -31,7 +31,10 @@ class LoggedInNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, private val loggedInPresenter: LoggedInPresenter, -) : Node(buildContext, plugins = plugins) { +) : Node( + buildContext = buildContext, + plugins = plugins +) { @Composable override fun View(modifier: Modifier) { diff --git a/features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinState.kt b/features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinState.kt new file mode 100644 index 0000000000..0ff1b0b3d5 --- /dev/null +++ b/features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinState.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 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.pin.api + +sealed interface PinState { + data object Unlocked : PinState + data object Locked : PinState +} diff --git a/features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinStateDataSource.kt b/features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinStateDataSource.kt new file mode 100644 index 0000000000..c0b52af8b7 --- /dev/null +++ b/features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinStateDataSource.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 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.pin.api + +import kotlinx.coroutines.flow.StateFlow + +interface PinStateDataSource { + val pinState: StateFlow + + fun lock() + fun unlock() +} diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationEvents.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationEvents.kt index a56412aa6e..110c62660a 100644 --- a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationEvents.kt +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationEvents.kt @@ -17,5 +17,5 @@ package io.element.android.features.pin.impl.auth sealed interface PinAuthenticationEvents { - object MyEvent : PinAuthenticationEvents + data object Unlock : PinAuthenticationEvents } diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationPresenter.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationPresenter.kt index 79eace0072..36970f34ab 100644 --- a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationPresenter.kt +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationPresenter.kt @@ -17,20 +17,21 @@ package io.element.android.features.pin.impl.auth import androidx.compose.runtime.Composable +import io.element.android.features.pin.api.PinStateDataSource import io.element.android.libraries.architecture.Presenter import javax.inject.Inject -class PinAuthenticationPresenter @Inject constructor() : Presenter { +class PinAuthenticationPresenter @Inject constructor( + private val pinStateDataSource: PinStateDataSource, +) : Presenter { @Composable override fun present(): PinAuthenticationState { - fun handleEvents(event: PinAuthenticationEvents) { when (event) { - PinAuthenticationEvents.MyEvent -> Unit + PinAuthenticationEvents.Unlock -> pinStateDataSource.unlock() } } - return PinAuthenticationState( eventSink = ::handleEvents ) diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationStateProvider.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationStateProvider.kt index 7aea002ee9..8e3f45ac07 100644 --- a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationStateProvider.kt +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationStateProvider.kt @@ -22,7 +22,6 @@ open class PinAuthenticationStateProvider : PreviewParameterProvider get() = sequenceOf( aPinAuthenticationState(), - // Add other states here ) } diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationView.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationView.kt index b318eebe79..9aa2527099 100644 --- a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationView.kt +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationView.kt @@ -16,29 +16,64 @@ package io.element.android.features.pin.impl.auth -import androidx.compose.foundation.layout.Box -import androidx.compose.material3.MaterialTheme +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Lock import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import androidx.lifecycle.Lifecycle +import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule +import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.theme.components.Button +import io.element.android.libraries.designsystem.theme.components.Surface +import io.element.android.libraries.designsystem.utils.OnLifecycleEvent @Composable fun PinAuthenticationView( state: PinAuthenticationState, modifier: Modifier = Modifier, ) { - Box(modifier, contentAlignment = Alignment.Center) { - Text( - "PinAuthentication feature view", - color = MaterialTheme.colorScheme.primary, + Surface(modifier) { + HeaderFooterPage( + modifier = Modifier + .systemBarsPadding() + .fillMaxSize(), + header = { PinAuthenticationHeader(modifier = Modifier.padding(top = 60.dp, bottom = 12.dp)) }, + footer = { PinAuthenticationFooter(state) }, ) } } +@Composable +private fun PinAuthenticationHeader( + modifier: Modifier = Modifier, +) { + IconTitleSubtitleMolecule( + modifier = modifier, + title = "Element X is locked", + subTitle = null, + iconImageVector = Icons.Default.Lock, + ) +} + +@Composable +private fun PinAuthenticationFooter(state: PinAuthenticationState) { + Button( + modifier = Modifier.fillMaxWidth(), + text = "Unlock", + onClick = { + state.eventSink(PinAuthenticationEvents.Unlock) + } + ) +} + @Composable @PreviewsDayNight fun PinAuthenticationViewLightPreview(@PreviewParameter(PinAuthenticationStateProvider::class) state: PinAuthenticationState) = diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/state/DefaultPinStateDataSource.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/state/DefaultPinStateDataSource.kt new file mode 100644 index 0000000000..e0f8c6e6f9 --- /dev/null +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/state/DefaultPinStateDataSource.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023 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.pin.impl.state + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.pin.api.PinState +import io.element.android.features.pin.api.PinStateDataSource +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.SingleIn +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@SingleIn(AppScope::class) +@ContributesBinding(AppScope::class) +class DefaultPinStateDataSource @Inject constructor() : PinStateDataSource { + + private val _pinState = MutableStateFlow(PinState.Locked) + override val pinState: StateFlow = _pinState + + override fun unlock() { + _pinState.value = PinState.Unlocked + } + + override fun lock() { + _pinState.value = PinState.Locked + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt index 88bc258348..52d8eb5baf 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt @@ -31,8 +31,8 @@ import androidx.compose.ui.unit.dp import io.element.android.libraries.designsystem.R import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtom import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtomSize -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.theme.ElementTheme @@ -49,7 +49,7 @@ import io.element.android.libraries.theme.ElementTheme @Composable fun IconTitleSubtitleMolecule( title: String, - subTitle: String, + subTitle: String?, modifier: Modifier = Modifier, iconResourceId: Int? = null, iconImageVector: ImageVector? = null, @@ -73,14 +73,16 @@ fun IconTitleSubtitleMolecule( style = ElementTheme.typography.fontHeadingMdBold, color = MaterialTheme.colorScheme.primary, ) - Spacer(Modifier.height(8.dp)) - Text( - text = subTitle, - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Center, - style = ElementTheme.typography.fontBodyMdRegular, - color = MaterialTheme.colorScheme.secondary, - ) + if (subTitle != null) { + Spacer(Modifier.height(8.dp)) + Text( + text = subTitle, + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center, + style = ElementTheme.typography.fontBodyMdRegular, + color = MaterialTheme.colorScheme.secondary, + ) + } } } @@ -90,6 +92,6 @@ internal fun IconTitleSubtitleMoleculePreview() = ElementPreview { IconTitleSubtitleMolecule( iconResourceId = R.drawable.ic_compound_chat, title = "Title", - subTitle = "Sub iitle", + subTitle = "Subtitle", ) } From ea0963c0c8e6c4c658ea44cb160389f7da435080 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 11 Oct 2023 20:25:51 +0200 Subject: [PATCH 03/10] Pin: use moveTaskToBack instead of finish --- .../src/main/kotlin/io/element/android/appnav/BackstackExt.kt | 4 ++-- .../main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/BackstackExt.kt b/appnav/src/main/kotlin/io/element/android/appnav/BackstackExt.kt index 73bb9b9b85..e7bf9f200f 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/BackstackExt.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/BackstackExt.kt @@ -48,7 +48,7 @@ fun BackStack.removeLast(element: T) { } @Composable -fun FinishActivityBackHandler(enabled: Boolean = true) { +fun MoveActivityToBackgroundBackHandler(enabled: Boolean = true) { fun Context.findActivity(): ComponentActivity? = when (this) { is ComponentActivity -> this @@ -58,7 +58,7 @@ fun FinishActivityBackHandler(enabled: Boolean = true) { val context = LocalContext.current BackHandler(enabled = enabled) { - context.findActivity()?.finish() + context.findActivity()?.moveTaskToBack(false) } } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 276f0ebbde..6bc8c3afaf 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -351,7 +351,7 @@ class LoggedInFlowNode @AssistedInject constructor( } } PinState.Locked -> { - FinishActivityBackHandler() + MoveActivityToBackgroundBackHandler() PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LockPermanent) } } From 304ec0b74045b832a1e974530a24f58d946fbaf7 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 11 Oct 2023 20:36:30 +0200 Subject: [PATCH 04/10] Pin unlock : hides behind feature flag (disabled by default) --- .../android/appnav/LoggedInFlowNode.kt | 4 ++-- .../features/pin/api/PinStateDataSource.kt | 4 ++-- features/pin/impl/build.gradle.kts | 1 + .../impl/auth/PinAuthenticationPresenter.kt | 6 +++++- .../impl/state/DefaultPinStateDataSource.kt | 20 +++++++++++++------ .../libraries/featureflag/api/FeatureFlags.kt | 6 ++++++ .../impl/StaticFeatureFlagProvider.kt | 1 + 7 files changed, 31 insertions(+), 11 deletions(-) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 6bc8c3afaf..8879c39220 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -135,9 +135,9 @@ class LoggedInFlowNode @AssistedInject constructor( } }, onStop = { - pinStateDataSource.lock() - //Counterpart startSync is done in observeSyncStateAndNetworkStatus method. coroutineScope.launch { + pinStateDataSource.lock() + //Counterpart startSync is done in observeSyncStateAndNetworkStatus method. syncService.stopSync() } }, diff --git a/features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinStateDataSource.kt b/features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinStateDataSource.kt index c0b52af8b7..5098f6c0a6 100644 --- a/features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinStateDataSource.kt +++ b/features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinStateDataSource.kt @@ -21,6 +21,6 @@ import kotlinx.coroutines.flow.StateFlow interface PinStateDataSource { val pinState: StateFlow - fun lock() - fun unlock() + suspend fun lock() + suspend fun unlock() } diff --git a/features/pin/impl/build.gradle.kts b/features/pin/impl/build.gradle.kts index 6bf14646e3..0d115ac46c 100644 --- a/features/pin/impl/build.gradle.kts +++ b/features/pin/impl/build.gradle.kts @@ -40,6 +40,7 @@ dependencies { implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrixui) implementation(projects.libraries.designsystem) + implementation(projects.libraries.featureflag.api) testImplementation(libs.test.junit) testImplementation(libs.coroutines.test) diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationPresenter.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationPresenter.kt index 36970f34ab..a9660552ad 100644 --- a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationPresenter.kt +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationPresenter.kt @@ -19,17 +19,21 @@ package io.element.android.features.pin.impl.auth import androidx.compose.runtime.Composable import io.element.android.features.pin.api.PinStateDataSource import io.element.android.libraries.architecture.Presenter +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import javax.inject.Inject class PinAuthenticationPresenter @Inject constructor( private val pinStateDataSource: PinStateDataSource, + private val coroutineScope: CoroutineScope, ) : Presenter { @Composable override fun present(): PinAuthenticationState { + fun handleEvents(event: PinAuthenticationEvents) { when (event) { - PinAuthenticationEvents.Unlock -> pinStateDataSource.unlock() + PinAuthenticationEvents.Unlock -> coroutineScope.launch { pinStateDataSource.unlock() } } } return PinAuthenticationState( diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/state/DefaultPinStateDataSource.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/state/DefaultPinStateDataSource.kt index e0f8c6e6f9..396d3a8d3a 100644 --- a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/state/DefaultPinStateDataSource.kt +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/state/DefaultPinStateDataSource.kt @@ -21,22 +21,30 @@ import io.element.android.features.pin.api.PinState import io.element.android.features.pin.api.PinStateDataSource import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.SingleIn +import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.api.FeatureFlags import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) -class DefaultPinStateDataSource @Inject constructor() : PinStateDataSource { +class DefaultPinStateDataSource @Inject constructor( + private val featureFlagService: FeatureFlagService, +) : PinStateDataSource { - private val _pinState = MutableStateFlow(PinState.Locked) + private val _pinState = MutableStateFlow(PinState.Unlocked) override val pinState: StateFlow = _pinState - override fun unlock() { - _pinState.value = PinState.Unlocked + override suspend fun unlock() { + if (featureFlagService.isFeatureEnabled(FeatureFlags.PinUnlock)) { + _pinState.value = PinState.Unlocked + } } - override fun lock() { - _pinState.value = PinState.Locked + override suspend fun lock() { + if (featureFlagService.isFeatureEnabled(FeatureFlags.PinUnlock)) { + _pinState.value = PinState.Locked + } } } diff --git a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt index 8d35223986..121cf26271 100644 --- a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt +++ b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt @@ -49,4 +49,10 @@ enum class FeatureFlags( description = "Send and receive voice messages", defaultValue = false, ), + PinUnlock( + key = "feature.pinunlock", + title = "Pin unlock", + description = "Allow user to lock/unlock the app with a pin code or biometrics", + defaultValue = false, + ), } diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt index 7ef10262c9..48f159de83 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt @@ -36,6 +36,7 @@ class StaticFeatureFlagProvider @Inject constructor() : FeatureFlags.Polls -> true FeatureFlags.NotificationSettings -> true FeatureFlags.VoiceMessages -> false + FeatureFlags.PinUnlock -> false } } else { false From bdcd2714cce5a98612ddea9ac12dfab4ca28bbeb Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 11 Oct 2023 20:38:41 +0200 Subject: [PATCH 05/10] Pin : rename PinStateDataSource by PinStateService --- .../kotlin/io/element/android/appnav/LoggedInFlowNode.kt | 4 ++-- .../pin/api/{PinStateDataSource.kt => PinStateService.kt} | 2 +- .../features/pin/impl/auth/PinAuthenticationPresenter.kt | 4 ++-- ...faultPinStateDataSource.kt => DefaultPinStateService.kt} | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) rename features/pin/api/src/main/kotlin/io/element/android/features/pin/api/{PinStateDataSource.kt => PinStateService.kt} (96%) rename features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/state/{DefaultPinStateDataSource.kt => DefaultPinStateService.kt} (92%) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 8879c39220..e09a17503a 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -52,7 +52,7 @@ import io.element.android.features.networkmonitor.api.NetworkMonitor import io.element.android.features.networkmonitor.api.NetworkStatus import io.element.android.features.pin.api.PinEntryPoint import io.element.android.features.pin.api.PinState -import io.element.android.features.pin.api.PinStateDataSource +import io.element.android.features.pin.api.PinStateService import io.element.android.features.preferences.api.PreferencesEntryPoint import io.element.android.features.roomlist.api.RoomListEntryPoint import io.element.android.features.verifysession.api.VerifySessionEntryPoint @@ -94,7 +94,7 @@ class LoggedInFlowNode @AssistedInject constructor( private val notificationDrawerManager: NotificationDrawerManager, private val ftueState: FtueState, private val pinEntryPoint: PinEntryPoint, - private val pinStateDataSource: PinStateDataSource, + private val pinStateService PinStateService, private val matrixClient: MatrixClient, snackbarDispatcher: SnackbarDispatcher, ) : BackstackNode( diff --git a/features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinStateDataSource.kt b/features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinStateService.kt similarity index 96% rename from features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinStateDataSource.kt rename to features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinStateService.kt index 5098f6c0a6..92ec8715ab 100644 --- a/features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinStateDataSource.kt +++ b/features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinStateService.kt @@ -18,7 +18,7 @@ package io.element.android.features.pin.api import kotlinx.coroutines.flow.StateFlow -interface PinStateDataSource { +interface PinStateService { val pinState: StateFlow suspend fun lock() diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationPresenter.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationPresenter.kt index a9660552ad..754ca3a4fa 100644 --- a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationPresenter.kt +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationPresenter.kt @@ -17,14 +17,14 @@ package io.element.android.features.pin.impl.auth import androidx.compose.runtime.Composable -import io.element.android.features.pin.api.PinStateDataSource +import io.element.android.features.pin.api.PinStateService import io.element.android.libraries.architecture.Presenter import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import javax.inject.Inject class PinAuthenticationPresenter @Inject constructor( - private val pinStateDataSource: PinStateDataSource, + private val pinStateService PinStateService, private val coroutineScope: CoroutineScope, ) : Presenter { diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/state/DefaultPinStateDataSource.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/state/DefaultPinStateService.kt similarity index 92% rename from features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/state/DefaultPinStateDataSource.kt rename to features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/state/DefaultPinStateService.kt index 396d3a8d3a..e08a96f458 100644 --- a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/state/DefaultPinStateDataSource.kt +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/state/DefaultPinStateService.kt @@ -18,7 +18,7 @@ package io.element.android.features.pin.impl.state import com.squareup.anvil.annotations.ContributesBinding import io.element.android.features.pin.api.PinState -import io.element.android.features.pin.api.PinStateDataSource +import io.element.android.features.pin.api.PinStateService import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.SingleIn import io.element.android.libraries.featureflag.api.FeatureFlagService @@ -29,9 +29,9 @@ import javax.inject.Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) -class DefaultPinStateDataSource @Inject constructor( +class DefaultPinStateService @Inject constructor( private val featureFlagService: FeatureFlagService, -) : PinStateDataSource { +) : PinStateService { private val _pinState = MutableStateFlow(PinState.Unlocked) override val pinState: StateFlow = _pinState From 588565995c1dd71ae9eeea0626e0bfe23a7d54a1 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 11 Oct 2023 20:52:19 +0200 Subject: [PATCH 06/10] Pin code : add simple grace period --- .../android/appnav/LoggedInFlowNode.kt | 15 ++++++++++--- .../features/pin/api/PinStateService.kt | 3 ++- .../impl/auth/PinAuthenticationPresenter.kt | 4 ++-- .../pin/impl/state/DefaultPinStateService.kt | 21 ++++++++++++++++--- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index e09a17503a..d1c975f5b6 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -94,7 +94,7 @@ class LoggedInFlowNode @AssistedInject constructor( private val notificationDrawerManager: NotificationDrawerManager, private val ftueState: FtueState, private val pinEntryPoint: PinEntryPoint, - private val pinStateService PinStateService, + private val pinStateService: PinStateService, private val matrixClient: MatrixClient, snackbarDispatcher: SnackbarDispatcher, ) : BackstackNode( @@ -134,9 +134,18 @@ class LoggedInFlowNode @AssistedInject constructor( backstack.push(NavTarget.Ftue) } }, + onResume = { + coroutineScope.launch { + pinStateService.entersForeground() + } + }, + onPause = { + coroutineScope.launch { + pinStateService.entersBackground() + } + }, onStop = { coroutineScope.launch { - pinStateDataSource.lock() //Counterpart startSync is done in observeSyncStateAndNetworkStatus method. syncService.stopSync() } @@ -336,7 +345,7 @@ class LoggedInFlowNode @AssistedInject constructor( @Composable override fun View(modifier: Modifier) { Box(modifier = modifier) { - val pinState by pinStateDataSource.pinState.collectAsState() + val pinState by pinStateService.pinState.collectAsState() when (pinState) { PinState.Unlocked -> { Children( diff --git a/features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinStateService.kt b/features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinStateService.kt index 92ec8715ab..4ecb473c18 100644 --- a/features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinStateService.kt +++ b/features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinStateService.kt @@ -21,6 +21,7 @@ import kotlinx.coroutines.flow.StateFlow interface PinStateService { val pinState: StateFlow - suspend fun lock() + suspend fun entersForeground() + suspend fun entersBackground() suspend fun unlock() } diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationPresenter.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationPresenter.kt index 754ca3a4fa..5e7e274ba7 100644 --- a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationPresenter.kt +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationPresenter.kt @@ -24,7 +24,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject class PinAuthenticationPresenter @Inject constructor( - private val pinStateService PinStateService, + private val pinStateService: PinStateService, private val coroutineScope: CoroutineScope, ) : Presenter { @@ -33,7 +33,7 @@ class PinAuthenticationPresenter @Inject constructor( fun handleEvents(event: PinAuthenticationEvents) { when (event) { - PinAuthenticationEvents.Unlock -> coroutineScope.launch { pinStateDataSource.unlock() } + PinAuthenticationEvents.Unlock -> coroutineScope.launch { pinStateService.unlock() } } } return PinAuthenticationState( diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/state/DefaultPinStateService.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/state/DefaultPinStateService.kt index e08a96f458..69371c40ee 100644 --- a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/state/DefaultPinStateService.kt +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/state/DefaultPinStateService.kt @@ -23,10 +23,16 @@ import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.SingleIn import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags +import kotlinx.coroutines.Job +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch import javax.inject.Inject +private const val GRACE_PERIOD_IN_MILLIS = 90 * 1000L + @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class DefaultPinStateService @Inject constructor( @@ -36,15 +42,24 @@ class DefaultPinStateService @Inject constructor( private val _pinState = MutableStateFlow(PinState.Unlocked) override val pinState: StateFlow = _pinState + private var lockJob: Job? = null + override suspend fun unlock() { if (featureFlagService.isFeatureEnabled(FeatureFlags.PinUnlock)) { _pinState.value = PinState.Unlocked } } - override suspend fun lock() { - if (featureFlagService.isFeatureEnabled(FeatureFlags.PinUnlock)) { - _pinState.value = PinState.Locked + override suspend fun entersForeground() { + lockJob?.cancel() + } + + override suspend fun entersBackground(): Unit = coroutineScope { + lockJob = launch { + if (featureFlagService.isFeatureEnabled(FeatureFlags.PinUnlock)) { + delay(GRACE_PERIOD_IN_MILLIS) + _pinState.value = PinState.Locked + } } } } From a3e6d691a5a89a701fbe48239f3c1c2ff5788694 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 12 Oct 2023 12:29:53 +0200 Subject: [PATCH 07/10] Pin setup : clean up --- .../main/kotlin/io/element/android/appnav/RootFlowNode.kt | 1 - .../android/features/pin/impl/auth/PinAuthenticationView.kt | 5 ++--- .../android/features/pin/impl/create/CreatePinView.kt | 5 ++++- .../features/pin/impl/state/DefaultPinStateService.kt | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt index 403b53f0e1..94f344be7e 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -44,7 +44,6 @@ import io.element.android.appnav.root.RootPresenter import io.element.android.appnav.root.RootView import io.element.android.features.login.api.oidc.OidcAction import io.element.android.features.login.api.oidc.OidcActionFlow -import io.element.android.features.pin.api.PinEntryPoint import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint import io.element.android.libraries.architecture.BackstackNode import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationView.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationView.kt index 9aa2527099..9fe689bb39 100644 --- a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationView.kt +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/auth/PinAuthenticationView.kt @@ -26,14 +26,12 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp -import androidx.lifecycle.Lifecycle import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.Surface -import io.element.android.libraries.designsystem.utils.OnLifecycleEvent @Composable fun PinAuthenticationView( @@ -76,10 +74,11 @@ private fun PinAuthenticationFooter(state: PinAuthenticationState) { @Composable @PreviewsDayNight -fun PinAuthenticationViewLightPreview(@PreviewParameter(PinAuthenticationStateProvider::class) state: PinAuthenticationState) = +internal fun PinAuthenticationViewPreview(@PreviewParameter(PinAuthenticationStateProvider::class) state: PinAuthenticationState) { ElementPreview { PinAuthenticationView( state = state, ) } +} diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/create/CreatePinView.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/create/CreatePinView.kt index 871d440a89..efdbe62bfa 100644 --- a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/create/CreatePinView.kt +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/create/CreatePinView.kt @@ -25,12 +25,14 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Text +import timber.log.Timber @Composable fun CreatePinView( state: CreatePinState, modifier: Modifier = Modifier, ) { + Timber.d("CreatePinView: $state") Box(modifier, contentAlignment = Alignment.Center) { Text( "CreatePin feature view", @@ -41,9 +43,10 @@ fun CreatePinView( @Composable @PreviewsDayNight -fun CreatePinViewLightPreview(@PreviewParameter(CreatePinStateProvider::class) state: CreatePinState) = +internal fun CreatePinViewLightPreview(@PreviewParameter(CreatePinStateProvider::class) state: CreatePinState) { ElementPreview { CreatePinView( state = state, ) } +} diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/state/DefaultPinStateService.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/state/DefaultPinStateService.kt index 69371c40ee..9accef2b80 100644 --- a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/state/DefaultPinStateService.kt +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/state/DefaultPinStateService.kt @@ -54,7 +54,7 @@ class DefaultPinStateService @Inject constructor( lockJob?.cancel() } - override suspend fun entersBackground(): Unit = coroutineScope { + override suspend fun entersBackground() = coroutineScope { lockJob = launch { if (featureFlagService.isFeatureEnabled(FeatureFlags.PinUnlock)) { delay(GRACE_PERIOD_IN_MILLIS) From c5742b146b5bb691760cc82e888c60728b824dca Mon Sep 17 00:00:00 2001 From: ElementBot Date: Thu, 12 Oct 2023 10:45:08 +0000 Subject: [PATCH 08/10] Update screenshots --- ...ull_PinAuthenticationView-D-0_0_null_0,NEXUS_5,1.0,en].png | 3 +++ ...ull_PinAuthenticationView-N-0_1_null_0,NEXUS_5,1.0,en].png | 3 +++ ...e_null_CreatePinViewLight-D-1_1_null_0,NEXUS_5,1.0,en].png | 3 +++ ...e_null_CreatePinViewLight-N-1_2_null_0,NEXUS_5,1.0,en].png | 3 +++ ...ull_IconTitleSubtitleMolecule-D_0_null,NEXUS_5,1.0,en].png | 4 ++-- ...ull_IconTitleSubtitleMolecule-N_1_null,NEXUS_5,1.0,en].png | 4 ++-- 6 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.pin.impl.auth_null_PinAuthenticationView-D-0_0_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.pin.impl.auth_null_PinAuthenticationView-N-0_1_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.pin.impl.create_null_CreatePinViewLight-D-1_1_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.pin.impl.create_null_CreatePinViewLight-N-1_2_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.pin.impl.auth_null_PinAuthenticationView-D-0_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.pin.impl.auth_null_PinAuthenticationView-D-0_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..f1992899f6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.pin.impl.auth_null_PinAuthenticationView-D-0_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f19d53c688e3f862894775756f00040adb5cbba99de71c053ed503c1b8af9518 +size 15239 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.pin.impl.auth_null_PinAuthenticationView-N-0_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.pin.impl.auth_null_PinAuthenticationView-N-0_1_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..f7a21c8025 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.pin.impl.auth_null_PinAuthenticationView-N-0_1_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11c145595f7713bc7b66f9d07e917bca82d6e06a1b55367801eb4d2cbfef89b0 +size 14353 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.pin.impl.create_null_CreatePinViewLight-D-1_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.pin.impl.create_null_CreatePinViewLight-D-1_1_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..e8dd1a28d7 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.pin.impl.create_null_CreatePinViewLight-D-1_1_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9914a33ba23544bdfce1e21b52ad247024392730fb22b60bc9b6fa6440f004d4 +size 9216 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.pin.impl.create_null_CreatePinViewLight-N-1_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.pin.impl.create_null_CreatePinViewLight-N-1_2_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..157a7c52c3 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.pin.impl.create_null_CreatePinViewLight-N-1_2_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ad524a918e499fcea6fd5293358167ff52f8877cc31b778c8def01925fa662f +size 8582 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_null_IconTitleSubtitleMolecule-D_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_null_IconTitleSubtitleMolecule-D_0_null,NEXUS_5,1.0,en].png index 9b578f2a48..8fb30525d0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_null_IconTitleSubtitleMolecule-D_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_null_IconTitleSubtitleMolecule-D_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d5a887e4fa9170810c5567933b9c1a80c9ffbbb7b5b9f7187de9d0331482a78a -size 10619 +oid sha256:b514737761239d60aac83a2b5d822c1079b7029474097f5ea4a4a29a88174e81 +size 10687 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_null_IconTitleSubtitleMolecule-N_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_null_IconTitleSubtitleMolecule-N_1_null,NEXUS_5,1.0,en].png index 1d604d08ee..95a1fb946e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_null_IconTitleSubtitleMolecule-N_1_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_null_IconTitleSubtitleMolecule-N_1_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4d65a93f6e93d3725f51bb48b1d7dabaac4c4b10cb0897024a02966c04bfc3d5 -size 10508 +oid sha256:d648d16fa94fa4add5784cb0ca32173cc447dfd592bcf88ff82b53894c02f137 +size 10527 From 087142224c1e4afc2667e9492cef64d4f2caa6eb Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 12 Oct 2023 16:19:24 +0200 Subject: [PATCH 09/10] Pin setup: PR review --- .../android/appnav/LoggedInFlowNode.kt | 2 +- .../android/features/pin/api/PinEntryPoint.kt | 20 ++----------------- features/pin/impl/build.gradle.kts | 2 -- .../features/pin/impl/DefaultPinEntryPoint.kt | 17 ++-------------- 4 files changed, 5 insertions(+), 36 deletions(-) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index d1c975f5b6..ebb9fee54a 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -218,7 +218,7 @@ class LoggedInFlowNode @AssistedInject constructor( createNode(buildContext) } NavTarget.LockPermanent -> { - pinEntryPoint.nodeBuilder(this, buildContext).build() + pinEntryPoint.createNode(this, buildContext) } NavTarget.RoomList -> { val callback = object : RoomListEntryPoint.Callback { diff --git a/features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinEntryPoint.kt b/features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinEntryPoint.kt index df0bf255f4..1fe3caf574 100644 --- a/features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinEntryPoint.kt +++ b/features/pin/api/src/main/kotlin/io/element/android/features/pin/api/PinEntryPoint.kt @@ -16,22 +16,6 @@ package io.element.android.features.pin.api -import com.bumble.appyx.core.modality.BuildContext -import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin -import io.element.android.libraries.architecture.FeatureEntryPoint - -interface PinEntryPoint : FeatureEntryPoint { - - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder - - interface NodeBuilder { - fun callback(callback: Callback): NodeBuilder - fun build(): Node - } - - interface Callback : Plugin { - // Add your callbacks - } -} +import io.element.android.libraries.architecture.SimpleFeatureEntryPoint +interface PinEntryPoint : SimpleFeatureEntryPoint diff --git a/features/pin/impl/build.gradle.kts b/features/pin/impl/build.gradle.kts index 0d115ac46c..6cfc9fce11 100644 --- a/features/pin/impl/build.gradle.kts +++ b/features/pin/impl/build.gradle.kts @@ -14,8 +14,6 @@ * limitations under the License. */ -// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed -@Suppress("DSL_SCOPE_VIOLATION") plugins { id("io.element.android-compose-library") alias(libs.plugins.anvil) diff --git a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/DefaultPinEntryPoint.kt b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/DefaultPinEntryPoint.kt index 1dfba1a4cf..920691cad2 100644 --- a/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/DefaultPinEntryPoint.kt +++ b/features/pin/impl/src/main/kotlin/io/element/android/features/pin/impl/DefaultPinEntryPoint.kt @@ -18,7 +18,6 @@ package io.element.android.features.pin.impl import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin import com.squareup.anvil.annotations.ContributesBinding import io.element.android.features.pin.api.PinEntryPoint import io.element.android.libraries.architecture.createNode @@ -28,19 +27,7 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) class DefaultPinEntryPoint @Inject constructor() : PinEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): PinEntryPoint.NodeBuilder { - val plugins = ArrayList() - - return object : PinEntryPoint.NodeBuilder { - - override fun callback(callback: PinEntryPoint.Callback): PinEntryPoint.NodeBuilder { - plugins += callback - return this - } - - override fun build(): Node { - return parentNode.createNode(buildContext, plugins) - } - } + override fun createNode(parentNode: Node, buildContext: BuildContext): Node { + return parentNode.createNode(buildContext) } } From 0ad7e8fab0b040de46e1892991f36039024dbe03 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 12 Oct 2023 17:45:22 +0200 Subject: [PATCH 10/10] Pin setup : exclude pin presenters from test coverage check --- build.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index f08c023b1d..fcd67f07fe 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -250,6 +250,8 @@ koverMerged { excludes += "io.element.android.appnav.loggedin.LoggedInPresenter$*" // Some options can't be tested at the moment excludes += "io.element.android.features.preferences.impl.developer.DeveloperSettingsPresenter$*" + // Temporary until we have actually something to test. + excludes += "io.element.android.features.pin.impl.*Presenter" } bound { minValue = 85