FTUE - Choose a display name (#5211)

* adding base choose name fragment with UI

* add click handling for the display name actions

* updating real account display name

* setting the initial disabled state when the view is created

* adding header padding which would have been a toolbar

* exiting the flow on display name updated or skipped, the next PR will introduce the profile picture screen

* updating view model state testing to take all emissions into account

* adding tests around the onboarding view model
- cases for the personalisation and display name actions

* using colorSecondary instead of accent as per quality script rule

* making use of viewevent delegating action for the back handling

* using debounced clicks

* consuming the back action when existing the display name fragment via viewmodel

* making the keyboard imeDone update the display name
This commit is contained in:
Adam Brown 2022-03-02 17:59:40 +00:00 committed by GitHub
parent 74040c5563
commit 99e5a8f2fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 628 additions and 15 deletions

View File

@ -99,6 +99,7 @@ import im.vector.app.features.matrixto.MatrixToRoomSpaceFragment
import im.vector.app.features.matrixto.MatrixToUserFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthAccountCreatedFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthCaptchaFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthChooseDisplayNameFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthGenericTextInputFormFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthLoginFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthResetPasswordFragment
@ -479,6 +480,11 @@ interface FragmentModule {
@FragmentKey(FtueAuthAccountCreatedFragment::class)
fun bindFtueAuthAccountCreatedFragment(fragment: FtueAuthAccountCreatedFragment): Fragment
@Binds
@IntoMap
@FragmentKey(FtueAuthChooseDisplayNameFragment::class)
fun bindFtueAuthChooseDisplayNameFragment(fragment: FtueAuthChooseDisplayNameFragment): Fragment
@Binds
@IntoMap
@FragmentKey(UserListFragment::class)

View File

@ -73,4 +73,7 @@ sealed class OnboardingAction : VectorViewModelAction {
data class PostViewEvent(val viewEvent: OnboardingViewEvents) : OnboardingAction()
data class UserAcceptCertificate(val fingerprint: Fingerprint) : OnboardingAction()
data class UpdateDisplayName(val displayName: String) : OnboardingAction()
object UpdateDisplayNameSkipped : OnboardingAction()
}

View File

@ -52,4 +52,6 @@ sealed class OnboardingViewEvents : VectorViewEvents {
object OnAccountSignedIn : OnboardingViewEvents()
object OnTakeMeHome : OnboardingViewEvents()
object OnPersonalizeProfile : OnboardingViewEvents()
object OnDisplayNameUpdated : OnboardingViewEvents()
object OnDisplayNameSkipped : OnboardingViewEvents()
}

View File

@ -157,6 +157,8 @@ class OnboardingViewModel @AssistedInject constructor(
is OnboardingAction.UserAcceptCertificate -> handleUserAcceptCertificate(action)
OnboardingAction.ClearHomeServerHistory -> handleClearHomeServerHistory()
is OnboardingAction.PostViewEvent -> _viewEvents.post(action.viewEvent)
is OnboardingAction.UpdateDisplayName -> updateDisplayName(action.displayName)
OnboardingAction.UpdateDisplayNameSkipped -> _viewEvents.post(OnboardingViewEvents.OnDisplayNameSkipped)
}.exhaustive
}
@ -892,6 +894,21 @@ class OnboardingViewModel @AssistedInject constructor(
fun getFallbackUrl(forSignIn: Boolean, deviceId: String?): String? {
return authenticationService.getFallbackUrl(forSignIn, deviceId)
}
private fun updateDisplayName(displayName: String) {
setState { copy(asyncDisplayName = Loading()) }
viewModelScope.launch {
val activeSession = activeSessionHolder.getActiveSession()
try {
activeSession.setDisplayName(activeSession.myUserId, displayName)
setState { copy(asyncDisplayName = Success(Unit)) }
_viewEvents.post(OnboardingViewEvents.OnDisplayNameUpdated)
} catch (error: Throwable) {
setState { copy(asyncDisplayName = Fail(error)) }
_viewEvents.post(OnboardingViewEvents.Failure(error))
}
}
}
}
private fun LoginMode.supportsSignModeScreen(): Boolean {

View File

@ -32,6 +32,7 @@ data class OnboardingViewState(
val asyncResetPassword: Async<Unit> = Uninitialized,
val asyncResetMailConfirmed: Async<Unit> = Uninitialized,
val asyncRegistration: Async<Unit> = Uninitialized,
val asyncDisplayName: Async<Unit> = Uninitialized,
@PersistState
val onboardingFlow: OnboardingFlow? = null,
@ -71,7 +72,8 @@ data class OnboardingViewState(
asyncHomeServerLoginFlowRequest is Loading ||
asyncResetPassword is Loading ||
asyncResetMailConfirmed is Loading ||
asyncRegistration is Loading
asyncRegistration is Loading ||
asyncDisplayName is Loading
}
fun isAuthTaskCompleted(): Boolean {

View File

@ -0,0 +1,83 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.onboarding.ftueauth
import android.os.Bundle
import android.text.Editable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import com.google.android.material.textfield.TextInputLayout
import im.vector.app.core.platform.SimpleTextWatcher
import im.vector.app.databinding.FragmentFtueDisplayNameBinding
import im.vector.app.features.onboarding.OnboardingAction
import im.vector.app.features.onboarding.OnboardingViewEvents
import javax.inject.Inject
class FtueAuthChooseDisplayNameFragment @Inject constructor() : AbstractFtueAuthFragment<FragmentFtueDisplayNameBinding>() {
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueDisplayNameBinding {
return FragmentFtueDisplayNameBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupViews()
}
private fun setupViews() {
views.displayNameSubmit.isEnabled = views.displayNameInput.hasContentEmpty()
views.displayNameInput.editText?.addTextChangedListener(object : SimpleTextWatcher() {
override fun afterTextChanged(s: Editable) {
val newContent = s.toString()
views.displayNameSubmit.isEnabled = newContent.isNotEmpty()
}
})
views.displayNameInput.editText?.setOnEditorActionListener { _, actionId, _ ->
when (actionId) {
EditorInfo.IME_ACTION_DONE -> {
updateDisplayName()
true
}
else -> false
}
}
views.displayNameSubmit.debouncedClicks {
updateDisplayName()
}
views.displayNameSkip.debouncedClicks { viewModel.handle(OnboardingAction.UpdateDisplayNameSkipped) }
}
private fun updateDisplayName() {
val newDisplayName = views.displayNameInput.editText?.text.toString()
viewModel.handle(OnboardingAction.UpdateDisplayName(newDisplayName))
}
override fun resetViewModel() {
// Nothing to do
}
override fun onBackPressed(toolbarButton: Boolean): Boolean {
viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnTakeMeHome))
return true
}
}
private fun TextInputLayout.hasContentEmpty() = !editText?.text.isNullOrEmpty()

View File

@ -231,8 +231,10 @@ class FtueAuthVariant(
}
OnboardingViewEvents.OnAccountCreated -> onAccountCreated()
OnboardingViewEvents.OnAccountSignedIn -> onAccountSignedIn()
OnboardingViewEvents.OnPersonalizeProfile -> TODO()
OnboardingViewEvents.OnPersonalizeProfile -> onPersonalizeProfile()
OnboardingViewEvents.OnTakeMeHome -> navigateToHome(createdAccount = true)
OnboardingViewEvents.OnDisplayNameUpdated -> onDisplayNameUpdated()
OnboardingViewEvents.OnDisplayNameSkipped -> onDisplayNameUpdated()
}.exhaustive
}
@ -410,4 +412,16 @@ class FtueAuthVariant(
activity.startActivity(intent)
activity.finish()
}
private fun onPersonalizeProfile() {
activity.addFragmentToBackstack(views.loginFragmentContainer,
FtueAuthChooseDisplayNameFragment::class.java,
option = commonOption
)
}
private fun onDisplayNameUpdated() {
// TODO go to the real profile picture fragment
navigateToHome(createdAccount = true)
}
}

