From b7826c02a3c32f9516554c74de02559c18a23e19 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 15 Jul 2022 17:17:00 +0200 Subject: [PATCH] Start SDK before handling permalink or sharing to the app. It also fixes a crash when trying to share to the app if there is no active session: `IncomingShareViewModel` injects the `session` in the constructor. --- .../vector/app/core/di/ActiveSessionSetter.kt | 7 ++- .../app/core/di/MavericksViewModelModule.kt | 6 +-- .../im/vector/app/features/MainActivity.kt | 23 +++++++-- .../app/features/link/LinkHandlerActivity.kt | 26 ++++++++++ .../app/features/share/IncomingShareAction.kt | 2 +- .../features/share/IncomingShareActivity.kt | 51 +++++++++++++++++-- .../features/share/IncomingShareFragment.kt | 19 +------ .../features/share/IncomingShareViewModel.kt | 4 +- .../app/features/start/StartAppAction.kt | 23 +++++++++ .../app/features/start/StartAppViewEvent.kt | 23 +++++++++ .../app/features/start/StartAppViewModel.kt} | 35 +++++-------- .../app/features/start/StartAppViewState.kt | 23 +++++++++ 12 files changed, 188 insertions(+), 54 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/start/StartAppAction.kt create mode 100644 vector/src/main/java/im/vector/app/features/start/StartAppViewEvent.kt rename vector/src/{debug/java/im/vector/app/features/MainViewModel.kt => main/java/im/vector/app/features/start/StartAppViewModel.kt} (58%) create mode 100644 vector/src/main/java/im/vector/app/features/start/StartAppViewState.kt diff --git a/vector/src/main/java/im/vector/app/core/di/ActiveSessionSetter.kt b/vector/src/main/java/im/vector/app/core/di/ActiveSessionSetter.kt index 59c2037cbe..09479a230f 100644 --- a/vector/src/main/java/im/vector/app/core/di/ActiveSessionSetter.kt +++ b/vector/src/main/java/im/vector/app/core/di/ActiveSessionSetter.kt @@ -26,12 +26,15 @@ class ActiveSessionSetter @Inject constructor( private val authenticationService: AuthenticationService, private val applicationContext: Context, ) { + fun shouldSetActionSession(): Boolean { + return authenticationService.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession() + } + fun tryToSetActiveSession(startSync: Boolean) { - if (authenticationService.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession()) { + if (shouldSetActionSession()) { val lastAuthenticatedSession = authenticationService.getLastAuthenticatedSession()!! activeSessionHolder.setActiveSession(lastAuthenticatedSession) lastAuthenticatedSession.configureAndStart(applicationContext, startSyncing = startSync) - activeSessionHolder.setActiveSession(lastAuthenticatedSession) } } } diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index 3cd8dc66e9..a5fa04f1b0 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -20,7 +20,6 @@ import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.multibindings.IntoMap -import im.vector.app.features.MainViewModel import im.vector.app.features.analytics.accountdata.AnalyticsAccountDataViewModel import im.vector.app.features.analytics.ui.consent.AnalyticsConsentViewModel import im.vector.app.features.auth.ReAuthViewModel @@ -112,6 +111,7 @@ import im.vector.app.features.spaces.manage.SpaceManageSharedViewModel import im.vector.app.features.spaces.people.SpacePeopleViewModel import im.vector.app.features.spaces.preview.SpacePreviewViewModel import im.vector.app.features.spaces.share.ShareSpaceViewModel +import im.vector.app.features.start.StartAppViewModel import im.vector.app.features.terms.ReviewTermsViewModel import im.vector.app.features.usercode.UserCodeSharedViewModel import im.vector.app.features.userdirectory.UserListViewModel @@ -486,8 +486,8 @@ interface MavericksViewModelModule { @Binds @IntoMap - @MavericksViewModelKey(MainViewModel::class) - fun mainViewModelFactory(factory: MainViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + @MavericksViewModelKey(StartAppViewModel::class) + fun startAppViewModelFactory(factory: StartAppViewModel.Factory): MavericksAssistedViewModelFactory<*, *> @Binds @IntoMap diff --git a/vector/src/main/java/im/vector/app/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt index c0af9f9e70..f160e77aa0 100644 --- a/vector/src/main/java/im/vector/app/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt @@ -46,6 +46,9 @@ import im.vector.app.features.popup.PopupAlertManager import im.vector.app.features.session.VectorSessionStore import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.signout.hard.SignedOutActivity +import im.vector.app.features.start.StartAppAction +import im.vector.app.features.start.StartAppViewEvent +import im.vector.app.features.start.StartAppViewModel import im.vector.app.features.themes.ActivityOtherThemes import im.vector.app.features.ui.UiStateRepository import kotlinx.coroutines.Dispatchers @@ -78,6 +81,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity companion object { private const val EXTRA_ARGS = "EXTRA_ARGS" private const val EXTRA_NEXT_INTENT = "EXTRA_NEXT_INTENT" + private const val EXTRA_INIT_SESSION = "EXTRA_INIT_SESSION" // Special action to clear cache and/or clear credentials fun restartApp(activity: Activity, args: MainActivityArgs) { @@ -88,6 +92,12 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity activity.startActivity(intent) } + fun getIntentToInitSession(activity: Activity): Intent { + val intent = Intent(activity, MainActivity::class.java) + intent.putExtra(EXTRA_INIT_SESSION, true) + return intent + } + fun getIntentWithNextIntent(context: Context, nextIntent: Intent): Intent { val intent = Intent(context, MainActivity::class.java) intent.putExtra(EXTRA_NEXT_INTENT, nextIntent) @@ -95,7 +105,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity } } - private val mainViewModel: MainViewModel by viewModel() + private val startAppViewModel: StartAppViewModel by viewModel() override fun getBinding() = ActivityMainBinding.inflate(layoutInflater) @@ -117,16 +127,16 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - mainViewModel.viewEvents.stream() + startAppViewModel.viewEvents.stream() .onEach(::handleViewEvents) .launchIn(lifecycleScope) - mainViewModel.handle(MainViewAction.StartApp) + startAppViewModel.handle(StartAppAction.StartApp) } - private fun handleViewEvents(event: MainViewEvent) { + private fun handleViewEvents(event: StartAppViewEvent) { when (event) { - MainViewEvent.AppStarted -> handleAppStarted() + StartAppViewEvent.AppStarted -> handleAppStarted() } } @@ -135,6 +145,9 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity // Start the next Activity val nextIntent = intent.getParcelableExtra(EXTRA_NEXT_INTENT) startIntentAndFinish(nextIntent) + } else if (intent.hasExtra(EXTRA_INIT_SESSION)) { + setResult(RESULT_OK) + finish() } else { args = parseArgs() if (args.clearCredentials || args.isUserLoggedOut || args.clearCache) { diff --git a/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt b/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt index 6de73cb20f..0bdec53f60 100644 --- a/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/link/LinkHandlerActivity.kt @@ -18,18 +18,23 @@ package im.vector.app.features.link import android.content.Intent import android.net.Uri +import android.os.Bundle import androidx.lifecycle.lifecycleScope +import com.airbnb.mvrx.viewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.error.ErrorFormatter +import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.utils.toast import im.vector.app.databinding.ActivityProgressBinding +import im.vector.app.features.MainActivity import im.vector.app.features.home.HomeActivity import im.vector.app.features.login.LoginConfig import im.vector.app.features.permalink.PermalinkHandler +import im.vector.app.features.start.StartAppViewModel import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.permalinks.PermalinkService import timber.log.Timber @@ -45,12 +50,33 @@ class LinkHandlerActivity : VectorBaseActivity() { @Inject lateinit var errorFormatter: ErrorFormatter @Inject lateinit var permalinkHandler: PermalinkHandler + private val startAppViewModel: StartAppViewModel by viewModel() + override fun getBinding() = ActivityProgressBinding.inflate(layoutInflater) override fun initUiAndData() { handleIntent() } + private val launcher = registerStartForActivityResult { + if (it.resultCode == RESULT_OK) { + handleIntent() + } else { + // User has pressed back on the MainActivity, so finish also this one. + finish() + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + if (startAppViewModel.shouldStartApp()) { + launcher.launch(MainActivity.getIntentToInitSession(this)) + } else { + handleIntent() + } + } + override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) handleIntent() diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareAction.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareAction.kt index 4e9f024b15..70be2b2b6d 100644 --- a/vector/src/main/java/im/vector/app/features/share/IncomingShareAction.kt +++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareAction.kt @@ -22,7 +22,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary sealed class IncomingShareAction : VectorViewModelAction { data class SelectRoom(val roomSummary: RoomSummary, val enableMultiSelect: Boolean) : IncomingShareAction() object ShareToSelectedRooms : IncomingShareAction() - data class ShareToRoom(val roomSummary: RoomSummary) : IncomingShareAction() + data class ShareToRoom(val roomId: String) : IncomingShareAction() data class ShareMedia(val keepOriginalSize: Boolean) : IncomingShareAction() data class FilterWith(val filter: String) : IncomingShareAction() data class UpdateSharedData(val sharedData: SharedData) : IncomingShareAction() diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareActivity.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareActivity.kt index 439d9b64fa..3d603e3f6a 100644 --- a/vector/src/main/java/im/vector/app/features/share/IncomingShareActivity.kt +++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareActivity.kt @@ -16,21 +16,66 @@ package im.vector.app.features.share +import android.content.Intent +import android.os.Bundle +import com.airbnb.mvrx.viewModel import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.extensions.addFragment +import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivitySimpleBinding +import im.vector.app.features.MainActivity +import im.vector.app.features.start.StartAppViewModel +import javax.inject.Inject @AndroidEntryPoint class IncomingShareActivity : VectorBaseActivity() { + private val startAppViewModel: StartAppViewModel by viewModel() + + @Inject lateinit var activeSessionHolder: ActiveSessionHolder + + private val launcher = registerStartForActivityResult { + if (it.resultCode == RESULT_OK) { + handleAppStarted() + } else { + // User has pressed back on the MainActivity, so finish also this one. + finish() + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + if (startAppViewModel.shouldStartApp()) { + launcher.launch(MainActivity.getIntentToInitSession(this)) + } else { + handleAppStarted() + } + } + override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater) override fun getCoordinatorLayout() = views.coordinatorLayout - override fun initUiAndData() { - if (isFirstCreation()) { - addFragment(views.simpleFragmentContainer, IncomingShareFragment::class.java) + private fun handleAppStarted() { + // If we are not logged in, stop the sharing process and open login screen. + // In the future, we might want to relaunch the sharing process after login. + if (!activeSessionHolder.hasActiveSession()) { + startLoginActivity() + } else { + if (isFirstCreation()) { + addFragment(views.simpleFragmentContainer, IncomingShareFragment::class.java) + } } } + + private fun startLoginActivity() { + navigator.openLogin( + context = this, + flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK + ) + finish() + } } diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt index 3f8923dd68..3e2ddc469c 100644 --- a/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt +++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareFragment.kt @@ -30,7 +30,6 @@ import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R -import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.registerStartForActivityResult @@ -40,7 +39,6 @@ import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.attachments.ShareIntentHandler import im.vector.app.features.attachments.preview.AttachmentsPreviewActivity import im.vector.app.features.attachments.preview.AttachmentsPreviewArgs -import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import javax.inject.Inject @@ -50,7 +48,6 @@ import javax.inject.Inject */ class IncomingShareFragment @Inject constructor( private val incomingShareController: IncomingShareController, - private val sessionHolder: ActiveSessionHolder, private val shareIntentHandler: ShareIntentHandler, ) : VectorBaseFragment(), @@ -63,12 +60,6 @@ class IncomingShareFragment @Inject constructor( } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - // If we are not logged in, stop the sharing process and open login screen. - // In the future, we might want to relaunch the sharing process after login. - if (!sessionHolder.hasActiveSession()) { - startLoginActivity() - return - } super.onViewCreated(view, savedInstanceState) setupRecyclerView() setupToolbar(views.incomingShareToolbar) @@ -88,7 +79,7 @@ class IncomingShareFragment @Inject constructor( // Direct share if (intent.hasExtra(Intent.EXTRA_SHORTCUT_ID)) { val roomId = intent.getStringExtra(Intent.EXTRA_SHORTCUT_ID)!! - sessionHolder.getSafeActiveSession()?.getRoomSummary(roomId)?.let { viewModel.handle(IncomingShareAction.ShareToRoom(it)) } + viewModel.handle(IncomingShareAction.ShareToRoom(roomId)) } isShareManaged } @@ -192,14 +183,6 @@ class IncomingShareFragment @Inject constructor( .show() } - private fun startLoginActivity() { - navigator.openLogin( - context = requireActivity(), - flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK - ) - requireActivity().finish() - } - override fun invalidate() = withState(viewModel) { views.sendShareButton.isVisible = it.isInMultiSelectionMode incomingShareController.setData(it) diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt index 1191fd04e8..85629ea150 100644 --- a/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt @@ -35,6 +35,7 @@ import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.flow.flow @@ -134,7 +135,8 @@ class IncomingShareViewModel @AssistedInject constructor( private fun handleShareToRoom(action: IncomingShareAction.ShareToRoom) = withState { state -> val sharedData = state.sharedData ?: return@withState - _viewEvents.post(IncomingShareViewEvents.ShareToRoom(action.roomSummary, sharedData, showAlert = false)) + val roomSummary = session.getRoomSummary(action.roomId) ?: return@withState + _viewEvents.post(IncomingShareViewEvents.ShareToRoom(roomSummary, sharedData, showAlert = false)) } private fun handleShareMediaToSelectedRooms(action: IncomingShareAction.ShareMedia) = withState { state -> diff --git a/vector/src/main/java/im/vector/app/features/start/StartAppAction.kt b/vector/src/main/java/im/vector/app/features/start/StartAppAction.kt new file mode 100644 index 0000000000..fffb124f12 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/start/StartAppAction.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.start + +import im.vector.app.core.platform.VectorViewModelAction + +sealed interface StartAppAction : VectorViewModelAction { + object StartApp : StartAppAction +} diff --git a/vector/src/main/java/im/vector/app/features/start/StartAppViewEvent.kt b/vector/src/main/java/im/vector/app/features/start/StartAppViewEvent.kt new file mode 100644 index 0000000000..9e185cbe04 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/start/StartAppViewEvent.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.start + +import im.vector.app.core.platform.VectorViewEvents + +sealed interface StartAppViewEvent : VectorViewEvents { + object AppStarted : StartAppViewEvent +} diff --git a/vector/src/debug/java/im/vector/app/features/MainViewModel.kt b/vector/src/main/java/im/vector/app/features/start/StartAppViewModel.kt similarity index 58% rename from vector/src/debug/java/im/vector/app/features/MainViewModel.kt rename to vector/src/main/java/im/vector/app/features/start/StartAppViewModel.kt index 51fdabebc8..eeec52283d 100644 --- a/vector/src/debug/java/im/vector/app/features/MainViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/start/StartAppViewModel.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.features +package im.vector.app.features.start import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted @@ -23,36 +23,29 @@ import dagger.assisted.AssistedInject import im.vector.app.core.di.ActiveSessionSetter import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory -import im.vector.app.core.platform.VectorDummyViewState -import im.vector.app.core.platform.VectorViewEvents import im.vector.app.core.platform.VectorViewModel -import im.vector.app.core.platform.VectorViewModelAction import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -sealed interface MainViewAction : VectorViewModelAction { - object StartApp : MainViewAction -} - -sealed interface MainViewEvent : VectorViewEvents { - object AppStarted : MainViewEvent -} - -class MainViewModel @AssistedInject constructor( - @Assisted val initialState: VectorDummyViewState, +class StartAppViewModel @AssistedInject constructor( + @Assisted val initialState: StartAppViewState, private val activeSessionSetter: ActiveSessionSetter, -) : VectorViewModel(initialState) { +) : VectorViewModel(initialState) { @AssistedFactory - interface Factory : MavericksAssistedViewModelFactory { - override fun create(initialState: VectorDummyViewState): MainViewModel + interface Factory : MavericksAssistedViewModelFactory { + override fun create(initialState: StartAppViewState): StartAppViewModel } - companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() - override fun handle(action: MainViewAction) { + fun shouldStartApp(): Boolean { + return activeSessionSetter.shouldSetActionSession() + } + + override fun handle(action: StartAppAction) { when (action) { - MainViewAction.StartApp -> handleStartApp() + StartAppAction.StartApp -> handleStartApp() } } @@ -60,7 +53,7 @@ class MainViewModel @AssistedInject constructor( viewModelScope.launch(Dispatchers.IO) { // This can take time because of DB migration(s), so do it in a background task. activeSessionSetter.tryToSetActiveSession(startSync = true) - _viewEvents.post(MainViewEvent.AppStarted) + _viewEvents.post(StartAppViewEvent.AppStarted) } } } diff --git a/vector/src/main/java/im/vector/app/features/start/StartAppViewState.kt b/vector/src/main/java/im/vector/app/features/start/StartAppViewState.kt new file mode 100644 index 0000000000..50418e90dc --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/start/StartAppViewState.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.start + +import com.airbnb.mvrx.MavericksState + +data class StartAppViewState( + val duration: Long = 0 +) : MavericksState