diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e251f5f8f..c2489a4f4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Change log for Android @Anytype app. +## Version 0.3.3 (WIP) + +### New features & enhancements 🚀 + +* App: Restoring the last opened object or the last opened set on application start (#1851) +* App: Drafts without any history will be deleted after close (#1833) +* Editor | Drag & drop (position above, below or inside) (#1848) +* Sets | Grid as fallback view: views not supported on Android can be seen as grid (#1850) + +### Design & UX 🔳 + +### Fixes & tech 🚒 + + + ## Version 0.3.2 ### New features & enhancements 🚀 diff --git a/app/src/androidTest/java/com/anytypeio/anytype/features/sets/dv/TestObjectSetSetup.kt b/app/src/androidTest/java/com/anytypeio/anytype/features/sets/dv/TestObjectSetSetup.kt index 98eee51e45..dac73de521 100644 --- a/app/src/androidTest/java/com/anytypeio/anytype/features/sets/dv/TestObjectSetSetup.kt +++ b/app/src/androidTest/java/com/anytypeio/anytype/features/sets/dv/TestObjectSetSetup.kt @@ -6,6 +6,7 @@ import androidx.fragment.app.testing.launchFragmentInContainer import com.anytypeio.anytype.R import com.anytypeio.anytype.analytics.base.Analytics import com.anytypeio.anytype.core_models.* +import com.anytypeio.anytype.domain.auth.repo.AuthRepository import com.anytypeio.anytype.domain.block.interactor.UpdateText import com.anytypeio.anytype.domain.block.repo.BlockRepository import com.anytypeio.anytype.domain.config.Gateway @@ -46,6 +47,10 @@ abstract class TestObjectSetSetup { @Mock lateinit var repo: BlockRepository + + @Mock + lateinit var auth: AuthRepository + @Mock lateinit var gateway: Gateway @Mock @@ -88,7 +93,7 @@ abstract class TestObjectSetSetup { addDataViewRelation = AddNewRelationToDataView(repo) updateText = UpdateText(repo) - openObjectSet = OpenObjectSet(repo) + openObjectSet = OpenObjectSet(repo, auth) createDataViewRecord = CreateDataViewRecord(repo) updateDataViewRecord = UpdateDataViewRecord(repo) updateDataViewViewer = UpdateDataViewViewer(repo) diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/DashboardDi.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/DashboardDi.kt index 6dce89596f..204f357d5b 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/DashboardDi.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/DashboardDi.kt @@ -4,6 +4,7 @@ import com.anytypeio.anytype.analytics.base.Analytics import com.anytypeio.anytype.core_utils.di.scope.PerScreen import com.anytypeio.anytype.domain.`object`.ObjectTypesProvider import com.anytypeio.anytype.domain.auth.interactor.GetProfile +import com.anytypeio.anytype.domain.auth.repo.AuthRepository import com.anytypeio.anytype.domain.block.interactor.Move import com.anytypeio.anytype.domain.block.repo.BlockRepository import com.anytypeio.anytype.domain.config.GetConfig @@ -87,9 +88,11 @@ object HomeDashboardModule { @Provides @PerScreen fun provideOpenDashboardUseCase( - repo: BlockRepository + repo: BlockRepository, + auth: AuthRepository ): OpenDashboard = OpenDashboard( - repo = repo + repo = repo, + auth = auth ) @JvmStatic diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/EditorDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/EditorDI.kt index dd1012431d..d2a7020327 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/EditorDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/EditorDI.kt @@ -12,6 +12,7 @@ import com.anytypeio.anytype.di.feature.relations.RelationCreateFromScratchForOb import com.anytypeio.anytype.di.feature.relations.RelationCreateFromScratchForObjectSubComponent import com.anytypeio.anytype.domain.`object`.ObjectTypesProvider import com.anytypeio.anytype.domain.`object`.UpdateDetail +import com.anytypeio.anytype.domain.auth.repo.AuthRepository import com.anytypeio.anytype.domain.block.UpdateDivider import com.anytypeio.anytype.domain.block.interactor.* import com.anytypeio.anytype.domain.block.interactor.sets.GetObjectTypes @@ -297,9 +298,11 @@ object EditorUseCaseModule { @Provides @PerScreen fun provideOpenPageUseCase( - repo: BlockRepository + repo: BlockRepository, + auth: AuthRepository ): OpenPage = OpenPage( - repo = repo + repo = repo, + auth = auth ) @JvmStatic diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectSetDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectSetDI.kt index 1fd422433e..13e412807c 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectSetDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/ObjectSetDI.kt @@ -13,6 +13,7 @@ import com.anytypeio.anytype.di.feature.sets.CreateFilterSubComponent import com.anytypeio.anytype.di.feature.sets.ModifyFilterSubComponent import com.anytypeio.anytype.di.feature.sets.SelectFilterRelationSubComponent import com.anytypeio.anytype.domain.`object`.UpdateDetail +import com.anytypeio.anytype.domain.auth.repo.AuthRepository import com.anytypeio.anytype.domain.block.interactor.UpdateText import com.anytypeio.anytype.domain.block.repo.BlockRepository import com.anytypeio.anytype.domain.dataview.interactor.* @@ -118,7 +119,10 @@ object ObjectSetModule { @JvmStatic @Provides @PerScreen - fun provideOpenObjectSetUseCase(repo: BlockRepository): OpenObjectSet = OpenObjectSet(repo) + fun provideOpenObjectSetUseCase( + repo: BlockRepository, + auth: AuthRepository + ): OpenObjectSet = OpenObjectSet(repo = repo, auth = auth) @JvmStatic @Provides diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/SplashDi.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/SplashDi.kt index ffaf9e08f7..81f977d720 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/SplashDi.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/SplashDi.kt @@ -4,6 +4,7 @@ import com.anytypeio.anytype.analytics.base.Analytics import com.anytypeio.anytype.core_utils.di.scope.PerScreen import com.anytypeio.anytype.domain.`object`.ObjectTypesProvider import com.anytypeio.anytype.domain.auth.interactor.CheckAuthorizationStatus +import com.anytypeio.anytype.domain.auth.interactor.GetLastOpenedObject import com.anytypeio.anytype.domain.auth.interactor.LaunchAccount import com.anytypeio.anytype.domain.auth.interactor.LaunchWallet import com.anytypeio.anytype.domain.auth.repo.AuthRepository @@ -13,6 +14,7 @@ import com.anytypeio.anytype.domain.config.FlavourConfigProvider import com.anytypeio.anytype.domain.device.PathProvider import com.anytypeio.anytype.presentation.splash.SplashViewModelFactory import com.anytypeio.anytype.ui.splash.SplashFragment +import com.squareup.wire.get import dagger.Module import dagger.Provides import dagger.Subcomponent @@ -47,13 +49,15 @@ object SplashModule { launchAccount: LaunchAccount, launchWallet: LaunchWallet, analytics: Analytics, - storeObjectTypes: StoreObjectTypes + storeObjectTypes: StoreObjectTypes, + getLastOpenedObject: GetLastOpenedObject ): SplashViewModelFactory = SplashViewModelFactory( checkAuthorizationStatus = checkAuthorizationStatus, launchAccount = launchAccount, launchWallet = launchWallet, analytics = analytics, - storeObjectTypes = storeObjectTypes + storeObjectTypes = storeObjectTypes, + getLastOpenedObject = getLastOpenedObject ) @JvmStatic @@ -97,4 +101,15 @@ object SplashModule { repo: BlockRepository, objectTypesProvider: ObjectTypesProvider ) : StoreObjectTypes = StoreObjectTypes(repo, objectTypesProvider) + + @JvmStatic + @Provides + @PerScreen + fun getLastOpenedObject( + repo: BlockRepository, + auth: AuthRepository + ) : GetLastOpenedObject = GetLastOpenedObject( + authRepo = auth, + blockRepo = repo + ) } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/navigation/Navigator.kt b/app/src/main/java/com/anytypeio/anytype/navigation/Navigator.kt index cffa3e94d2..b36b881637 100644 --- a/app/src/main/java/com/anytypeio/anytype/navigation/Navigator.kt +++ b/app/src/main/java/com/anytypeio/anytype/navigation/Navigator.kt @@ -105,6 +105,20 @@ class Navigator : AppNavigation { ) } + override fun launchObjectFromSplash(id: Id) { + navController?.navigate( + R.id.action_splashScreen_to_objectScreen, + bundleOf(EditorFragment.ID_KEY to id), + ) + } + + override fun launchObjectSetFromSplash(id: Id) { + navController?.navigate( + R.id.action_splashScreen_to_objectSetScreen, + bundleOf(ObjectSetFragment.CONTEXT_ID_KEY to id), + ) + } + override fun openKeychainScreen() { navController?.navigate(R.id.action_open_keychain) } @@ -149,11 +163,17 @@ class Navigator : AppNavigation { } override fun exit() { - navController?.popBackStack() + val popped = navController?.popBackStack() + if (popped == false) { + navController?.navigate(R.id.desktopScreen) + } } override fun exitToDesktop() { - navController?.popBackStack(R.id.desktopScreen, false) + val popped = navController?.popBackStack(R.id.desktopScreen, false) + if (popped == false) { + navController?.navigate(R.id.desktopScreen) + } } override fun openDebugSettings() { diff --git a/app/src/main/java/com/anytypeio/anytype/ui/base/NavigationFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/base/NavigationFragment.kt index cd51cf5da1..d493d554f4 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/base/NavigationFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/base/NavigationFragment.kt @@ -42,6 +42,8 @@ abstract class NavigationFragment( is Command.OpenObjectSet -> navigation.openObjectSet(command.target) is Command.LaunchObjectSet -> navigation.launchObjectSet(command.target) is Command.LaunchDocument -> navigation.launchDocument(command.id) + is Command.LaunchObjectFromSplash -> navigation.launchObjectFromSplash(command.target) + is Command.LaunchObjectSetFromSplash -> navigation.launchObjectSetFromSplash(command.target) is Command.OpenDatabaseViewAddView -> navigation.openDatabaseViewAddView() is Command.OpenEditDatabase -> navigation.openEditDatabase() is Command.OpenKeychainScreen -> navigation.openKeychainScreen() diff --git a/app/src/main/res/navigation/graph.xml b/app/src/main/res/navigation/graph.xml index b0e16943ea..e5627803a0 100644 --- a/app/src/main/res/navigation/graph.xml +++ b/app/src/main/res/navigation/graph.xml @@ -206,6 +206,24 @@ app:popExitAnim="@anim/nav_default_pop_exit_anim" app:popUpTo="@+id/splashScreen" app:popUpToInclusive="true" /> + + suspend fun setCurrentAccount(id: String) + + suspend fun saveLastOpenedObject(id: Id) + suspend fun getLastOpenedObject() : Id? + suspend fun clearLastOpenedObject() } \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthCacheDataStore.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthCacheDataStore.kt index 8d9b1cabb8..4a1f74ab5f 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthCacheDataStore.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthCacheDataStore.kt @@ -1,5 +1,7 @@ package com.anytypeio.anytype.data.auth.repo +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.data.auth.model.AccountEntity import com.anytypeio.anytype.data.auth.model.FlavourConfigEntity import com.anytypeio.anytype.data.auth.model.WalletEntity @@ -69,4 +71,8 @@ class AuthCacheDataStore(private val cache: AuthCache) : AuthDataStore { override suspend fun getVersion(): String { throw UnsupportedOperationException() } + + override suspend fun saveLastOpenedObject(id: Id) { cache.saveLastOpenedObject(id) } + override suspend fun getLastOpenedObject(): Id? = cache.getLastOpenedObject() + override suspend fun clearLastOpenedObject() { cache.clearLastOpenedObject() } } \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataRepository.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataRepository.kt index 56660cf57d..0e4d5c47b7 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataRepository.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataRepository.kt @@ -7,6 +7,8 @@ import com.anytypeio.anytype.domain.auth.model.Account import com.anytypeio.anytype.domain.auth.model.Wallet import com.anytypeio.anytype.domain.auth.repo.AuthRepository import com.anytypeio.anytype.core_models.FlavourConfig +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.ObjectType import kotlinx.coroutines.flow.map class AuthDataRepository( @@ -77,4 +79,8 @@ class AuthDataRepository( } override suspend fun getVersion(): String = factory.remote.getVersion() + + override suspend fun saveLastOpenedObjectId(id: Id) { factory.cache.saveLastOpenedObject(id) } + override suspend fun getLastOpenedObjectId(): Id? = factory.cache.getLastOpenedObject() + override suspend fun clearLastOpenedObject() { factory.cache.clearLastOpenedObject() } } \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataStore.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataStore.kt index eb55126f3a..c649066aaa 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataStore.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataStore.kt @@ -1,5 +1,7 @@ package com.anytypeio.anytype.data.auth.repo +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.data.auth.model.AccountEntity import com.anytypeio.anytype.data.auth.model.FlavourConfigEntity import com.anytypeio.anytype.data.auth.model.WalletEntity @@ -33,4 +35,8 @@ interface AuthDataStore { suspend fun setCurrentAccount(id: String) suspend fun getVersion(): String + + suspend fun saveLastOpenedObject(id: Id) + suspend fun getLastOpenedObject() : Id? + suspend fun clearLastOpenedObject() } \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthRemoteDataStore.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthRemoteDataStore.kt index 6900aa3a50..29f8be1df1 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthRemoteDataStore.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthRemoteDataStore.kt @@ -1,5 +1,7 @@ package com.anytypeio.anytype.data.auth.repo +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.data.auth.model.AccountEntity import com.anytypeio.anytype.data.auth.model.WalletEntity @@ -71,4 +73,16 @@ class AuthRemoteDataStore( } override suspend fun getVersion(): String = authRemote.getVersion() + + override suspend fun saveLastOpenedObject(id: Id) { + throw UnsupportedOperationException() + } + + override suspend fun getLastOpenedObject(): Id? { + throw UnsupportedOperationException() + } + + override suspend fun clearLastOpenedObject() { + throw UnsupportedOperationException() + } } \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/ClearLastOpenedObject.kt b/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/ClearLastOpenedObject.kt new file mode 100644 index 0000000000..dd6b8c3eec --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/ClearLastOpenedObject.kt @@ -0,0 +1,15 @@ +package com.anytypeio.anytype.domain.auth.interactor + +import com.anytypeio.anytype.domain.auth.repo.AuthRepository +import com.anytypeio.anytype.domain.base.BaseUseCase + +/** + * Use case for clearing last open object's id from user session. + * @see GetLastOpenedObject + * @see SaveLastOpenedObject + */ +class ClearLastOpenedObject( + private val repo: AuthRepository +) : BaseUseCase() { + override suspend fun run(params: None) = safe { repo.clearLastOpenedObject() } +} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/GetLastOpenedObject.kt b/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/GetLastOpenedObject.kt new file mode 100644 index 0000000000..c6d1a2f4c5 --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/GetLastOpenedObject.kt @@ -0,0 +1,66 @@ +package com.anytypeio.anytype.domain.auth.interactor + +import com.anytypeio.anytype.core_models.Block +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.ObjectWrapper +import com.anytypeio.anytype.core_models.Relations +import com.anytypeio.anytype.domain.auth.repo.AuthRepository +import com.anytypeio.anytype.domain.base.BaseUseCase +import com.anytypeio.anytype.domain.block.repo.BlockRepository + +/** + * Use case for fetching last open object's id for restoring user session. + * @see SaveLastOpenedObject + * @see ClearLastOpenedObject + */ +class GetLastOpenedObject( + private val authRepo: AuthRepository, + private val blockRepo: BlockRepository +) : BaseUseCase() { + + override suspend fun run(params: None) = safe { + val lastOpenObjectId = authRepo.getLastOpenedObjectId() + if (lastOpenObjectId == null) { + Response.Empty + } else { + val searchResults = blockRepo.searchObjects( + offset = 0, + limit = 1, + sorts = emptyList(), + filters = listOf( + Block.Content.DataView.Filter( + relationKey = Relations.ID, + condition = Block.Content.DataView.Filter.Condition.EQUAL, + operator = Block.Content.DataView.Filter.Operator.AND, + value = lastOpenObjectId + ) + ), + fulltext = "" + ) + val wrappedObjects = searchResults.map { ObjectWrapper.Basic(it) } + val lastOpenedObject = wrappedObjects.firstOrNull { it.id == lastOpenObjectId } + if (lastOpenedObject != null) { + Response.Success(lastOpenedObject) + } else { + Response.NotFound(lastOpenObjectId) + } + } + } + + sealed class Response { + /** + * There was no information about the last opened object. + */ + object Empty : Response() + + /** + * The last opened object could not be found. It might habe been deleted. + */ + data class NotFound(val id: Id) : Response() + + /** + * The last opened object has been found. + */ + data class Success(val obj: ObjectWrapper.Basic) : Response() + } +} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/SaveLastOpenedObject.kt b/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/SaveLastOpenedObject.kt new file mode 100644 index 0000000000..fedd9a25ef --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/SaveLastOpenedObject.kt @@ -0,0 +1,16 @@ +package com.anytypeio.anytype.domain.auth.interactor + +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.domain.auth.repo.AuthRepository +import com.anytypeio.anytype.domain.base.BaseUseCase + +/** + * Use case for saving last open object's id for restoring user session. + * @see GetLastOpenedObject + * @see ClearLastOpenedObject + */ +class SaveLastOpenedObject( + private val repo: AuthRepository +) : BaseUseCase() { + override suspend fun run(params: Id) = safe { repo.saveLastOpenedObjectId(params) } +} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/auth/repo/AuthRepository.kt b/domain/src/main/java/com/anytypeio/anytype/domain/auth/repo/AuthRepository.kt index 94bb839247..4e5f34ae8c 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/auth/repo/AuthRepository.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/auth/repo/AuthRepository.kt @@ -3,6 +3,8 @@ package com.anytypeio.anytype.domain.auth.repo import com.anytypeio.anytype.domain.auth.model.Account import com.anytypeio.anytype.domain.auth.model.Wallet import com.anytypeio.anytype.core_models.FlavourConfig +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.ObjectType import kotlinx.coroutines.flow.Flow interface AuthRepository { @@ -49,4 +51,8 @@ interface AuthRepository { suspend fun setCurrentAccount(id: String) suspend fun getVersion(): String + + suspend fun saveLastOpenedObjectId(id: Id) + suspend fun getLastOpenedObjectId() : Id? + suspend fun clearLastOpenedObject() } \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/dashboard/interactor/OpenDashboard.kt b/domain/src/main/java/com/anytypeio/anytype/domain/dashboard/interactor/OpenDashboard.kt index 84d6273e69..ef61064b0a 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/dashboard/interactor/OpenDashboard.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/dashboard/interactor/OpenDashboard.kt @@ -4,6 +4,7 @@ import com.anytypeio.anytype.domain.base.BaseUseCase import com.anytypeio.anytype.domain.base.Either import com.anytypeio.anytype.domain.block.repo.BlockRepository import com.anytypeio.anytype.core_models.Payload +import com.anytypeio.anytype.domain.auth.repo.AuthRepository /** * Use-case for opening a dashboard by sending a special request. @@ -11,7 +12,8 @@ import com.anytypeio.anytype.core_models.Payload * @property repo */ class OpenDashboard( - private val repo: BlockRepository + private val repo: BlockRepository, + private val auth: AuthRepository, ) : BaseUseCase() { override suspend fun run(params: Param?) = try { @@ -20,7 +22,9 @@ class OpenDashboard( contextId = params.contextId, id = params.id ).let { - Either.Right(it) + Either.Right(it).also { + auth.clearLastOpenedObject() + } } else { repo.getConfig().let { config -> @@ -28,7 +32,9 @@ class OpenDashboard( contextId = config.home, id = config.home ).let { - Either.Right(it) + Either.Right(it).also { + auth.clearLastOpenedObject() + } } } } diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/page/OpenPage.kt b/domain/src/main/java/com/anytypeio/anytype/domain/page/OpenPage.kt index 13b5b241d9..8bd1811728 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/page/OpenPage.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/page/OpenPage.kt @@ -4,12 +4,18 @@ import com.anytypeio.anytype.domain.base.BaseUseCase import com.anytypeio.anytype.domain.base.Result import com.anytypeio.anytype.domain.block.repo.BlockRepository import com.anytypeio.anytype.core_models.Payload +import com.anytypeio.anytype.domain.auth.repo.AuthRepository open class OpenPage( - private val repo: BlockRepository + private val repo: BlockRepository, + private val auth: AuthRepository ) : BaseUseCase, OpenPage.Params>() { - override suspend fun run(params: Params) = safe { repo.openPage(params.id) } + override suspend fun run(params: Params) = safe { + repo.openPage(params.id).also { + auth.saveLastOpenedObjectId(params.id) + } + } /** * @property id page's id diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/sets/OpenObjectSet.kt b/domain/src/main/java/com/anytypeio/anytype/domain/sets/OpenObjectSet.kt index 8c7a03f85b..4d31990b93 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/sets/OpenObjectSet.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/sets/OpenObjectSet.kt @@ -2,9 +2,17 @@ package com.anytypeio.anytype.domain.sets import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.Payload +import com.anytypeio.anytype.domain.auth.repo.AuthRepository import com.anytypeio.anytype.domain.base.BaseUseCase import com.anytypeio.anytype.domain.block.repo.BlockRepository -class OpenObjectSet(private val repo: BlockRepository) : BaseUseCase() { - override suspend fun run(params: Id) = safe { repo.openObjectSet(params) } +class OpenObjectSet( + private val repo: BlockRepository, + private val auth: AuthRepository +) : BaseUseCase() { + override suspend fun run(params: Id) = safe { + repo.openObjectSet(params).also { + auth.saveLastOpenedObjectId(params) + } + } } \ No newline at end of file diff --git a/domain/src/test/java/com/anytypeio/anytype/domain/dashboard/OpenDashboardTest.kt b/domain/src/test/java/com/anytypeio/anytype/domain/dashboard/OpenDashboardTest.kt index a1d305d4a5..af9f0728df 100644 --- a/domain/src/test/java/com/anytypeio/anytype/domain/dashboard/OpenDashboardTest.kt +++ b/domain/src/test/java/com/anytypeio/anytype/domain/dashboard/OpenDashboardTest.kt @@ -4,6 +4,7 @@ import com.anytypeio.anytype.domain.block.repo.BlockRepository import com.anytypeio.anytype.core_models.CoroutineTestRule import com.anytypeio.anytype.domain.common.MockDataFactory import com.anytypeio.anytype.core_models.Config +import com.anytypeio.anytype.domain.auth.repo.AuthRepository import com.anytypeio.anytype.domain.dashboard.interactor.OpenDashboard import com.nhaarman.mockitokotlin2.* import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -23,12 +24,15 @@ class OpenDashboardTest { @Mock lateinit var repo: BlockRepository + @Mock + lateinit var auth: AuthRepository + private lateinit var usecase: OpenDashboard @Before fun setup() { MockitoAnnotations.initMocks(this) - usecase = OpenDashboard(repo = repo) + usecase = OpenDashboard(repo = repo, auth = auth) } @Test diff --git a/persistence/src/main/java/com/anytypeio/anytype/persistence/repo/DefaultAuthCache.kt b/persistence/src/main/java/com/anytypeio/anytype/persistence/repo/DefaultAuthCache.kt index 0760fa0745..7dd8478455 100644 --- a/persistence/src/main/java/com/anytypeio/anytype/persistence/repo/DefaultAuthCache.kt +++ b/persistence/src/main/java/com/anytypeio/anytype/persistence/repo/DefaultAuthCache.kt @@ -74,6 +74,7 @@ class DefaultAuthCache( encryptedPrefs .edit() .remove(MNEMONIC_KEY) + .remove(LAST_OPENED_OBJECT_KEY) .remove(CURRENT_ACCOUNT_ID_KEY) .apply() } @@ -88,8 +89,21 @@ class DefaultAuthCache( encryptedPrefs.edit().putString(CURRENT_ACCOUNT_ID_KEY, id).apply() } + override suspend fun saveLastOpenedObject(id: String) { + encryptedPrefs.edit().putString(LAST_OPENED_OBJECT_KEY, id).apply() + } + + override suspend fun getLastOpenedObject(): String? { + return encryptedPrefs.getString(LAST_OPENED_OBJECT_KEY, null) + } + + override suspend fun clearLastOpenedObject() { + encryptedPrefs.edit().remove(LAST_OPENED_OBJECT_KEY).apply() + } + companion object { const val MNEMONIC_KEY = "mnemonic" + const val LAST_OPENED_OBJECT_KEY = "last_opened_object" const val CURRENT_ACCOUNT_ID_KEY = "current_account" } } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt index 0172f6f64f..844887295f 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt @@ -134,7 +134,7 @@ class EditorViewModel( private val updateDetail: UpdateDetail, private val getCompatibleObjectTypes: GetCompatibleObjectTypes, private val objectTypesProvider: ObjectTypesProvider, - private val searchObjects: SearchObjects + private val searchObjects: SearchObjects, ) : ViewStateViewModel(), SupportNavigation>, SupportCommand, diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/AppNavigation.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/AppNavigation.kt index d7f36b37b4..89daecb5a3 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/AppNavigation.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/navigation/AppNavigation.kt @@ -24,6 +24,8 @@ interface AppNavigation { fun openDocument(id: String, editorSettings: EditorSettings?) fun launchDocument(id: String) + fun launchObjectFromSplash(id: Id) + fun launchObjectSetFromSplash(id: Id) fun launchObjectSet(id: Id) fun startDesktopFromSplash() @@ -65,6 +67,8 @@ interface AppNavigation { data class OpenObject(val id: String, val editorSettings: EditorSettings? = null) : Command() data class LaunchDocument(val id: String) : Command() + data class LaunchObjectFromSplash(val target: Id) : Command() + data class LaunchObjectSetFromSplash(val target: Id) : Command() object OpenProfile : Command() object OpenKeychainScreen : Command() diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModel.kt index 7c40720d5e..80dd010960 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModel.kt @@ -11,15 +11,18 @@ import com.anytypeio.anytype.analytics.base.sendEvent 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.ObjectType import com.anytypeio.anytype.core_utils.common.EventWrapper import com.anytypeio.anytype.core_utils.ui.ViewState import com.anytypeio.anytype.domain.auth.interactor.CheckAuthorizationStatus +import com.anytypeio.anytype.domain.auth.interactor.GetLastOpenedObject import com.anytypeio.anytype.domain.auth.interactor.LaunchAccount import com.anytypeio.anytype.domain.auth.interactor.LaunchWallet import com.anytypeio.anytype.domain.auth.model.AuthStatus import com.anytypeio.anytype.domain.base.BaseUseCase import com.anytypeio.anytype.domain.block.interactor.sets.StoreObjectTypes import com.anytypeio.anytype.presentation.navigation.AppNavigation +import com.anytypeio.anytype.presentation.objects.SupportedLayouts import kotlinx.coroutines.launch import timber.log.Timber @@ -33,7 +36,8 @@ class SplashViewModel( private val checkAuthorizationStatus: CheckAuthorizationStatus, private val launchWallet: LaunchWallet, private val launchAccount: LaunchAccount, - private val storeObjectTypes: StoreObjectTypes + private val storeObjectTypes: StoreObjectTypes, + private val getLastOpenedObject: GetLastOpenedObject ) : ViewModel() { val state = MutableLiveData>() @@ -106,9 +110,41 @@ class SplashViewModel( storeObjectTypes.invoke(Unit).process( failure = { Timber.e(it, "Error while store account object types") - navigateToDashboard() + handleNavigation() }, - success = { navigateToDashboard() } + success = { handleNavigation() } + ) + } + } + + private fun handleNavigation() { + viewModelScope.launch { + getLastOpenedObject(BaseUseCase.None).process( + failure = { navigateToDashboard() }, + success = { response -> + when(response) { + is GetLastOpenedObject.Response.Success -> { + if (SupportedLayouts.layouts.contains(response.obj.layout)) { + if (response.obj.layout == ObjectType.Layout.SET) { + navigation.postValue( + EventWrapper( + AppNavigation.Command.LaunchObjectSetFromSplash(response.obj.id) + ) + ) + } else { + navigation.postValue( + EventWrapper( + AppNavigation.Command.LaunchObjectFromSplash(response.obj.id) + ) + ) + } + } else { + navigateToDashboard() + } + } + else -> navigateToDashboard() + } + } ) } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModelFactory.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModelFactory.kt index 1fa0daf099..eac0009538 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModelFactory.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/splash/SplashViewModelFactory.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import com.anytypeio.anytype.analytics.base.Analytics import com.anytypeio.anytype.domain.auth.interactor.CheckAuthorizationStatus +import com.anytypeio.anytype.domain.auth.interactor.GetLastOpenedObject import com.anytypeio.anytype.domain.auth.interactor.LaunchAccount import com.anytypeio.anytype.domain.auth.interactor.LaunchWallet import com.anytypeio.anytype.domain.block.interactor.sets.StoreObjectTypes @@ -18,7 +19,8 @@ class SplashViewModelFactory( private val launchAccount: LaunchAccount, private val launchWallet: LaunchWallet, private val analytics: Analytics, - private val storeObjectTypes: StoreObjectTypes + private val storeObjectTypes: StoreObjectTypes, + private val getLastOpenedObject: GetLastOpenedObject ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") @@ -28,6 +30,7 @@ class SplashViewModelFactory( launchAccount = launchAccount, launchWallet = launchWallet, analytics = analytics, - storeObjectTypes = storeObjectTypes + storeObjectTypes = storeObjectTypes, + getLastOpenedObject = getLastOpenedObject ) as T } \ No newline at end of file diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/splash/SplashViewModelTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/splash/SplashViewModelTest.kt index a785c5cd56..438a9d7148 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/splash/SplashViewModelTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/splash/SplashViewModelTest.kt @@ -6,9 +6,11 @@ import com.anytypeio.anytype.analytics.base.Analytics import com.anytypeio.anytype.core_utils.ui.ViewState import com.anytypeio.anytype.domain.`object`.ObjectTypesProvider import com.anytypeio.anytype.domain.auth.interactor.CheckAuthorizationStatus +import com.anytypeio.anytype.domain.auth.interactor.GetLastOpenedObject import com.anytypeio.anytype.domain.auth.interactor.LaunchAccount import com.anytypeio.anytype.domain.auth.interactor.LaunchWallet import com.anytypeio.anytype.domain.auth.model.AuthStatus +import com.anytypeio.anytype.domain.auth.repo.AuthRepository import com.anytypeio.anytype.domain.base.Either import com.anytypeio.anytype.domain.block.interactor.sets.StoreObjectTypes import com.anytypeio.anytype.domain.block.repo.BlockRepository @@ -46,10 +48,14 @@ class SplashViewModelTest { @Mock lateinit var repo: BlockRepository + @Mock + lateinit var auth: AuthRepository + @Mock lateinit var objectTypesProvider: ObjectTypesProvider - lateinit var storeObjectTypes: StoreObjectTypes + private lateinit var storeObjectTypes: StoreObjectTypes + private lateinit var getLastOpenedObject: GetLastOpenedObject lateinit var vm: SplashViewModel @@ -61,12 +67,17 @@ class SplashViewModelTest { repo = repo, objectTypesProvider = objectTypesProvider ) + getLastOpenedObject = GetLastOpenedObject( + authRepo = auth, + blockRepo = repo + ) vm = SplashViewModel( checkAuthorizationStatus = checkAuthorizationStatus, launchAccount = launchAccount, launchWallet = launchWallet, analytics = analytics, - storeObjectTypes = storeObjectTypes + storeObjectTypes = storeObjectTypes, + getLastOpenedObject = getLastOpenedObject ) } @@ -79,6 +90,7 @@ class SplashViewModelTest { stubCheckAuthStatus(response) stubLaunchWallet() stubLaunchAccount() + stubGetLastOpenedObject() runBlocking { verify(checkAuthorizationStatus, times(0)).invoke(any(), any(), any()) @@ -96,6 +108,7 @@ class SplashViewModelTest { stubCheckAuthStatus(response) stubLaunchWallet() stubLaunchAccount() + stubGetLastOpenedObject() vm.onResume() @@ -114,6 +127,7 @@ class SplashViewModelTest { stubCheckAuthStatus(response) stubLaunchWallet() stubLaunchAccount() + stubGetLastOpenedObject() vm.onResume() @@ -132,6 +146,7 @@ class SplashViewModelTest { stubCheckAuthStatus(response) stubLaunchWallet() stubLaunchAccount() + stubGetLastOpenedObject() vm.onResume() @@ -172,6 +187,7 @@ class SplashViewModelTest { val response = Either.Right(status) stubCheckAuthStatus(response) + stubGetLastOpenedObject() vm.onResume() @@ -229,4 +245,12 @@ class SplashViewModelTest { onBlocking { invoke(any()) } doReturn Either.Right("accountId") } } + + private fun stubGetLastOpenedObject() { + auth.stub { + onBlocking { + getLastOpenedObjectId() + } doReturn null + } + } } \ No newline at end of file