View File

@ -7,13 +7,16 @@
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/loginContainer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/loginFragmentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Group
android:id="@+id/loginLoading"

View File

@ -0,0 +1,152 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
style="@style/LoginFormScrollView"
android:layout_height="match_parent"
android:background="?android:colorBackground"
android:fillViewport="true"
android:paddingTop="0dp"
android:paddingBottom="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/displayNameGutterStart"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_constraintGuide_percent="@dimen/ftue_auth_gutter_start_percent" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/displayNameGutterEnd"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_constraintGuide_percent="@dimen/ftue_auth_gutter_end_percent" />
<Space
android:id="@+id/headerSpacing"
android:layout_width="match_parent"
android:layout_height="52dp"
app:layout_constraintBottom_toTopOf="@id/displayNameHeaderIcon"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/displayNameHeaderIcon"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:adjustViewBounds="true"
android:contentDescription="@null"
android:src="@drawable/ic_user_round"
app:layout_constraintBottom_toTopOf="@id/displayNameHeaderTitle"
app:layout_constraintEnd_toEndOf="@id/displayNameGutterEnd"
app:layout_constraintHeight_percent="0.15"
app:layout_constraintStart_toStartOf="@id/displayNameGutterStart"
app:layout_constraintTop_toTopOf="parent"
app:tint="?colorSecondary" />
<TextView
android:id="@+id/displayNameHeaderTitle"
style="@style/Widget.Vector.TextView.Title.Medium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center"
android:text="@string/ftue_display_name_title"
android:textColor="?vctr_content_primary"
app:layout_constraintBottom_toTopOf="@id/displayNameHeaderSubtitle"
app:layout_constraintEnd_toEndOf="@id/displayNameGutterEnd"
app:layout_constraintStart_toStartOf="@id/displayNameGutterStart"
app:layout_constraintTop_toBottomOf="@id/displayNameHeaderIcon" />
<TextView
android:id="@+id/displayNameHeaderSubtitle"
style="@style/Widget.Vector.TextView.Subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
android:text="@string/ftue_display_name_subtitle"
android:textColor="?vctr_content_secondary"
app:layout_constraintBottom_toTopOf="@id/titleContentSpacing"
app:layout_constraintEnd_toEndOf="@id/displayNameGutterEnd"
app:layout_constraintStart_toStartOf="@id/displayNameGutterStart"
app:layout_constraintTop_toBottomOf="@id/displayNameHeaderTitle" />
<Space
android:id="@+id/titleContentSpacing"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/displayNameInput"
app:layout_constraintHeight_percent="0.03"
app:layout_constraintTop_toBottomOf="@id/displayNameHeaderSubtitle" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/displayNameInput"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/ftue_display_name_entry_title"
app:layout_constraintEnd_toEndOf="@id/displayNameGutterEnd"
app:layout_constraintStart_toStartOf="@id/displayNameGutterStart"
app:layout_constraintTop_toBottomOf="@id/titleContentSpacing">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:imeOptions="actionDone"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/displayNameEntryFooter"
style="@style/Widget.Vector.TextView.Micro"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@string/ftue_display_name_entry_footer"
app:layout_constraintEnd_toEndOf="@id/displayNameGutterEnd"
app:layout_constraintStart_toStartOf="@id/displayNameGutterStart"
app:layout_constraintTop_toBottomOf="@id/displayNameInput" />
<Space
android:id="@+id/actionsSpacing"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/displayNameSubmit"
app:layout_constraintHeight_percent="0.03"
app:layout_constraintTop_toBottomOf="@id/displayNameEntryFooter"
app:layout_constraintVertical_bias="0"
app:layout_constraintVertical_chainStyle="packed" />
<Button
android:id="@+id/displayNameSubmit"
style="@style/Widget.Vector.Button.Login"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/ftue_personalize_submit"
android:textAllCaps="true"
app:layout_constraintBottom_toTopOf="@id/displayNameSkip"
app:layout_constraintEnd_toEndOf="@id/displayNameGutterEnd"
app:layout_constraintStart_toStartOf="@id/displayNameGutterStart"
app:layout_constraintTop_toBottomOf="@id/actionsSpacing" />
<Button
android:id="@+id/displayNameSkip"
style="@style/Widget.Vector.Button.Text.Login"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/ftue_personalize_skip_this_step"
android:textAllCaps="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/displayNameGutterEnd"
app:layout_constraintStart_toStartOf="@id/displayNameGutterStart"
app:layout_constraintTop_toBottomOf="@id/displayNameSubmit" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>

