Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Part of #4938: Profile Configuration and Migration [Blocked on #5457] #5387

Open
wants to merge 84 commits into
base: onboarding-language-domain-config
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 74 commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
772d126
Merge branch 'onboarding-audio-language-screen' of github.com:oppia/o…
adhiamboperes Apr 15, 2024
f0e9f23
cherry pick from old branch
adhiamboperes Feb 28, 2024
430e995
Create profile
adhiamboperes Apr 15, 2024
027c745
Merge branch 'onboarding-audio-language-screen' into onboarding-profi…
adhiamboperes Jun 13, 2024
ae9886f
Create sole learner profile with tests
adhiamboperes Jun 13, 2024
5257e30
Create profile onboarding event logs
adhiamboperes Jun 13, 2024
edd2151
Exit app for sole learner profile
adhiamboperes Jun 13, 2024
c1698ed
Add login pathway for sole learner
adhiamboperes Jun 13, 2024
441b2d0
fix import order
adhiamboperes Jun 13, 2024
bcc2cbf
Add tests for migrated login routes
adhiamboperes Jun 17, 2024
6c1a28f
Add tests for migrated login routes
adhiamboperes Jun 17, 2024
822e1fb
Log onboarding started event
adhiamboperes Jun 17, 2024
d71ed56
Update test initialization for onboarding v2 off
adhiamboperes Jun 17, 2024
1e9c136
Update test initialization for onboarding v2 off
adhiamboperes Jun 17, 2024
9c2e1d6
Merge branch 'onboarding-audio-language-screen' into onboarding-profi…
adhiamboperes Jun 17, 2024
0b74249
Merge branch 'onboarding-audio-language-screen' into onboarding-profi…
adhiamboperes Jun 19, 2024
db36244
Fix merge conflict
adhiamboperes Jun 19, 2024
35a839e
Add missing bazel dependency
adhiamboperes Jun 19, 2024
59c4665
Complete login migration route tests
adhiamboperes Jun 20, 2024
ab36c03
Fix tests/flows broken by changes
adhiamboperes Jun 20, 2024
c327be9
Add tests for end profile onboarding event log
adhiamboperes Jun 20, 2024
fccac67
Add tests for profile creation errors
adhiamboperes Jun 20, 2024
4cd3918
General cleanup
adhiamboperes Jun 21, 2024
2c33188
General cleanup
adhiamboperes Jun 21, 2024
026ff1d
Revert breaking change
adhiamboperes Jun 21, 2024
7304449
Add kdoc
adhiamboperes Jun 21, 2024
2cb0469
Fix static check failures
adhiamboperes Jun 21, 2024
c32d2e9
Fix failing event log events
adhiamboperes Jun 21, 2024
cb8b21e
Fix failing app deprecation tests
adhiamboperes Jun 22, 2024
c827a68
Refactor the app startup state
adhiamboperes Jun 23, 2024
5a570b2
Fix event logs
adhiamboperes Jun 23, 2024
381c883
Revert changes to app init
adhiamboperes Jun 24, 2024
bdcf5c1
Enforce v2 onboarding flow
adhiamboperes Jun 24, 2024
0a59cfc
Enforce conditional profile exit for sole learner
adhiamboperes Jun 24, 2024
5ee3a20
Revert unnecessary test file changes
adhiamboperes Jun 24, 2024
df789ea
Fix proto field case
adhiamboperes Jun 24, 2024
a1b0964
Fix SplashActivityTests
adhiamboperes Jun 24, 2024
8d8ab17
Merge branch 'onboarding-audio-language-screen' into onboarding-profi…
adhiamboperes Jun 25, 2024
61a7724
Fix test file exemption path
adhiamboperes Jun 25, 2024
5512216
Add missing bazel dep
adhiamboperes Jun 25, 2024
a0fe5c6
Add dropdown view id
adhiamboperes Jun 25, 2024
2bf2eda
Hook up app language options
adhiamboperes Jun 26, 2024
d76307b
Revert conflicting/already implemented UI layer code
adhiamboperes Jul 12, 2024
844c0e2
Temporary comments
adhiamboperes Jul 12, 2024
9ba0b9b
Merge branch 'onboarding-language-domain-config' into onboarding-prof…
adhiamboperes Jul 12, 2024
36f3cf6
Fix leftover refactor issue
adhiamboperes Jul 12, 2024
de5783c
Complete the sole learner onboarding flow
adhiamboperes Jul 15, 2024
7564eba
Merge branch 'onboarding-language-domain-config' into onboarding-prof…
adhiamboperes Jul 15, 2024
56f668a
Destroy onboarding activities after onboarding completed.
adhiamboperes Jul 15, 2024
7a97741
Fix failing SplashActivityTests
adhiamboperes Jul 15, 2024
8efa8b9
Fix failing SplashActivityTests + finish migration pathways
adhiamboperes Jul 17, 2024
318334f
Onboard existing learner profiles
adhiamboperes Jul 17, 2024
0bf61cc
Toggle homescreen with multiple classrooms flag
adhiamboperes Jul 17, 2024
cd25696
Merge branch 'onboarding-language-domain-config' into onboarding-prof…
adhiamboperes Jul 18, 2024
eb64975
General cleanup
adhiamboperes Jul 18, 2024
e7ee78a
Fix failing test
adhiamboperes Jul 23, 2024
e942263
Merge branch 'develop' into onboarding-language-domain-config
adhiamboperes Aug 1, 2024
9a8024b
Merge branch 'onboarding-language-domain-config' into onboarding-prof…
adhiamboperes Aug 1, 2024
ef1fd91
Merge branch 'onboarding-language-domain-config' into onboarding-prof…
adhiamboperes Aug 1, 2024
666ad36
Create test suite for TextInputLayoutBindingAdapters
adhiamboperes Aug 5, 2024
9ad283a
Address more reviewer comments.
adhiamboperes Aug 5, 2024
5503aee
Merge branch 'onboarding-language-domain-config' into onboarding-prof…
adhiamboperes Aug 6, 2024
0133cd0
Merge branch 'onboarding-language-domain-config' into onboarding-prof…
adhiamboperes Aug 22, 2024
d982d06
Merge branch 'onboarding-language-domain-config' into onboarding-prof…
adhiamboperes Aug 23, 2024
0fc0078
Fix self review issues
adhiamboperes Aug 23, 2024
e06d28f
Merge branch 'onboarding-language-domain-config' into onboarding-prof…
adhiamboperes Aug 23, 2024
6cf179f
Remove duplicate exemption
adhiamboperes Aug 23, 2024
1cc21bb
Remove leftover profile audiolanguage
adhiamboperes Aug 24, 2024
946c79b
Merge branch 'onboarding-language-domain-config' into onboarding-prof…
adhiamboperes Aug 27, 2024
ca334d6
Integrate classrooms and onboarding
adhiamboperes Aug 27, 2024
831fa1c
Fix Admin profile creation and onboarding
adhiamboperes Aug 27, 2024
b94a0f8
Add missing argument proto
adhiamboperes Aug 27, 2024
b3d6ee3
Update broken tests
adhiamboperes Aug 27, 2024
daea15f
Fix profile migration bugs
adhiamboperes Aug 28, 2024
0397de7
Fix failing audio language tests
adhiamboperes Sep 4, 2024
063b97a
Address general comments
adhiamboperes Sep 9, 2024
73105a0
Nit
adhiamboperes Sep 10, 2024
1a4d82d
Merge branch 'onboarding-language-domain-config' into onboarding-prof…
adhiamboperes Sep 10, 2024
0ceb9fc
Fix failing tests
adhiamboperes Sep 10, 2024
28d2742
Fix failing tests
adhiamboperes Sep 10, 2024
6ae7968
Merge branch 'onboarding-profile-domain-config' of github.com:oppia/o…
adhiamboperes Sep 10, 2024
6a5634e
remove unused import
adhiamboperes Sep 10, 2024
7d2c1a7
Refactor event log management
adhiamboperes Sep 11, 2024
0047916
Merge branch 'onboarding-language-domain-config' into onboarding-prof…
adhiamboperes Oct 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,24 @@ import org.oppia.android.app.activity.InjectableAutoLocalizedAppCompatActivity
import org.oppia.android.app.activity.route.ActivityRouter
import org.oppia.android.app.drawer.ExitProfileDialogFragment
import org.oppia.android.app.drawer.TAG_SWITCH_PROFILE_DIALOG
import org.oppia.android.app.home.ExitProfileListener
import org.oppia.android.app.home.RouteToRecentlyPlayedListener
import org.oppia.android.app.home.RouteToTopicListener
import org.oppia.android.app.home.RouteToTopicPlayStoryListener
import org.oppia.android.app.model.DestinationScreen
import org.oppia.android.app.model.ExitProfileDialogArguments
import org.oppia.android.app.model.HighlightItem
import org.oppia.android.app.model.ProfileId
import org.oppia.android.app.model.ProfileType
import org.oppia.android.app.model.RecentlyPlayedActivityParams
import org.oppia.android.app.model.RecentlyPlayedActivityTitle
import org.oppia.android.app.model.ScreenName.CLASSROOM_LIST_ACTIVITY
import org.oppia.android.app.topic.TopicActivity.Companion.createTopicActivityIntent
import org.oppia.android.app.topic.TopicActivity.Companion.createTopicPlayStoryActivityIntent
import org.oppia.android.app.translation.AppLanguageResourceHandler
import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.decorateWithScreenName
import org.oppia.android.util.platformparameter.EnableOnboardingFlowV2
import org.oppia.android.util.platformparameter.PlatformParameterValue
import org.oppia.android.util.profile.CurrentUserProfileIdIntentDecorator.decorateWithUserProfileId
import org.oppia.android.util.profile.CurrentUserProfileIdIntentDecorator.extractCurrentUserProfileId
import javax.inject.Inject
Expand All @@ -32,7 +36,8 @@ class ClassroomListActivity :
InjectableAutoLocalizedAppCompatActivity(),
RouteToTopicListener,
RouteToTopicPlayStoryListener,
RouteToRecentlyPlayedListener {
RouteToRecentlyPlayedListener,
ExitProfileListener {
@Inject
lateinit var classroomListActivityPresenter: ClassroomListActivityPresenter

Expand All @@ -44,6 +49,10 @@ class ClassroomListActivity :

private var internalProfileId: Int = -1

@Inject
@field:EnableOnboardingFlowV2
lateinit var enableOnboardingFlowV2: PlatformParameterValue<Boolean>

companion object {
/** Returns a new [Intent] to route to [ClassroomListActivity] for a specified [profileId]. */
fun createClassroomListActivity(context: Context, profileId: ProfileId?): Intent {
Expand All @@ -68,22 +77,6 @@ class ClassroomListActivity :
classroomListActivityPresenter.handleOnRestart()
}

override fun onBackPressed() {
val previousFragment =
supportFragmentManager.findFragmentByTag(TAG_SWITCH_PROFILE_DIALOG)
if (previousFragment != null) {
supportFragmentManager.beginTransaction().remove(previousFragment).commitNow()
}
val exitProfileDialogArguments =
ExitProfileDialogArguments
.newBuilder()
.setHighlightItem(HighlightItem.NONE)
.build()
val dialogFragment = ExitProfileDialogFragment
.newInstance(exitProfileDialogArguments = exitProfileDialogArguments)
dialogFragment.showNow(supportFragmentManager, TAG_SWITCH_PROFILE_DIALOG)
}

override fun routeToRecentlyPlayed(recentlyPlayedActivityTitle: RecentlyPlayedActivityTitle) {
val recentlyPlayedActivityParams =
RecentlyPlayedActivityParams
Expand Down Expand Up @@ -121,4 +114,24 @@ class ClassroomListActivity :
)
)
}

override fun exitProfile(profileType: ProfileType) {
val previousFragment =
supportFragmentManager.findFragmentByTag(TAG_SWITCH_PROFILE_DIALOG)
if (previousFragment != null) {
supportFragmentManager.beginTransaction().remove(previousFragment).commitNow()
}
val exitProfileDialogArguments =
ExitProfileDialogArguments
.newBuilder().apply {
if (enableOnboardingFlowV2.value) {
this.profileType = profileType
}
this.highlightItem = HighlightItem.NONE
}
.build()
val dialogFragment = ExitProfileDialogFragment
.newInstance(exitProfileDialogArguments = exitProfileDialogArguments)
dialogFragment.showNow(supportFragmentManager, TAG_SWITCH_PROFILE_DIALOG)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.oppia.android.app.classroom
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
Expand Down Expand Up @@ -37,6 +38,7 @@ import org.oppia.android.app.classroom.promotedlist.PromotedStoryList
import org.oppia.android.app.classroom.topiclist.AllTopicsHeaderText
import org.oppia.android.app.classroom.topiclist.TopicCard
import org.oppia.android.app.classroom.welcome.WelcomeText
import org.oppia.android.app.home.ExitProfileListener
import org.oppia.android.app.home.HomeItemViewModel
import org.oppia.android.app.home.RouteToTopicPlayStoryListener
import org.oppia.android.app.home.WelcomeViewModel
Expand All @@ -50,6 +52,9 @@ import org.oppia.android.app.model.AppStartupState
import org.oppia.android.app.model.ClassroomSummary
import org.oppia.android.app.model.LessonThumbnail
import org.oppia.android.app.model.LessonThumbnailGraphic
import org.oppia.android.app.model.Profile
import org.oppia.android.app.model.ProfileId
import org.oppia.android.app.model.ProfileType
import org.oppia.android.app.model.TopicSummary
import org.oppia.android.app.translation.AppLanguageResourceHandler
import org.oppia.android.app.utility.datetime.DateTimeUtil
Expand All @@ -66,6 +71,8 @@ import org.oppia.android.util.data.DataProviders.Companion.toLiveData
import org.oppia.android.util.locale.OppiaLocale
import org.oppia.android.util.parser.html.StoryHtmlParserEntityType
import org.oppia.android.util.parser.html.TopicHtmlParserEntityType
import org.oppia.android.util.platformparameter.EnableOnboardingFlowV2
import org.oppia.android.util.platformparameter.PlatformParameterValue
import org.oppia.android.util.profile.CurrentUserProfileIdIntentDecorator.extractCurrentUserProfileId
import javax.inject.Inject

Expand All @@ -88,8 +95,11 @@ class ClassroomListFragmentPresenter @Inject constructor(
private val machineLocale: OppiaLocale.MachineLocale,
private val appStartupStateController: AppStartupStateController,
private val analyticsController: AnalyticsController,
@EnableOnboardingFlowV2
private val enableOnboardingFlowV2: PlatformParameterValue<Boolean>
) {
private val routeToTopicPlayStoryListener = activity as RouteToTopicPlayStoryListener
private val exitProfileListener = activity as ExitProfileListener
private lateinit var binding: ClassroomListFragmentBinding
private lateinit var classroomListViewModel: ClassroomListViewModel
private var internalProfileId: Int = -1
Expand Down Expand Up @@ -134,7 +144,8 @@ class ClassroomListFragmentPresenter @Inject constructor(
sender: ObservableList<HomeItemViewModel>,
positionStart: Int,
itemCount: Int
) {}
) {
}

override fun onItemRangeInserted(
sender: ObservableList<HomeItemViewModel>,
Expand All @@ -149,17 +160,23 @@ class ClassroomListFragmentPresenter @Inject constructor(
fromPosition: Int,
toPosition: Int,
itemCount: Int
) {}
) {
}

override fun onItemRangeRemoved(
sender: ObservableList<HomeItemViewModel>,
positionStart: Int,
itemCount: Int
) {}
) {
}
}
)

