1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 05:47:05 +09:00

DROID-1051 | Splash screen fixes (#2989)

DROID-1051 Splash | Fix | Fixed splash screen non-fatal crash
This commit is contained in:
Allan Quatermain 2023-03-10 13:56:35 +03:00 committed by GitHub
parent 9a68347cbe
commit 9ac64c79fa
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 87 additions and 266 deletions

View file

@ -11,6 +11,7 @@ import com.anytypeio.anytype.di.feature.CreateAccountModule
import com.anytypeio.anytype.di.feature.CreateBookmarkModule
import com.anytypeio.anytype.di.feature.CreateDataViewViewerModule
import com.anytypeio.anytype.di.feature.CreateObjectModule
import com.anytypeio.anytype.di.feature.DaggerSplashComponent
import com.anytypeio.anytype.di.feature.DebugSettingsModule
import com.anytypeio.anytype.di.feature.EditDataViewViewerModule
import com.anytypeio.anytype.di.feature.EditorSessionModule
@ -54,7 +55,6 @@ import com.anytypeio.anytype.di.feature.SelectCoverObjectSetModule
import com.anytypeio.anytype.di.feature.SelectSortRelationModule
import com.anytypeio.anytype.di.feature.SetupNewAccountModule
import com.anytypeio.anytype.di.feature.SetupSelectedAccountModule
import com.anytypeio.anytype.di.feature.SplashModule
import com.anytypeio.anytype.di.feature.StartLoginModule
import com.anytypeio.anytype.di.feature.TextBlockIconPickerModule
import com.anytypeio.anytype.di.feature.ViewerFilterModule
@ -166,10 +166,9 @@ class ComponentManager(
}
val splashLoginComponent = Component {
main
.splashComponentBuilder()
.module(SplashModule)
.build()
DaggerSplashComponent
.factory()
.create(findComponentDependencies())
}
val keychainPhraseComponent = Component {

View file

@ -25,7 +25,6 @@ import com.anytypeio.anytype.ui_settings.appearance.ThemeApplicatorImpl
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
import kotlinx.coroutines.Dispatchers
@Subcomponent(
modules = [MainEntryModule::class]

View file

@ -1,7 +1,9 @@
package com.anytypeio.anytype.di.feature
import androidx.lifecycle.ViewModelProvider
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.core_utils.di.scope.PerScreen
import com.anytypeio.anytype.di.common.ComponentDependencies
import com.anytypeio.anytype.domain.auth.interactor.CheckAuthorizationStatus
import com.anytypeio.anytype.domain.auth.interactor.GetLastOpenedObject
import com.anytypeio.anytype.domain.auth.interactor.LaunchAccount
@ -16,6 +18,7 @@ import com.anytypeio.anytype.domain.device.PathProvider
import com.anytypeio.anytype.domain.launch.GetDefaultPageType
import com.anytypeio.anytype.domain.launch.SetDefaultEditorType
import com.anytypeio.anytype.domain.misc.AppActionManager
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.page.CreateObject
import com.anytypeio.anytype.domain.search.ObjectTypesSubscriptionManager
import com.anytypeio.anytype.domain.search.RelationsSubscriptionManager
@ -23,18 +26,24 @@ import com.anytypeio.anytype.domain.templates.GetTemplates
import com.anytypeio.anytype.domain.workspace.WorkspaceManager
import com.anytypeio.anytype.presentation.splash.SplashViewModelFactory
import com.anytypeio.anytype.ui.splash.SplashFragment
import dagger.Binds
import dagger.Component
import dagger.Module
import dagger.Provides
import dagger.Subcomponent
@Component(
dependencies = [SplashDependencies::class],
modules = [
SplashModule::class,
SplashModule.Declarations::class
]
)
@PerScreen
@Subcomponent(modules = [SplashModule::class])
interface SplashSubComponent {
interface SplashComponent {
@Subcomponent.Builder
interface Builder {
fun build(): SplashSubComponent
fun module(module: SplashModule): Builder
@Component.Factory
interface Factory {
fun create(dependencies: SplashDependencies): SplashComponent
}
fun inject(fragment: SplashFragment)
@ -43,35 +52,6 @@ interface SplashSubComponent {
@Module
object SplashModule {
@JvmStatic
@PerScreen
@Provides
fun provideSplashViewModelFactory(
checkAuthorizationStatus: CheckAuthorizationStatus,
launchAccount: LaunchAccount,
launchWallet: LaunchWallet,
analytics: Analytics,
getLastOpenedObject: GetLastOpenedObject,
getDefaultPageType: GetDefaultPageType,
setDefaultEditorType: SetDefaultEditorType,
createObject: CreateObject,
appActionManager: AppActionManager,
relationsSubscriptionManager: RelationsSubscriptionManager,
objectTypesSubscriptionManager: ObjectTypesSubscriptionManager
): SplashViewModelFactory = SplashViewModelFactory(
checkAuthorizationStatus = checkAuthorizationStatus,
launchAccount = launchAccount,
launchWallet = launchWallet,
analytics = analytics,
getLastOpenedObject = getLastOpenedObject,
setDefaultEditorType = setDefaultEditorType,
getDefaultPageType = getDefaultPageType,
createObject = createObject,
appActionManager = appActionManager,
relationsSubscriptionManager = relationsSubscriptionManager,
objectTypesSubscriptionManager = objectTypesSubscriptionManager
)
@JvmStatic
@PerScreen
@Provides
@ -167,4 +147,28 @@ object SplashModule {
repo = repo,
dispatchers = dispatchers
)
@Module
interface Declarations {
@PerScreen
@Binds
fun bindViewModelFactory(factory: SplashViewModelFactory): ViewModelProvider.Factory
}
}
interface SplashDependencies : ComponentDependencies {
fun blockRepository(): BlockRepository
fun workspaceManager(): WorkspaceManager
fun urlBuilder(): UrlBuilder
fun analytics(): Analytics
fun appActionManager(): AppActionManager
fun relationsSubscriptionManager(): RelationsSubscriptionManager
fun objectTypesSubscriptionManager(): ObjectTypesSubscriptionManager
fun authRepository(): AuthRepository
fun pathProvider(): PathProvider
fun featuresConfigProvider(): FeaturesConfigProvider
fun configStorage(): ConfigStorage
fun userSettingsRepository(): UserSettingsRepository
fun dispatchers(): AppCoroutineDispatchers
}

View file

@ -14,12 +14,12 @@ import com.anytypeio.anytype.di.feature.KeychainPhraseSubComponent
import com.anytypeio.anytype.di.feature.LinkToObjectSubComponent
import com.anytypeio.anytype.di.feature.MainEntrySubComponent
import com.anytypeio.anytype.di.feature.MoveToSubComponent
import com.anytypeio.anytype.di.feature.SplashDependencies
import com.anytypeio.anytype.di.feature.ObjectSearchSubComponent
import com.anytypeio.anytype.di.feature.ObjectSetSubComponent
import com.anytypeio.anytype.di.feature.ObjectTypeChangeSubComponent
import com.anytypeio.anytype.di.feature.OtherSettingsSubComponent
import com.anytypeio.anytype.di.feature.PageNavigationSubComponent
import com.anytypeio.anytype.di.feature.SplashSubComponent
import com.anytypeio.anytype.di.feature.auth.DeletedAccountSubcomponent
import com.anytypeio.anytype.di.feature.home.HomeScreenDependencies
import com.anytypeio.anytype.di.feature.library.LibraryDependencies
@ -36,7 +36,6 @@ import com.anytypeio.anytype.di.feature.types.TypeCreationDependencies
import com.anytypeio.anytype.di.feature.types.TypeEditDependencies
import com.anytypeio.anytype.di.feature.types.TypeIconPickDependencies
import com.anytypeio.anytype.di.feature.wallpaper.WallpaperSelectSubComponent
import com.anytypeio.anytype.presentation.relations.RelationCreateFromLibraryViewModel
import com.anytypeio.anytype.ui.widgets.collection.CollectionDependencies
import dagger.Binds
import dagger.Component
@ -71,10 +70,10 @@ interface MainComponent :
TypeIconPickDependencies,
TypeEditDependencies,
RelationCreateFromLibraryDependencies,
RelationEditDependencies {
RelationEditDependencies,
SplashDependencies {
fun inject(app: AndroidApplication)
fun splashComponentBuilder(): SplashSubComponent.Builder
fun homeDashboardComponentBuilder(): HomeDashboardSubComponent.Builder
fun editorComponentBuilder(): EditorSubComponent.Builder
fun archiveComponentBuilder(): ArchiveSubComponent.Builder
@ -159,4 +158,9 @@ private abstract class ComponentDependenciesModule private constructor() {
@ComponentDependenciesKey(RelationEditDependencies::class)
abstract fun provideRelationEditDependencies(component: MainComponent): ComponentDependencies
@Binds
@IntoMap
@ComponentDependenciesKey(SplashDependencies::class)
abstract fun provideSplashDependencies(component: MainComponent): ComponentDependencies
}

View file

@ -1,7 +1,6 @@
package com.anytypeio.anytype.ui.splash
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@ -57,10 +56,6 @@ class SplashFragment : BaseFragment<FragmentSplashBinding>(R.layout.fragment_spl
private fun observe(command: SplashViewModel.Command) {
when (command) {
SplashViewModel.Command.CheckFirstInstall -> {
val isFirstInstall = isFirstInstall()
vm.onFirstInstallStatusChecked(isFirstInstall = isFirstInstall)
}
is SplashViewModel.Command.Error -> {
toast(command.msg)
binding.error.visible()
@ -128,25 +123,6 @@ class SplashFragment : BaseFragment<FragmentSplashBinding>(R.layout.fragment_spl
binding.version.text = "${BuildConfig.VERSION_NAME}-alpha"
}
private fun isFirstInstall(): Boolean {
return try {
val firstInstallTime: Long =
requireContext().packageManager.getPackageInfo(
BuildConfig.APPLICATION_ID,
0
).firstInstallTime
val lastUpdateTime: Long =
requireContext().packageManager.getPackageInfo(
BuildConfig.APPLICATION_ID,
0
).lastUpdateTime
firstInstallTime == lastUpdateTime
} catch (e: PackageManager.NameNotFoundException) {
Timber.e(e, "Error while checking first install time")
false
}
}
override fun injectDependencies() {
componentManager().splashLoginComponent.get().inject(this)
}

View file

@ -372,4 +372,12 @@ fun Fragment.shareFile(uri: Uri) {
}
Timber.e(e, "Error while opening file")
}
}
inline fun <T1 : Any, T2 : Any, R : Any> Pair<T1?, T2?>.letNotNull(block: (T1, T2) -> R): R? {
return if (first != null && second != null) {
block(first!!, second!!)
} else {
null
}
}

View file

@ -2,8 +2,6 @@ package com.anytypeio.anytype.domain.launch
import com.anytypeio.anytype.core_models.DVFilter
import com.anytypeio.anytype.core_models.DVFilterCondition
import com.anytypeio.anytype.core_models.DVSort
import com.anytypeio.anytype.core_models.DVSortType
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectTypeIds
import com.anytypeio.anytype.core_models.ObjectWrapper

View file

@ -9,10 +9,8 @@ import com.anytypeio.anytype.analytics.base.updateUserProperties
import com.anytypeio.anytype.analytics.props.Props
import com.anytypeio.anytype.analytics.props.UserProperty
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.MarketplaceObjectTypeIds.MARKETPLACE_OBJECT_TYPE_PREFIX
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.ObjectTypeIds.NOTE
import com.anytypeio.anytype.core_models.ObjectTypeIds.PAGE
import com.anytypeio.anytype.core_utils.ext.letNotNull
import com.anytypeio.anytype.domain.auth.interactor.CheckAuthorizationStatus
import com.anytypeio.anytype.domain.auth.interactor.GetLastOpenedObject
import com.anytypeio.anytype.domain.auth.interactor.LaunchAccount
@ -21,7 +19,6 @@ import com.anytypeio.anytype.domain.auth.model.AuthStatus
import com.anytypeio.anytype.domain.base.BaseUseCase
import com.anytypeio.anytype.domain.base.fold
import com.anytypeio.anytype.domain.launch.GetDefaultPageType
import com.anytypeio.anytype.domain.launch.SetDefaultEditorType
import com.anytypeio.anytype.domain.misc.AppActionManager
import com.anytypeio.anytype.domain.page.CreateObject
import com.anytypeio.anytype.domain.search.ObjectTypesSubscriptionManager
@ -44,7 +41,6 @@ class SplashViewModel(
private val launchAccount: LaunchAccount,
private val getLastOpenedObject: GetLastOpenedObject,
private val getDefaultPageType: GetDefaultPageType,
private val setDefaultEditorType: SetDefaultEditorType,
private val createObject: CreateObject,
private val appActionManager: AppActionManager,
private val relationsSubscriptionManager: RelationsSubscriptionManager,
@ -54,73 +50,7 @@ class SplashViewModel(
val commands = MutableSharedFlow<Command>(replay = 0)
init {
proceedWithUserSettings()
}
private fun proceedWithUserSettings() {
viewModelScope.launch {
getDefaultPageType.execute(Unit).fold(
onFailure = { e ->
Timber.e(e, "Error while getting default page type")
checkAuthorizationStatus()
},
onSuccess = { result ->
Timber.d("getDefaultPageType: ${result.type}")
val defaultObjectType = result.type
if (defaultObjectType == null) {
commands.emit(Command.CheckFirstInstall)
} else if (defaultObjectType.contains(MARKETPLACE_OBJECT_TYPE_PREFIX)) {
fallbackToNoteAsDefaultObjectType()
} else {
checkAuthorizationStatus()
}
}
)
}
}
fun onFirstInstallStatusChecked(isFirstInstall: Boolean) {
Timber.d("setDefaultUserSettings, isFirstInstall:[$isFirstInstall]")
val (typeId, typeName) = if (isFirstInstall) {
DEFAULT_TYPE_FIRST_INSTALL
} else {
DEFAULT_TYPE_UPDATE
}
proceedWithUpdatingDefaultObjectType(
typeId = typeId,
typeName = typeName
)
}
private fun fallbackToNoteAsDefaultObjectType() {
val (typeId, typeName) = DEFAULT_TYPE_FIRST_INSTALL
proceedWithUpdatingDefaultObjectType(
typeId = typeId,
typeName = typeName
)
}
private fun proceedWithUpdatingDefaultObjectType(
typeId: String,
typeName: String
) {
appActionManager.setup(
AppActionManager.Action.CreateNew(
type = typeId,
name = typeName
)
)
viewModelScope.launch {
val params = SetDefaultEditorType.Params(typeId, typeName)
Timber.d("Start to update Default Page Type:${params.type}")
setDefaultEditorType.invoke(params).process(
failure = {
Timber.e(it, "Error while setting default page type")
checkAuthorizationStatus()
},
success = { checkAuthorizationStatus() }
)
}
checkAuthorizationStatus()
}
private fun checkAuthorizationStatus() {
@ -171,7 +101,7 @@ class SplashViewModel(
val props = Props.empty()
sendEvent(startTime, openAccount, props)
proceedWithGlobalSubscriptions()
commands.emit(Command.CheckAppStartIntent)
setupShortcutsAndStartApp()
},
failure = { e ->
Timber.e(e, "Error while launching account")
@ -181,6 +111,27 @@ class SplashViewModel(
}
}
private fun setupShortcutsAndStartApp() {
viewModelScope.launch {
getDefaultPageType.execute(Unit).fold(
onSuccess = {
Pair(it.name, it.type).letNotNull { name, type ->
appActionManager.setup(
AppActionManager.Action.CreateNew(
type = type,
name = name
)
)
}
commands.emit(Command.CheckAppStartIntent)
},
onFailure = {
commands.emit(Command.CheckAppStartIntent)
}
)
}
}
private fun proceedWithGlobalSubscriptions() {
relationsSubscriptionManager.onStart()
objectTypesSubscriptionManager.onStart()
@ -262,7 +213,6 @@ class SplashViewModel(
}
sealed class Command {
object CheckFirstInstall : Command()
object NavigateToDashboard : Command()
object NavigateToWidgets : Command()
object NavigateToLogin : Command()
@ -274,7 +224,5 @@ class SplashViewModel(
companion object {
const val ERROR_MESSAGE = "An error occurred while starting account..."
val DEFAULT_TYPE_FIRST_INSTALL = Pair(NOTE, "Note")
val DEFAULT_TYPE_UPDATE = Pair(PAGE, "Page")
}
}

View file

@ -13,20 +13,20 @@ import com.anytypeio.anytype.domain.misc.AppActionManager
import com.anytypeio.anytype.domain.page.CreateObject
import com.anytypeio.anytype.domain.search.ObjectTypesSubscriptionManager
import com.anytypeio.anytype.domain.search.RelationsSubscriptionManager
import javax.inject.Inject
/**
* Created by Konstantin Ivanov
* email : ki@agileburo.com
* on 2019-10-21.
*/
class SplashViewModelFactory(
class SplashViewModelFactory @Inject constructor(
private val checkAuthorizationStatus: CheckAuthorizationStatus,
private val launchAccount: LaunchAccount,
private val launchWallet: LaunchWallet,
private val analytics: Analytics,
private val getLastOpenedObject: GetLastOpenedObject,
private val getDefaultPageType: GetDefaultPageType,
private val setDefaultEditorType: SetDefaultEditorType,
private val appActionManager: AppActionManager,
private val createObject: CreateObject,
private val relationsSubscriptionManager: RelationsSubscriptionManager,
@ -42,7 +42,6 @@ class SplashViewModelFactory(
analytics = analytics,
getLastOpenedObject = getLastOpenedObject,
getDefaultPageType = getDefaultPageType,
setDefaultEditorType = setDefaultEditorType,
appActionManager = appActionManager,
createObject = createObject,
relationsSubscriptionManager = relationsSubscriptionManager,

View file

@ -2,7 +2,6 @@ package com.anytypeio.anytype.presentation.splash
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.core_models.MarketplaceObjectTypeIds.MARKETPLACE_OBJECT_TYPE_PREFIX
import com.anytypeio.anytype.core_models.ObjectTypeIds
import com.anytypeio.anytype.domain.auth.interactor.CheckAuthorizationStatus
import com.anytypeio.anytype.domain.auth.interactor.GetLastOpenedObject
@ -14,15 +13,12 @@ import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.domain.base.Resultat
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.launch.GetDefaultPageType
import com.anytypeio.anytype.domain.launch.SetDefaultEditorType
import com.anytypeio.anytype.domain.misc.AppActionManager
import com.anytypeio.anytype.domain.page.CreateObject
import com.anytypeio.anytype.domain.search.ObjectTypesSubscriptionManager
import com.anytypeio.anytype.domain.search.RelationsSubscriptionManager
import com.anytypeio.anytype.presentation.util.CoroutinesTestRule
import com.anytypeio.anytype.test_utils.MockDataFactory
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
@ -36,7 +32,6 @@ import org.mockito.kotlin.eq
import org.mockito.kotlin.stub
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoInteractions
import org.mockito.kotlin.verifyNoMoreInteractions
class SplashViewModelTest {
@ -67,9 +62,6 @@ class SplashViewModelTest {
private lateinit var getLastOpenedObject: GetLastOpenedObject
@Mock
private lateinit var setDefaultEditorType: SetDefaultEditorType
@Mock
lateinit var createObject: CreateObject
@ -103,7 +95,6 @@ class SplashViewModelTest {
launchWallet = launchWallet,
analytics = analytics,
getLastOpenedObject = getLastOpenedObject,
setDefaultEditorType = setDefaultEditorType,
getDefaultPageType = getDefaultPageType,
createObject = createObject,
appActionManager = appActionManager,
@ -122,13 +113,11 @@ class SplashViewModelTest {
stubLaunchWallet()
stubLaunchAccount()
stubGetLastOpenedObject()
stubGetDefaultObjectType(null)
initViewModel()
runBlocking {
verify(checkAuthorizationStatus, times(0)).invoke(any(), any(), any())
verify(checkAuthorizationStatus, times(0)).invoke(any())
verify(checkAuthorizationStatus, times(1)).invoke(any())
verifyNoMoreInteractions(checkAuthorizationStatus)
}
}
@ -231,109 +220,6 @@ class SplashViewModelTest {
}
}
@Test
fun `should fallback to default object type if default object type contains deprecated prefix id`() =
runTest {
stubCheckAuthStatus(Either.Right(AuthStatus.AUTHORIZED))
stubLaunchWallet()
stubLaunchAccount()
stubGetLastOpenedObject()
stubGetDefaultObjectType(type = MARKETPLACE_OBJECT_TYPE_PREFIX + MockDataFactory.randomUuid())
initViewModel()
verify(setDefaultEditorType, times(1)).invoke(
SetDefaultEditorType.Params(
SplashViewModel.DEFAULT_TYPE_FIRST_INSTALL.first,
SplashViewModel.DEFAULT_TYPE_FIRST_INSTALL.second
)
)
}
@Test
fun `should not fallback to default object type if default object type does not contain deprecated prefix id`() =
runTest {
stubCheckAuthStatus(Either.Right(AuthStatus.AUTHORIZED))
stubLaunchWallet()
stubLaunchAccount()
stubGetLastOpenedObject()
stubGetDefaultObjectType(type = ObjectTypeIds.DEFAULT_OBJECT_TYPE_PREFIX + MockDataFactory.randomUuid())
initViewModel()
verifyNoInteractions(setDefaultEditorType)
}
//Todo can't mock Amplitude
// @Test
// fun `should emit appropriate navigation command if account is launched`() {
//
// val status = AuthStatus.AUTHORIZED
//
// val response = Either.Right(status)
//
//
// //Mockito.`when`(amplitude.setUserId("accountId", true)).
// stubCheckAuthStatus(response)
// stubLaunchAccount()
// stubLaunchWallet()
//
// vm.onResume()
//
// verify(amplitude, times(1)).setUserId("accountId", true)
//
// vm.navigation.test().assertValue { value ->
// value.peekContent() == AppNavigation.Command.StartDesktopFromSplash
// }
// }
// @Test
// fun `should emit appropriate navigation command if user is unauthorized`() {
//
// val status = AuthStatus.UNAUTHORIZED
//
// val response = Either.Right(status)
//
// stubCheckAuthStatus(response)
// stubGetLastOpenedObject()
//
// vm.onResume()
//
// vm.navigation.test().assertValue { value ->
// value.peekContent() == AppNavigation.Command.OpenStartLoginScreen
// }
// }
// @Test
// fun `should retry launching wallet after failed launch and emit error`() {
//
// // SETUP
//
// val status = AuthStatus.AUTHORIZED
//
// val response = Either.Right(status)
//
// val exception = Exception(MockDataFactory.randomString())
//
// stubCheckAuthStatus(response)
//
// stubLaunchWallet(response = Either.Left(exception))
//
// // TESTING
//
// val state = vm.state.test()
//
// state.assertNoValue()
//
// vm.onResume()
//
// state.assertValue { value -> value is ViewState.Error }
//
// runBlocking {
// verify(launchWallet, times(2)).invoke(any())
// }
// }
private fun stubCheckAuthStatus(response: Either.Right<AuthStatus>) {
checkAuthorizationStatus.stub {
onBlocking { invoke(eq(Unit)) } doReturn response