View File

@ -22,4 +22,12 @@
<string name="ftue_account_created_take_me_home" translatable="false">Take me home</string>
<string name="ftue_account_created_congratulations_title" translatable="false">Congratulations!</string>
<string name="ftue_account_created_subtitle" translatable="false">Your account %s has been created.</string>
<string name="ftue_display_name_title" translatable="false">Choose a display name</string>
<string name="ftue_display_name_subtitle" translatable="false">This will be shown when you send messages.</string>
<string name="ftue_display_name_entry_title" translatable="false">Display Name</string>
<string name="ftue_display_name_entry_footer" translatable="false">You can change this later</string>
<string name="ftue_personalize_submit" translatable="false">Save and continue</string>
<string name="ftue_personalize_skip_this_step" translatable="false">Skip this step</string>
</resources>

View File

@ -102,10 +102,16 @@ class SharedSecureStorageViewModelTest {
viewModel.handle(SharedSecureStorageAction.UseKey)
test
.assertState(aViewState(
hasPassphrase = true,
step = SharedSecureStorageViewState.Step.EnterKey
))
.assertStates(
aViewState(
hasPassphrase = true,
step = SharedSecureStorageViewState.Step.EnterPassphrase
),
aViewState(
hasPassphrase = true,
step = SharedSecureStorageViewState.Step.EnterKey
)
)
.finish()
}
}
@ -121,10 +127,20 @@ class SharedSecureStorageViewModelTest {
viewModel.handle(SharedSecureStorageAction.Back)
test
.assertState(aViewState(
hasPassphrase = true,
step = SharedSecureStorageViewState.Step.EnterPassphrase
))
.assertStates(
aViewState(
hasPassphrase = true,
step = SharedSecureStorageViewState.Step.EnterPassphrase
),
aViewState(
hasPassphrase = true,
step = SharedSecureStorageViewState.Step.EnterKey
),
aViewState(
hasPassphrase = true,
step = SharedSecureStorageViewState.Step.EnterPassphrase
)
)
.finish()
}
}