logAppOnboardedEvent()
if (enableOnboardingFlowV2.value) {
subscribeToProfileResult(profileId)
} else {
logAppOnboardedEvent(profileId)
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to log this for all cases where the user successfully finishes the onboarding flow (there's a bug with it currently whereby it logs for every home screen, and we're looking into it).

It acts as a top-level app health metric, so please make sure to account for it with the new onboarding changes.

Copy link
Collaborator Author

@adhiamboperes adhiamboperes Sep 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

subscribeToProfileResult() > processProfileResult() > handleProfileOnboardingState(profile: Profile) ensures that all users who complete the new onboarding flow log a started_profile_onboarding and completed_profile_onboarding event. At the same time, the first profile on the app(supervisor or sole learner) will additionally log the app onboarding complete event(existing event).

In this PR, I have retained the legacy onboarding flow to only log the app onboarding event.

I implemented this per TDD, although it is different from your comment(sounds like we want all profiles to log all 3 events?). Could you please clarify the intended behaviour?

Copy link
Collaborator Author

@adhiamboperes adhiamboperes Sep 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seperately, I experienced the bug you mention, and given some context I may be able to pick up debugging from my prior attempt at solving the #5434 (closed due to reverting adjacent changes that may have triggered the bug for me).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per the discussion in the meeting, I left the implementation of the logAppOnboardedEvent as is.

}