View File

@ -0,0 +1,116 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.onboarding
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.test.MvRxTestRule
import im.vector.app.features.login.ReAuthHelper
import im.vector.app.test.fakes.FakeActiveSessionHolder
import im.vector.app.test.fakes.FakeAnalyticsTracker
import im.vector.app.test.fakes.FakeAuthenticationService
import im.vector.app.test.fakes.FakeContext
import im.vector.app.test.fakes.FakeHomeServerConnectionConfigFactory
import im.vector.app.test.fakes.FakeHomeServerHistoryService
import im.vector.app.test.fakes.FakeSession
import im.vector.app.test.fakes.FakeStringProvider
import im.vector.app.test.fakes.FakeVectorFeatures
import im.vector.app.test.test
import kotlinx.coroutines.test.runBlockingTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
private const val A_DISPLAY_NAME = "a display name"
class OnboardingViewModelTest {
@get:Rule
val mvrxTestRule = MvRxTestRule()
private val fakeContext = FakeContext()
lateinit var viewModel: OnboardingViewModel
private val initialState = OnboardingViewState()
private val fakeSession = FakeSession()
private val fakeActiveSessionHolder = FakeActiveSessionHolder(fakeSession)
@Before
fun setUp() {
viewModel = createViewModel()
}
@Test
fun `when handling PostViewEvent then emits contents as view event`() = runBlockingTest {
val test = viewModel.test(this)
viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnTakeMeHome))
test
.assertEvents(OnboardingViewEvents.OnTakeMeHome)
.finish()
}
@Test
fun `when handling display name updates action then updates user display name and emits name updated event`() = runBlockingTest {
val test = viewModel.test(this)
viewModel.handle(OnboardingAction.UpdateDisplayName(A_DISPLAY_NAME))
test
.assertStates(
initialState,
initialState.copy(asyncDisplayName = Loading()),
initialState.copy(asyncDisplayName = Success(Unit)),
)
.assertEvents(OnboardingViewEvents.OnDisplayNameUpdated)
.finish()
}
@Test
fun `given failure when handling display name updates action then emits failure event`() = runBlockingTest {
val test = viewModel.test(this)
val errorCause = RuntimeException("an error!")
fakeSession.fakeProfileService.givenSetDisplayNameErrors(errorCause)
viewModel.handle(OnboardingAction.UpdateDisplayName(A_DISPLAY_NAME))
test
.assertStates(
initialState,
initialState.copy(asyncDisplayName = Loading()),
initialState.copy(asyncDisplayName = Fail(errorCause)),
)
.assertEvents(OnboardingViewEvents.Failure(errorCause))
.finish()
}
private fun createViewModel(): OnboardingViewModel {
return OnboardingViewModel(
initialState,
fakeContext.instance,
FakeAuthenticationService(),
fakeActiveSessionHolder.instance,
FakeHomeServerConnectionConfigFactory().instance,
ReAuthHelper(),
FakeStringProvider().instance,
FakeHomeServerHistoryService(),
FakeVectorFeatures(),
FakeAnalyticsTracker()
)
}
}

View File

@ -21,18 +21,19 @@ import im.vector.app.core.platform.VectorViewEvents
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.platform.VectorViewModelAction
import kotlinx.coroutines.CoroutineScope
import org.amshove.kluent.shouldBeEqualTo
fun String.trimIndentOneLine() = trimIndent().replace("\n", "")
fun <S : MavericksState, VA : VectorViewModelAction, VE : VectorViewEvents> VectorViewModel<S, VA, VE>.test(coroutineScope: CoroutineScope): ViewModelTest<S, VE> {
val state = { com.airbnb.mvrx.withState(this) { it } }
// val state = { com.airbnb.mvrx.withState(this) { it } }
val state = stateFlow.test(coroutineScope)
val viewEvents = viewEvents.stream().test(coroutineScope)
return ViewModelTest(state, viewEvents)
}
class ViewModelTest<S, VE>(
val state: () -> S,
val states: FlowTestObserver<S>,
val viewEvents: FlowTestObserver<VE>
) {
@ -41,12 +42,18 @@ class ViewModelTest<S, VE>(
return this
}
fun assertStates(vararg expected: S): ViewModelTest<S, VE> {
states.assertValues(*expected)
return this
}
fun assertState(expected: S): ViewModelTest<S, VE> {
state() shouldBeEqualTo expected
states.assertValues(expected)
return this
}
fun finish() {
states.finish()
viewEvents.finish()
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.test.fakes
import im.vector.app.core.di.ActiveSessionHolder
import io.mockk.every
import io.mockk.mockk
class FakeActiveSessionHolder(
private val fakeSession: FakeSession = FakeSession()
) {
val instance = mockk<ActiveSessionHolder> {
every { getActiveSession() } returns fakeSession
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.test.fakes
import im.vector.app.features.analytics.AnalyticsTracker
import io.mockk.mockk
class FakeAnalyticsTracker : AnalyticsTracker by mockk()

View File

@ -0,0 +1,22 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.test.fakes
import io.mockk.mockk
import org.matrix.android.sdk.api.auth.AuthenticationService
class FakeAuthenticationService : AuthenticationService by mockk()

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.test.fakes
import im.vector.app.features.login.HomeServerConnectionConfigFactory
import io.mockk.mockk
class FakeHomeServerConnectionConfigFactory {
val instance: HomeServerConnectionConfigFactory = mockk()
}

View File

@ -0,0 +1,24 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.test.fakes
import io.mockk.mockk
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
class FakeHomeServerHistoryService : HomeServerHistoryService by mockk() {
override fun getKnownServersUrls() = emptyList<String>()
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.test.fakes
import io.mockk.mockk
import org.matrix.android.sdk.api.session.profile.ProfileService
class FakeProfileService : ProfileService by mockk() {
private var setDisplayNameError: Throwable? = null
override suspend fun setDisplayName(userId: String, newDisplayName: String) {
setDisplayNameError?.let { throw it }
}
fun givenSetDisplayNameErrors(errorCause: RuntimeException) {
setDisplayNameError = errorCause
}
}

View File

@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.session.Session
class FakeSession(
val fakeCryptoService: FakeCryptoService = FakeCryptoService(),
val fakeProfileService: FakeProfileService = FakeProfileService(),
val fakeSharedSecretStorageService: FakeSharedSecretStorageService = FakeSharedSecretStorageService()
) : Session by mockk(relaxed = true) {
@ -36,6 +37,7 @@ class FakeSession(
override fun cryptoService() = fakeCryptoService
override val sharedSecretStorageService = fakeSharedSecretStorageService
override val coroutineDispatchers = testCoroutineDispatchers
override suspend fun setDisplayName(userId: String, newDisplayName: String) = fakeProfileService.setDisplayName(userId, newDisplayName)
fun givenVectorStore(vectorSessionStore: VectorSessionStore) {
coEvery {

View File

@ -0,0 +1,27 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.test.fakes
import im.vector.app.features.VectorFeatures
class FakeVectorFeatures : VectorFeatures {
override fun onboardingVariant() = VectorFeatures.OnboardingVariant.FTUE_AUTH
override fun isOnboardingAlreadyHaveAccountSplashEnabled() = true
override fun isOnboardingSplashCarouselEnabled() = true
override fun isOnboardingUseCaseEnabled() = true
override fun isOnboardingPersonalizeEnabled() = false
}