return binding.root
}
Expand Down Expand Up @@ -265,7 +282,7 @@ class ClassroomListFragmentPresenter @Inject constructor(
}
}

private fun logAppOnboardedEvent() {
private fun logAppOnboardedEvent(profileId: ProfileId) {
val startupStateProvider = appStartupStateController.getAppStartupState()
val liveData = startupStateProvider.toLiveData()
liveData.observe(
Expand All @@ -274,7 +291,7 @@ class ClassroomListFragmentPresenter @Inject constructor(
override fun onChanged(startUpStateResult: AsyncResult<AppStartupState>?) {
when (startUpStateResult) {
null, is AsyncResult.Pending -> {
// Do nothing.
// Do nothing
}
is AsyncResult.Success -> {
liveData.removeObserver(this)
Expand All @@ -297,12 +314,71 @@ class ClassroomListFragmentPresenter @Inject constructor(
)
}

private fun subscribeToProfileResult(profileId: ProfileId) {
profileManagementController.getProfile(profileId).toLiveData().observe(fragment) {
processProfileResult(it)
}
}

private fun processProfileResult(result: AsyncResult<Profile>) {
when (result) {
is AsyncResult.Success -> {
val profile = result.value
handleProfileOnboardingState(profile)
handleBackPress(profile.profileType)
}
is AsyncResult.Failure -> {
oppiaLogger.e(
"ClassroomListFragment", "Failed to fetch profile with id:$profileId", result.error
)
Profile.getDefaultInstance()
}
is AsyncResult.Pending -> {
Profile.getDefaultInstance()
}
}
}

private fun handleProfileOnboardingState(profile: Profile) {
// App onboarding is completed by the first profile on the app(SOLE_LEARNER or SUPERVISOR),
// while profile onboarding is completed by each profile.
if (!profile.completedProfileOboarding) {
markProfileOnboardingEnded(profileId)
if (profile.profileType == ProfileType.SOLE_LEARNER ||
profile.profileType == ProfileType.SUPERVISOR
) {
appStartupStateController.markOnboardingFlowCompleted()
logAppOnboardedEvent(profileId)
}
}
}
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm quite uncomfortable, in general, with logging events in the app layer because its lifecycle is incredibly complex. It's very easily to accidentally duplicate logs (e.g. due to activity recreation, etc.). Can we log this in the domain layer, instead?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have moved this to the controller.


private fun markProfileOnboardingEnded(profileId: ProfileId) {
profileManagementController.markProfileOnboardingEnded(profileId)

analyticsController.logLowPriorityEvent(
oppiaLogger.createProfileOnboardingEndedContext(profileId),
profileId = profileId
)
}

private fun logHomeActivityEvent() {
analyticsController.logImportantEvent(
oppiaLogger.createOpenHomeContext(),
profileId
)
}

private fun handleBackPress(profileType: ProfileType) {
activity.onBackPressedDispatcher.addCallback(
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the profile provider changes multiple times, it will lead to multiple callbacks being registered. Does this code correctly handle that scenario?

fragment,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
exitProfileListener.exitProfile(profileType)
}
}
)
}
}

/** Adds a grid of items to a LazyListScope with specified arrangement and item content. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import org.oppia.android.app.fragment.FragmentComponentImpl
import org.oppia.android.app.fragment.InjectableDialogFragment
import org.oppia.android.app.model.ExitProfileDialogArguments
import org.oppia.android.app.model.HighlightItem
import org.oppia.android.app.model.ProfileType
import org.oppia.android.app.profile.ProfileChooserActivity
import org.oppia.android.util.extensions.getProto
import org.oppia.android.util.extensions.putProto
Expand Down Expand Up @@ -63,19 +64,26 @@ class ExitProfileDialogFragment : InjectableDialogFragment() {
else -> false
}

val soleLearnerProfile = exitProfileDialogArguments.profileType == ProfileType.SOLE_LEARNER

val alertDialog = AlertDialog
.Builder(ContextThemeWrapper(activity as Context, R.style.OppiaAlertDialogTheme))
.setMessage(R.string.home_activity_back_dialog_message)
.setNegativeButton(R.string.home_activity_back_dialog_cancel) { dialog, _ ->
dialog.dismiss()
}
.setPositiveButton(R.string.home_activity_back_dialog_exit) { _, _ ->
// TODO(#3641): Investigate on using finish instead of intent.
val intent = ProfileChooserActivity.createProfileChooserActivity(activity!!)
if (!restoreLastCheckedItem) {
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
if (soleLearnerProfile) {
requireActivity().finish()
} else {
// TODO(#3641): Investigate on using finish instead of intent.
val intent = ProfileChooserActivity.createProfileChooserActivity(requireActivity())
if (!restoreLastCheckedItem) {
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
}
requireActivity().startActivity(intent)
requireActivity().finish()
}
activity!!.startActivity(intent)
}
.create()
alertDialog.setCanceledOnTouchOutside(false)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.oppia.android.app.home

import org.oppia.android.app.model.ProfileType
/** Listener for when a user wishes to exit their profile. */
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: empty line above this please.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

interface ExitProfileListener {
/**
* Called when back press is clicked on the HomeScreen.
*
* A SOLE_LEARNER exits the app completely while other [ProfileType]s are routed to the
* [ProfileChooserActivity].
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest instead saying that routing behavior may change based on the profile type rather than specifying the actual implementation behavior (which seems a bit too specific at the interface level).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

*/
fun exitProfile(profileType: ProfileType)
}
Loading
Loading