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 960825f0ab..40c7d8d6cf 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 @@ -8,7 +8,8 @@ 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.* -import com.anytypeio.anytype.domain.dashboard.interactor.* +import com.anytypeio.anytype.domain.dashboard.interactor.CloseDashboard +import com.anytypeio.anytype.domain.dashboard.interactor.OpenDashboard import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects import com.anytypeio.anytype.domain.event.interactor.EventChannel import com.anytypeio.anytype.domain.event.interactor.InterceptEvents @@ -17,6 +18,7 @@ import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.objects.DeleteObjects import com.anytypeio.anytype.domain.objects.SetObjectListIsArchived import com.anytypeio.anytype.domain.page.CreatePage +import com.anytypeio.anytype.domain.search.SubscriptionEventChannel import com.anytypeio.anytype.presentation.dashboard.HomeDashboardEventConverter import com.anytypeio.anytype.presentation.dashboard.HomeDashboardViewModelFactory import com.anytypeio.anytype.ui.dashboard.DashboardFragment @@ -87,9 +89,11 @@ object HomeDashboardModule { @Provides @PerScreen fun provideGetProfileUseCase( - repository: BlockRepository + repository: BlockRepository, + subscriptionEventChannel: SubscriptionEventChannel ): GetProfile = GetProfile( - repo = repository + repo = repository, + channel = subscriptionEventChannel ) @JvmStatic @@ -167,7 +171,7 @@ object HomeDashboardModule { @PerScreen fun provideGetDebugSettings( repo: InfrastructureRepository - ) : GetDebugSettings = GetDebugSettings( + ): GetDebugSettings = GetDebugSettings( repo = repo ) @@ -176,7 +180,7 @@ object HomeDashboardModule { @PerScreen fun provideSearchObjects( repo: BlockRepository - ) : SearchObjects = SearchObjects( + ): SearchObjects = SearchObjects( repo = repo ) @@ -191,7 +195,7 @@ object HomeDashboardModule { @PerScreen fun deleteObjects( repo: BlockRepository - ) : DeleteObjects = DeleteObjects( + ): DeleteObjects = DeleteObjects( repo = repo ) @@ -200,7 +204,7 @@ object HomeDashboardModule { @PerScreen fun setObjectListIsArchived( repo: BlockRepository - ) : SetObjectListIsArchived = SetObjectListIsArchived( + ): SetObjectListIsArchived = SetObjectListIsArchived( repo = repo ) } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/di/main/EventModule.kt b/app/src/main/java/com/anytypeio/anytype/di/main/EventModule.kt index e21db8c32a..e8da9d33b7 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/main/EventModule.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/main/EventModule.kt @@ -2,13 +2,17 @@ package com.anytypeio.anytype.di.main import com.anytypeio.anytype.data.auth.event.EventDataChannel import com.anytypeio.anytype.data.auth.event.EventRemoteChannel +import com.anytypeio.anytype.data.auth.event.SubscriptionDataChannel +import com.anytypeio.anytype.data.auth.event.SubscriptionEventRemoteChannel import com.anytypeio.anytype.data.auth.status.ThreadStatusDataChannel import com.anytypeio.anytype.data.auth.status.ThreadStatusRemoteChannel import com.anytypeio.anytype.domain.event.interactor.EventChannel +import com.anytypeio.anytype.domain.search.SubscriptionEventChannel import com.anytypeio.anytype.domain.status.ThreadStatusChannel import com.anytypeio.anytype.middleware.EventProxy import com.anytypeio.anytype.middleware.interactor.EventHandler import com.anytypeio.anytype.middleware.interactor.MiddlewareEventChannel +import com.anytypeio.anytype.middleware.interactor.MiddlewareSubscriptionEventChannel import com.anytypeio.anytype.middleware.interactor.ThreadStatusMiddlewareChannel import dagger.Module import dagger.Provides @@ -65,4 +69,25 @@ object EventModule { fun provideEventProxy(): EventProxy { return EventHandler() } + + @JvmStatic + @Provides + @Singleton + fun provideSubscriptionEventChannel( + channel: SubscriptionDataChannel + ): SubscriptionEventChannel = channel + + @JvmStatic + @Provides + @Singleton + fun provideSubscriptionEventDataChannel( + remote: SubscriptionEventRemoteChannel + ): SubscriptionDataChannel = SubscriptionDataChannel(remote) + + @JvmStatic + @Provides + @Singleton + fun provideSubscriptionEventRemoteChannel( + proxy: EventProxy + ): SubscriptionEventRemoteChannel = MiddlewareSubscriptionEventChannel(events = proxy) } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardDragAndDropBehavior.kt b/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardDragAndDropBehavior.kt index 53be8c5937..c27e8674b4 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardDragAndDropBehavior.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardDragAndDropBehavior.kt @@ -1,20 +1,8 @@ package com.anytypeio.anytype.ui.dashboard -import androidx.recyclerview.widget.RecyclerView import com.anytypeio.anytype.core_ui.tools.DefaultDragAndDropBehavior class DashboardDragAndDropBehavior( onItemMoved: (Int, Int) -> Boolean, onItemDropped: (Int) -> Unit -) : DefaultDragAndDropBehavior(onItemMoved, onItemDropped) { - - override fun getMovementFlags( - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder - ): Int { - return if (viewHolder is ProfileContainerAdapter.ProfileContainerHolder) - makeMovementFlags(0, 0) - else - super.getMovementFlags(recyclerView, viewHolder) - } -} \ No newline at end of file +) : DefaultDragAndDropBehavior(onItemMoved, onItemDropped) \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardFragment.kt index a64ff450f3..bb019af6e5 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardFragment.kt @@ -10,8 +10,10 @@ import androidx.transition.ChangeBounds import androidx.transition.TransitionManager import androidx.transition.TransitionSet import com.anytypeio.anytype.R +import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.core_ui.reactive.clicks import com.anytypeio.anytype.core_utils.ext.* +import com.anytypeio.anytype.core_utils.ui.ViewState import com.anytypeio.anytype.di.common.componentManager import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.presentation.dashboard.DashboardView @@ -223,6 +225,32 @@ class DashboardFragment : ViewStateFragment(R.layout.fragment_dashboard) objectRemovalProgressBar.gone() } } + jobs += lifecycleScope.subscribe(vm.profile) { profile -> + setProfile(profile) + } + } + + private fun setProfile(profile: ViewState) { + when (profile) { + is ViewState.Success -> { + val obj = profile.data + avatarContainer.bind( + name = obj.name.orEmpty(), + color = context?.getColor(R.color.dashboard_default_avatar_circle_color) + ) + obj.iconImage?.let { avatar -> + avatarContainer.icon(avatar) + } + if (obj.name.isNullOrEmpty()) { + tvGreeting.text = getText(R.string.greet_user) + } else { + tvGreeting.text = getString(R.string.greet, obj.name) + } + } + else -> { + // TODO reset profile view to zero + } + } } override fun onPause() { @@ -264,31 +292,10 @@ class DashboardFragment : ViewStateFragment(R.layout.fragment_dashboard) override fun render(state: State) { when { - state.error != null -> { - requireActivity().toast("Error: ${state.error}") - } + state.error != null -> toast("Error: ${state.error}") state.isInitialzed -> { - state.blocks.let { views -> - val profile = views.filterIsInstance() - val links = views.filter { it !is DashboardView.Profile && it !is DashboardView.Archive }.groupBy { it.isArchived } - if (profile.isNotEmpty()) { - val view = profile.first() - avatarContainer.bind( - name = view.name, - color = context?.getColor(R.color.dashboard_default_avatar_circle_color) - ) - view.avatar?.let { avatar -> - avatarContainer.icon(avatar) - } - if (view.name.isNotEmpty()) { - tvGreeting.text = getString(R.string.greet, view.name) - } else { - tvGreeting.text = getText(R.string.greet_user) - } - } - // TODO refact (no need to filter anything in fragment) - dashboardDefaultAdapter.update(links[false] ?: emptyList()) - } + val links = state.blocks.filter { it !is DashboardView.Archive }.groupBy { it.isArchived } + dashboardDefaultAdapter.update(links[false] ?: emptyList()) } } } diff --git a/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardProfileAdapter.kt b/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardProfileAdapter.kt deleted file mode 100644 index 8543525814..0000000000 --- a/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DashboardProfileAdapter.kt +++ /dev/null @@ -1,82 +0,0 @@ -package com.anytypeio.anytype.ui.dashboard - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import com.anytypeio.anytype.R -import com.anytypeio.anytype.core_ui.extensions.avatarColor -import com.anytypeio.anytype.core_utils.ext.firstDigitByHash -import com.anytypeio.anytype.core_utils.ext.typeOf -import com.anytypeio.anytype.presentation.dashboard.DashboardView -import kotlinx.android.synthetic.main.item_dashboard_profile_header.view.* - -class DashboardProfileAdapter( - private var data: MutableList, - private val onProfileClicked: () -> Unit -) : RecyclerView.Adapter() { - - fun update(views: List) { - data.clear() - data.addAll(views) - notifyDataSetChanged() - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProfileHolder { - val inflater = LayoutInflater.from(parent.context) - return ProfileHolder(inflater.inflate(R.layout.item_dashboard_profile_header, parent, false)) - } - - override fun onBindViewHolder(holder: ProfileHolder, position: Int) { - val item = data[position] as DashboardView.Profile - with(holder) { - bindClick(onProfileClicked) - bindName(item.name) - bindAvatar(name = item.name, avatar = item.avatar) - } - } - - override fun onBindViewHolder( - holder: ProfileHolder, - position: Int, - payloads: MutableList - ) { - if (payloads.isEmpty()) { - onBindViewHolder(holder, position) - } else { - payloads.typeOf().forEach { payload -> - val item = data[position] as DashboardView.Profile - with(holder) { - if (payload.titleChanged()) { - bindName(item.name) - } - if (payload.imageChanged()) { - bindAvatar(item.name, item.avatar) - } - } - } - } - } - - override fun getItemCount(): Int = data.size - - class ProfileHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - - fun bindName(name: String) { - itemView.greeting.text = itemView.context.getString(R.string.greet, name) - } - - fun bindAvatar(name: String, avatar: String?) { - val pos = name.firstDigitByHash() - itemView.avatar.bind( - name = name, - color = itemView.context.avatarColor(pos) - ) - avatar?.let { itemView.avatar.icon(it) } - } - - fun bindClick(onClick: () -> Unit) { - itemView.avatar.setOnClickListener { onClick() } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DesktopDiffUtil.kt b/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DesktopDiffUtil.kt index 889322252c..155eb6987f 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DesktopDiffUtil.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/dashboard/DesktopDiffUtil.kt @@ -58,15 +58,6 @@ class DesktopDiffUtil( } } - if (oldDoc is DashboardView.Profile && newDoc is DashboardView.Profile) { - if (oldDoc.avatar != newDoc.avatar) { - changes.add(IMAGE_CHANGED) - } - if (oldDoc.name != newDoc.name) { - changes.add(TITLE_CHANGED) - } - } - if (oldDoc.isSelected != newDoc.isSelected) { changes.add(SELECTION_CHANGED) } diff --git a/app/src/main/java/com/anytypeio/anytype/ui/dashboard/ProfileContainerAdapter.kt b/app/src/main/java/com/anytypeio/anytype/ui/dashboard/ProfileContainerAdapter.kt deleted file mode 100644 index c5cabdec59..0000000000 --- a/app/src/main/java/com/anytypeio/anytype/ui/dashboard/ProfileContainerAdapter.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.anytypeio.anytype.ui.dashboard - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.FrameLayout -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import com.anytypeio.anytype.R -import kotlinx.android.synthetic.main.item_profile_container.view.* - -class ProfileContainerAdapter( - val adapter: DashboardProfileAdapter -) : RecyclerView.Adapter() { - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProfileContainerHolder { - val inflater = LayoutInflater.from(parent.context) - val view = inflater.inflate(R.layout.item_profile_container, parent, false) - view.recyclerView.apply { - layoutManager = LinearLayoutManager(parent.context) - val lp = (layoutParams as FrameLayout.LayoutParams) - lp.height = (parent.height / 2) - lp.topMargin - lp.bottomMargin - } - return ProfileContainerHolder(view) - } - - override fun onBindViewHolder(holder: ProfileContainerHolder, position: Int) { - holder.bind(adapter) - } - - override fun getItemCount(): Int = 1 - - class ProfileContainerHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - - fun bind(adapter: DashboardProfileAdapter) { - itemView.recyclerView.adapter = adapter - } - } -} \ No newline at end of file diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/SearchResult.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/SearchResult.kt index 848ff0fb90..9c9284c087 100644 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/SearchResult.kt +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/SearchResult.kt @@ -1,6 +1,6 @@ package com.anytypeio.anytype.core_models data class SearchResult( - val results: List, - val dependencies: List + val results: List, + val dependencies: List ) \ No newline at end of file diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/SubscriptionEvent.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/SubscriptionEvent.kt new file mode 100644 index 0000000000..0fb9cac681 --- /dev/null +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/SubscriptionEvent.kt @@ -0,0 +1,32 @@ +package com.anytypeio.anytype.core_models + +/** + * Events related to changes or transformations of objects. + * @see ObjectWrapper.Basic + */ +sealed class SubscriptionEvent { + /** + * @property [target] id of the object + * @property [diff] slice of changes to apply to the object + */ + data class Amend( + val target: Id, + val diff: Map + ) : SubscriptionEvent() + /** + * @property [target] id of the object + * @property [keys] keys, whose values should be removed + */ + data class Unset( + val target: Id, + val keys: List + ) : SubscriptionEvent() + /** + * @property [target] id of the object + * @property [data] new set of data for the object + */ + data class Set( + val target: Id, + val data: Map + ) : SubscriptionEvent() +} \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/event/SubscriptionDataChannel.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/event/SubscriptionDataChannel.kt new file mode 100644 index 0000000000..19449757bf --- /dev/null +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/event/SubscriptionDataChannel.kt @@ -0,0 +1,10 @@ +package com.anytypeio.anytype.data.auth.event + +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.domain.search.SubscriptionEventChannel + +class SubscriptionDataChannel( + private val remote: SubscriptionEventRemoteChannel +) : SubscriptionEventChannel { + override fun subscribe(subscriptions: List) = remote.subscribe(subscriptions) +} \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/event/SubscriptionEventRemoteChannel.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/event/SubscriptionEventRemoteChannel.kt new file mode 100644 index 0000000000..3315f645c3 --- /dev/null +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/event/SubscriptionEventRemoteChannel.kt @@ -0,0 +1,9 @@ +package com.anytypeio.anytype.data.auth.event + +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.SubscriptionEvent +import kotlinx.coroutines.flow.Flow + +interface SubscriptionEventRemoteChannel { + fun subscribe(subscriptions: List): Flow> +} \ No newline at end of file diff --git a/domain/build.gradle b/domain/build.gradle index d9035b8ecc..896bc5cb76 100644 --- a/domain/build.gradle +++ b/domain/build.gradle @@ -13,6 +13,7 @@ dependencies { def unitTestDependencies = rootProject.ext.unitTesting testImplementation unitTestDependencies.kotlinTest + testImplementation unitTestDependencies.turbine testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0" testImplementation unitTestDependencies.coroutineTesting diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/GetProfile.kt b/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/GetProfile.kt index a402833044..f9901c666d 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/GetProfile.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/GetProfile.kt @@ -1,25 +1,87 @@ package com.anytypeio.anytype.domain.auth.interactor +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.ObjectWrapper +import com.anytypeio.anytype.core_models.SubscriptionEvent +import com.anytypeio.anytype.domain.`object`.amend +import com.anytypeio.anytype.domain.`object`.unset 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.search.SubscriptionEventChannel +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.* /** Use case for getting currently selected user account. */ class GetProfile( - private val repo: BlockRepository -) : BaseUseCase() { + private val repo: BlockRepository, + private val channel: SubscriptionEventChannel +) : BaseUseCase() { - override suspend fun run(params: None) = try { + fun subscribe(subscription: Id) = channel.subscribe(subscriptions = listOf(subscription)) - val config = repo.getConfig() + fun observe( + subscription: Id, + keys: List, + dispatcher: CoroutineDispatcher = Dispatchers.IO + ): Flow { - val payload = repo.openProfile(config.profile) - - Either.Right(payload) - - } catch (t: Throwable) { - Either.Left(t) + return flow { + val profile = getProfile(subscription, keys) + emitAll( + channel.subscribe(subscriptions = listOf(subscription)).scan(profile) { prev, payload -> + var result = prev + payload.forEach { event -> + result = when (event) { + is SubscriptionEvent.Amend -> { + result.amend(event.diff) + } + is SubscriptionEvent.Set -> { + ObjectWrapper.Basic(event.data) + } + is SubscriptionEvent.Unset -> { + result.unset(event.keys) + } + } + } + result + } + ) + }.flowOn(dispatcher) } + + private suspend fun getProfile( + subscription: Id, + keys: List + ): ObjectWrapper.Basic { + val config = repo.getConfig() + val result = repo.searchObjectsByIdWithSubscription( + subscription = subscription, + ids = listOf(config.profile), + keys = keys + ) + val profile = result.results.first { obj -> + obj.id == config.profile + } + return profile + } + + @Deprecated("Should not be used. Will be changed.") + override suspend fun run(params: Params) = safe { + val config = repo.getConfig() + val result = repo.searchObjectsByIdWithSubscription( + subscription = params.subscription, + ids = listOf(config.profile), + keys = params.keys + ) + result.results.find { obj -> + obj.id == config.profile + } ?: throw Exception("Profile not found") + } + + class Params( + val subscription: Id, + val keys: List + ) } \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/object/ObjectWrapperExt.kt b/domain/src/main/java/com/anytypeio/anytype/domain/object/ObjectWrapperExt.kt new file mode 100644 index 0000000000..0959b17648 --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/object/ObjectWrapperExt.kt @@ -0,0 +1,18 @@ +package com.anytypeio.anytype.domain.`object` + +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.ObjectWrapper + +/** + * Function for applying granular changes in object, replacing existing values with the new ones. + * @param [diff] difference + */ +fun ObjectWrapper.Basic.amend(diff: Map) = ObjectWrapper.Basic(map + diff) +/** + * Function for applying granular changes in object. + */ +fun ObjectWrapper.Basic.unset(keys: List) = ObjectWrapper.Basic( + map.toMutableMap().apply { + keys.forEach { k -> remove(k) } + } +) diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/search/SubscriptionEventChannel.kt b/domain/src/main/java/com/anytypeio/anytype/domain/search/SubscriptionEventChannel.kt new file mode 100644 index 0000000000..d5aa83ccde --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/search/SubscriptionEventChannel.kt @@ -0,0 +1,13 @@ +package com.anytypeio.anytype.domain.search + +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.SubscriptionEvent +import kotlinx.coroutines.flow.Flow + +/** + * Channel for events related to changes and transformations of objects. + * @see SubscriptionEvent + */ +interface SubscriptionEventChannel { + fun subscribe(subscriptions: List): Flow> +} \ No newline at end of file diff --git a/domain/src/test/java/com/anytypeio/anytype/domain/dashboard/GetProfileTest.kt b/domain/src/test/java/com/anytypeio/anytype/domain/dashboard/GetProfileTest.kt new file mode 100644 index 0000000000..2a81f63590 --- /dev/null +++ b/domain/src/test/java/com/anytypeio/anytype/domain/dashboard/GetProfileTest.kt @@ -0,0 +1,335 @@ +package com.anytypeio.anytype.domain.dashboard + +import app.cash.turbine.test +import com.anytypeio.anytype.core_models.* +import com.anytypeio.anytype.domain.auth.interactor.GetProfile +import com.anytypeio.anytype.domain.block.repo.BlockRepository +import com.anytypeio.anytype.domain.common.MockDataFactory +import com.anytypeio.anytype.domain.search.SubscriptionEventChannel +import com.nhaarman.mockitokotlin2.doReturn +import com.nhaarman.mockitokotlin2.stub +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runBlockingTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import kotlin.test.assertEquals + +class GetProfileTest { + + @ExperimentalCoroutinesApi + @get:Rule + var rule = CoroutineTestRule() + + @Mock + lateinit var repo: BlockRepository + + @Mock + lateinit var channel: SubscriptionEventChannel + + private lateinit var usecase: GetProfile + + val config = Config( + home = MockDataFactory.randomUuid(), + profile = MockDataFactory.randomUuid(), + gateway = MockDataFactory.randomUuid() + ) + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + usecase = GetProfile( + repo = repo, + channel = channel + ) + } + + @Test + fun `should emit initial data with profile and complete`() = runBlockingTest { + + val subscription = MockDataFactory.randomUuid() + + val profile = ObjectWrapper.Basic( + mapOf( + Relations.ID to config.profile, + Relations.NAME to "Friedrich Kittler" + ) + ) + + repo.stub { + onBlocking { getConfig() } doReturn config + } + + channel.stub { + on { subscribe(listOf(subscription)) } doReturn flowOf() + } + + repo.stub { + onBlocking { + searchObjectsByIdWithSubscription( + subscription = subscription, + keys = emptyList(), + ids = listOf(config.profile) + ) + } doReturn SearchResult( + results = listOf(profile), + dependencies = emptyList() + ) + } + + usecase.observe( + subscription = subscription, + keys = emptyList(), + dispatcher = rule.testDispatcher + ).test { + assertEquals( + expected = profile.map, + actual = awaitItem().map + ) + awaitComplete() + } + } + + @Test + fun `should emit transformated profile object, then complete`() = runBlockingTest { + + val subscription = MockDataFactory.randomUuid() + + val nameBeforeUpdate = "Friedrich" + val nameAfterUpdate = "Friedrich Kittler" + + val profileObjectBeforeUpdate = ObjectWrapper.Basic( + mapOf( + Relations.ID to config.profile, + Relations.NAME to nameBeforeUpdate + ) + ) + + val profileObjectAfterUpdate = ObjectWrapper.Basic( + mapOf( + Relations.ID to config.profile, + Relations.NAME to nameAfterUpdate + ) + ) + + repo.stub { + onBlocking { getConfig() } doReturn config + } + + channel.stub { + on { subscribe(listOf(subscription)) } doReturn flow { + emit( + listOf( + SubscriptionEvent.Amend( + diff = mapOf( + Relations.NAME to nameAfterUpdate + ), + target = config.profile + ) + ) + ) + } + } + + repo.stub { + onBlocking { + searchObjectsByIdWithSubscription( + subscription = subscription, + keys = emptyList(), + ids = listOf(config.profile) + ) + } doReturn SearchResult( + results = listOf(profileObjectBeforeUpdate), + dependencies = emptyList() + ) + } + + usecase.observe( + subscription = subscription, + keys = emptyList(), + dispatcher = rule.testDispatcher + ).test { + assertEquals( + expected = profileObjectBeforeUpdate.map, + actual = awaitItem().map + ) + assertEquals( + expected = profileObjectAfterUpdate.map, + actual = awaitItem().map + ) + awaitComplete() + } + } + + @Test + fun `should apply several transformations, then complete`() = runBlockingTest { + + val subscription = MockDataFactory.randomUuid() + + val nameBeforeUpdate = "Friedrich" + val nameAfterUpdate = "Friedrich Kittler" + + val iconImageBeforeUpdate = null + val iconImageAfterUpdate = MockDataFactory.randomUuid() + + val profileObjectBeforeUpdate = ObjectWrapper.Basic( + mapOf( + Relations.ID to config.profile, + Relations.NAME to nameBeforeUpdate, + Relations.ICON_IMAGE to iconImageBeforeUpdate + ) + ) + + repo.stub { + onBlocking { getConfig() } doReturn config + } + + channel.stub { + on { subscribe(listOf(subscription)) } doReturn flow { + emit( + listOf( + SubscriptionEvent.Amend( + diff = mapOf( + Relations.NAME to nameAfterUpdate + ), + target = config.profile + ), + SubscriptionEvent.Amend( + diff = mapOf( + Relations.ICON_IMAGE to iconImageAfterUpdate + ), + target = config.profile + ) + ) + ) + } + } + + repo.stub { + onBlocking { + searchObjectsByIdWithSubscription( + subscription = subscription, + keys = emptyList(), + ids = listOf(config.profile) + ) + } doReturn SearchResult( + results = listOf(profileObjectBeforeUpdate), + dependencies = emptyList() + ) + } + + usecase.observe( + subscription = subscription, + keys = emptyList(), + dispatcher = rule.testDispatcher + ).test { + assertEquals( + expected = profileObjectBeforeUpdate.map, + actual = awaitItem().map + ) + assertEquals( + expected = mapOf( + Relations.ID to config.profile, + Relations.NAME to nameAfterUpdate, + Relations.ICON_IMAGE to iconImageAfterUpdate + ), + actual = awaitItem().map + ) + awaitComplete() + } + } + + @Test + fun `should apply all transformations, then complete`() = runBlockingTest { + + val subscription = MockDataFactory.randomUuid() + + val nameBeforeUpdate = "Friedrich" + val nameAfterUpdate = "Friedrich Kittler" + + val iconImageBeforeUpdate = null + val iconImageAfterUpdate = MockDataFactory.randomUuid() + + val profileObjectBeforeUpdate = ObjectWrapper.Basic( + mapOf( + Relations.ID to config.profile, + Relations.NAME to nameBeforeUpdate, + Relations.ICON_IMAGE to iconImageBeforeUpdate + ) + ) + + repo.stub { + onBlocking { getConfig() } doReturn config + } + + channel.stub { + on { subscribe(listOf(subscription)) } doReturn flow { + emit( + listOf( + SubscriptionEvent.Amend( + diff = mapOf( + Relations.NAME to nameAfterUpdate + ), + target = config.profile + ) + ) + ) + emit( + listOf( + SubscriptionEvent.Amend( + diff = mapOf( + Relations.ICON_IMAGE to iconImageAfterUpdate + ), + target = config.profile + ) + ) + ) + } + } + + repo.stub { + onBlocking { + searchObjectsByIdWithSubscription( + subscription = subscription, + keys = emptyList(), + ids = listOf(config.profile) + ) + } doReturn SearchResult( + results = listOf(profileObjectBeforeUpdate), + dependencies = emptyList() + ) + } + + usecase.observe( + subscription = subscription, + keys = emptyList(), + dispatcher = rule.testDispatcher + ).test { + assertEquals( + expected = profileObjectBeforeUpdate.map, + actual = awaitItem().map + ) + assertEquals( + expected = mapOf( + Relations.ID to config.profile, + Relations.NAME to nameAfterUpdate, + Relations.ICON_IMAGE to iconImageBeforeUpdate + ), + actual = awaitItem().map + ) + assertEquals( + expected = mapOf( + Relations.ID to config.profile, + Relations.NAME to nameAfterUpdate, + Relations.ICON_IMAGE to iconImageAfterUpdate + ), + actual = awaitItem().map + ) + awaitComplete() + } + } +} \ No newline at end of file diff --git a/domain/src/test/java/com/anytypeio/anytype/domain/ext/ObjectWrapperExtTest.kt b/domain/src/test/java/com/anytypeio/anytype/domain/ext/ObjectWrapperExtTest.kt new file mode 100644 index 0000000000..f2e7adee9b --- /dev/null +++ b/domain/src/test/java/com/anytypeio/anytype/domain/ext/ObjectWrapperExtTest.kt @@ -0,0 +1,92 @@ +package com.anytypeio.anytype.domain.ext + +import com.anytypeio.anytype.core_models.ObjectWrapper +import com.anytypeio.anytype.core_models.Relations +import com.anytypeio.anytype.domain.`object`.amend +import com.anytypeio.anytype.domain.`object`.unset +import com.anytypeio.anytype.domain.common.MockDataFactory +import org.junit.Test +import kotlin.test.assertEquals + +class ObjectWrapperExtTest { + + @Test + fun `should update several fields with amend operation and preserve old fields - amend operation`() { + + val initial = ObjectWrapper.Basic( + mapOf( + Relations.NAME to "Friedrich Kittler", + Relations.DONE to false, + Relations.DESCRIPTION to null, + Relations.IS_FAVORITE to false + ) + ) + + val expected = ObjectWrapper.Basic( + mapOf( + Relations.NAME to "Friedrich Kittler", + Relations.DONE to true, + Relations.DESCRIPTION to "German media philosopher", + Relations.IS_FAVORITE to true + ) + ) + + val result = initial.amend( + mapOf( + Relations.DONE to true, + Relations.IS_FAVORITE to true, + Relations.DESCRIPTION to "German media philosopher", + ) + ) + + assertEquals( + expected = expected.map, + actual = result.map + ) + } + + @Test + fun `should remove several fields - unset operation`() { + + val firstRelationId = MockDataFactory.randomUuid() + val firstRelationValue = MockDataFactory.randomString() + val secondRelationId = MockDataFactory.randomUuid() + val secondRelationValue = MockDataFactory.randomInt() + val thirdRelationId = MockDataFactory.randomUuid() + val thirdRelationValue = MockDataFactory.randomBoolean() + + val initial = ObjectWrapper.Basic( + mapOf( + Relations.NAME to "Friedrich Kittler", + Relations.DONE to false, + Relations.DESCRIPTION to null, + Relations.IS_FAVORITE to false, + firstRelationId to firstRelationValue, + secondRelationId to secondRelationValue, + thirdRelationId to thirdRelationValue + ) + ) + + val expected = ObjectWrapper.Basic( + mapOf( + Relations.NAME to "Friedrich Kittler", + Relations.DONE to false, + Relations.DESCRIPTION to null, + Relations.IS_FAVORITE to false, + ) + ) + + val result = initial.unset( + keys = listOf( + firstRelationId, + secondRelationId, + thirdRelationId + ) + ) + + assertEquals( + expected = expected.map, + actual = result.map + ) + } +} \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/MiddlewareSubscriptionEventChannel.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/MiddlewareSubscriptionEventChannel.kt new file mode 100644 index 0000000000..be3756f51c --- /dev/null +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/MiddlewareSubscriptionEventChannel.kt @@ -0,0 +1,63 @@ +package com.anytypeio.anytype.middleware.interactor + +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.SubscriptionEvent +import com.anytypeio.anytype.data.auth.event.SubscriptionEventRemoteChannel +import com.anytypeio.anytype.middleware.EventProxy +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.mapNotNull + +class MiddlewareSubscriptionEventChannel( + private val events: EventProxy +) : SubscriptionEventRemoteChannel { + + override fun subscribe(subscriptions: List) = events + .flow() + .mapNotNull { payload -> + payload.messages.mapNotNull { e -> + when { + e.objectDetailsAmend != null -> { + val event = e.objectDetailsAmend + checkNotNull(event) + if (subscriptions.any { it in event.subIds }) { + SubscriptionEvent.Amend( + target = event.id, + diff = event.details.associate { it.key to it.value } + ) + } else { + null + } + } + e.objectDetailsUnset != null -> { + val event = e.objectDetailsUnset + checkNotNull(event) + if (subscriptions.any { it in event.subIds }) { + SubscriptionEvent.Unset( + target = event.id, + keys = event.keys + ) + } else { + null + } + } + e.objectDetailsSet != null -> { + val event = e.objectDetailsSet + checkNotNull(event) + val data = event.details + if (subscriptions.any { it in event.subIds } && data != null) { + SubscriptionEvent.Set( + target = event.id, + data = data + ) + } else { + null + } + } + else -> { + null + } + } + } + } + .filter { it.isNotEmpty() } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/DashboardProfileView.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/DashboardProfileView.kt new file mode 100644 index 0000000000..fa0f1a5d2f --- /dev/null +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/DashboardProfileView.kt @@ -0,0 +1,10 @@ +package com.anytypeio.anytype.presentation.dashboard + +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.Url + +data class DashboardProfileView( + val id: Id, + val name: String, + val image: Url? +) \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/DashboardView.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/DashboardView.kt index 0bdffb67f3..9265ce5f96 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/DashboardView.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/DashboardView.kt @@ -2,7 +2,6 @@ package com.anytypeio.anytype.presentation.dashboard import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.ObjectType -import com.anytypeio.anytype.core_models.Url import com.anytypeio.anytype.presentation.objects.ObjectIcon sealed class DashboardView { @@ -12,15 +11,6 @@ sealed class DashboardView { abstract val isSelected: Boolean abstract val isLoading: Boolean - data class Profile( - override val id: Id, - val name: String, - val avatar: Url? = null, - override val isArchived: Boolean = false, - override val isSelected: Boolean = false, - override val isLoading: Boolean = false - ) : DashboardView() - data class Document( override val id: Id, val target: Id, diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardEventConverter.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardEventConverter.kt index d331b28e3b..38b1b15deb 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardEventConverter.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardEventConverter.kt @@ -34,14 +34,6 @@ interface HomeDashboardEventConverter { objectTypes = objectTypesProvider.get() ) } - SmartBlockType.PROFILE_PAGE -> { - HomeDashboardStateMachine.Event.OnShowProfile( - blocks = event.blocks, - context = event.context, - details = event.details, - builder = builder - ) - } else -> { null } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardStateMachine.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardStateMachine.kt index 6513d7068e..1c57a8fce2 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardStateMachine.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardStateMachine.kt @@ -75,13 +75,6 @@ sealed class HomeDashboardStateMachine { val objectTypes: List ) : Event() - data class OnShowProfile( - val context: String, - val blocks: List, - val details: Block.Details, - val builder: UrlBuilder - ) : Event() - data class OnDetailsUpdated( val context: String, val target: String, @@ -157,45 +150,24 @@ sealed class HomeDashboardStateMachine { ) is Event.OnShowDashboard -> { - val current = state.blocks.filterIsInstance() - - val new = event.blocks - .toDashboardViews( + val new = event.blocks.toDashboardViews( details = event.details, builder = event.builder, objectTypes = event.objectTypes - ) - - val childrenIdsList = event.blocks.getChildrenIdsList( - parent = event.context ) + val childrenIdsList = event.blocks.getChildrenIdsList(parent = event.context) + state.copy( isInitialzed = true, isLoading = false, error = null, - blocks = current.addAndSortByIds(childrenIdsList, new), + blocks = new, childrenIdsList = childrenIdsList, objectTypes = event.objectTypes, details = event.details ) } - is Event.OnShowProfile -> { - - val current = state.blocks.filter { it !is DashboardView.Profile } - - val new = event.blocks.toDashboardViews( - details = event.details, - builder = event.builder - ).filterIsInstance() - - state.copy( - isInitialzed = true, - isLoading = false, - error = null, - blocks = current.addAndSortByIds(state.childrenIdsList, new) - ) - } is Event.OnStartedCreatingPage -> state.copy( isLoading = true ) diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModel.kt index f6898b1609..869299c03d 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModel.kt @@ -16,15 +16,12 @@ import com.anytypeio.anytype.analytics.base.EventsDictionary.TAB_RECENT import com.anytypeio.anytype.analytics.base.EventsDictionary.TAB_SETS import com.anytypeio.anytype.analytics.base.sendEvent import com.anytypeio.anytype.analytics.props.Props -import com.anytypeio.anytype.core_models.Event -import com.anytypeio.anytype.core_models.Id -import com.anytypeio.anytype.core_models.ObjectType -import com.anytypeio.anytype.core_models.Position +import com.anytypeio.anytype.core_models.* import com.anytypeio.anytype.core_utils.common.EventWrapper import com.anytypeio.anytype.core_utils.ext.withLatestFrom +import com.anytypeio.anytype.core_utils.ui.ViewState import com.anytypeio.anytype.core_utils.ui.ViewStateViewModel import com.anytypeio.anytype.domain.auth.interactor.GetProfile -import com.anytypeio.anytype.domain.base.BaseUseCase import com.anytypeio.anytype.domain.block.interactor.Move import com.anytypeio.anytype.domain.config.FlavourConfigProvider import com.anytypeio.anytype.domain.config.GetConfig @@ -48,6 +45,7 @@ import com.anytypeio.anytype.presentation.objects.ObjectIcon import com.anytypeio.anytype.presentation.objects.SupportedLayouts import com.anytypeio.anytype.presentation.objects.getProperName import com.anytypeio.anytype.presentation.search.ObjectSearchConstants +import com.anytypeio.anytype.presentation.search.Subscriptions import com.anytypeio.anytype.presentation.settings.EditorSettings import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.* @@ -76,7 +74,6 @@ class HomeDashboardViewModel( HomeDashboardEventConverter by eventConverter, SupportNavigation> { - private val isProfileNavigationEnabled = MutableStateFlow(false) val toasts = MutableSharedFlow() private val machine = Interactor(scope = viewModelScope) @@ -89,7 +86,6 @@ class HomeDashboardViewModel( override val navigation = MutableLiveData>() private var ctx: Id = "" - private var profile: Id = "" val tabs = MutableStateFlow(listOf(TAB.FAVOURITE, TAB.RECENT, TAB.SETS, TAB.BIN)) @@ -109,9 +105,23 @@ class HomeDashboardViewModel( private val views: List get() = stateData.value?.blocks ?: emptyList() + val profile = MutableStateFlow>(ViewState.Init) + init { startProcessingState() proceedWithGettingConfig() + viewModelScope.launch { + getProfile.observe( + subscription = Subscriptions.SUBSCRIPTION_PROFILE, + keys = listOf( + Relations.ID, + Relations.NAME, + Relations.ICON_IMAGE + ) + ).collect { + profile.value = ViewState.Success(it) + } + } } private fun startProcessingState() { @@ -134,8 +144,6 @@ class HomeDashboardViewModel( result.either( fnR = { config -> ctx = config.home - profile = config.profile - isProfileNavigationEnabled.value = true startInterceptingEvents(context = config.home) processDragAndDrop(context = config.home) }, @@ -144,15 +152,6 @@ class HomeDashboardViewModel( } } - private fun proceedWithGettingAccount() { - getProfile(viewModelScope, BaseUseCase.None) { result -> - result.either( - fnL = { Timber.e(it, "Error while getting account") }, - fnR = { payload -> processEvents(payload.events) } - ) - } - } - private fun processDragAndDrop(context: String) { viewModelScope.launch { dropChanges @@ -193,7 +192,6 @@ class HomeDashboardViewModel( fun onViewCreated() { Timber.d("onViewCreated, ") - proceedWithGettingAccount() proceedWithOpeningHomeDashboard() } @@ -346,14 +344,19 @@ class HomeDashboardViewModel( fun onAvatarClicked() { Timber.d("onAvatarClicked, ") - if (isProfileNavigationEnabled.value) { - viewModelScope.sendEvent( - analytics = analytics, - eventName = SCREEN_PROFILE - ) - proceedWithOpeningDocument(profile) - } else { - toast("Profile is not ready yet. Please, try again later.") + profile.value.let { state -> + when(state) { + is ViewState.Success -> { + viewModelScope.sendEvent( + analytics = analytics, + eventName = SCREEN_PROFILE + ) + proceedWithOpeningDocument(state.data.id) + } + else -> { + toast("Profile is not ready yet. Please, try again later.") + } + } } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/extension/DashboardViewExtension.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/extension/DashboardViewExtension.kt index 9e3363787d..0f5cf3ca9c 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/extension/DashboardViewExtension.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/extension/DashboardViewExtension.kt @@ -24,7 +24,7 @@ fun List.sortByIds( } fun List.filterByNotArchivedPages(): List = - this.filterNot { it is DashboardView.Profile || it.isArchived } + this.filterNot { it.isArchived } fun List.updateDetails( target: String, @@ -34,19 +34,6 @@ fun List.updateDetails( ): List { return mapNotNull { view -> when (view) { - is DashboardView.Profile -> { - if (view.id == target) { - view.copy( - name = details.name.orEmpty(), - avatar = details.iconImage.let { - if (it.isNullOrEmpty()) null - else builder.image(it) - } - ) - } else { - view - } - } is DashboardView.Document -> { if (view.target == target) { val obj = ObjectWrapper.Basic(details.map) diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/mapper/MapperExtension.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/mapper/MapperExtension.kt index d08bdd4e57..b708864424 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/mapper/MapperExtension.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/mapper/MapperExtension.kt @@ -258,21 +258,6 @@ fun List.toDashboardViews( objectTypes: List = emptyList() ): List = this.mapNotNull { block -> when (val content = block.content) { - is Block.Content.Smart -> { - when (content.type) { - SmartBlockType.PROFILE_PAGE -> { - DashboardView.Profile( - id = block.id, - name = details.details[block.id]?.name.orEmpty(), - avatar = details.details[block.id]?.iconImage.let { - if (it.isNullOrEmpty()) null - else builder.image(it) - } - ) - } - else -> null - } - } is Block.Content.Link -> { val targetDetails = details.details[content.target] val typeUrl = targetDetails?.map?.type diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/search/Subscriptions.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/search/Subscriptions.kt new file mode 100644 index 0000000000..43c576e021 --- /dev/null +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/search/Subscriptions.kt @@ -0,0 +1,5 @@ +package com.anytypeio.anytype.presentation.search + +object Subscriptions { + const val SUBSCRIPTION_PROFILE = "subscription.profile" +} \ No newline at end of file diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModelTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModelTest.kt index ab1d8fd693..25e505928a 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModelTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/dashboard/HomeDashboardViewModelTest.kt @@ -10,7 +10,8 @@ import com.anytypeio.anytype.domain.auth.interactor.GetProfile import com.anytypeio.anytype.domain.base.Either import com.anytypeio.anytype.domain.block.interactor.Move import com.anytypeio.anytype.domain.config.* -import com.anytypeio.anytype.domain.dashboard.interactor.* +import com.anytypeio.anytype.domain.dashboard.interactor.CloseDashboard +import com.anytypeio.anytype.domain.dashboard.interactor.OpenDashboard import com.anytypeio.anytype.domain.dataview.interactor.SearchObjects import com.anytypeio.anytype.domain.event.interactor.InterceptEvents import com.anytypeio.anytype.domain.launch.GetDefaultEditorType @@ -22,6 +23,7 @@ import com.anytypeio.anytype.presentation.navigation.AppNavigation import com.anytypeio.anytype.presentation.util.CoroutinesTestRule import com.jraska.livedata.test import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runBlockingTest import org.junit.Before @@ -135,6 +137,7 @@ class HomeDashboardViewModelTest { stubGetConfig(response) stubObserveEvents(params = InterceptEvents.Params(context = null)) + stubObserveProfile() // TESTING @@ -142,7 +145,6 @@ class HomeDashboardViewModelTest { verify(getConfig, times(1)).invoke(any(), any(), any()) verifyZeroInteractions(openDashboard) - verifyZeroInteractions(getProfile) } @Test @@ -174,6 +176,7 @@ class HomeDashboardViewModelTest { stubGetConfig(response) stubObserveEvents(params = InterceptEvents.Params(context = config.home)) + stubObserveProfile() // TESTING @@ -181,7 +184,6 @@ class HomeDashboardViewModelTest { verify(getConfig, times(1)).invoke(any(), any(), any()) verifyZeroInteractions(openDashboard) - verifyZeroInteractions(getProfile) } @Test @@ -286,27 +288,25 @@ class HomeDashboardViewModelTest { } @Test - fun `should proceed with getting profile and opening dashboard when view is created`() { + fun `should proceed opening dashboard when view is created`() { // SETUP stubGetConfig(Either.Right(config)) stubObserveEvents(params = InterceptEvents.Params(context = config.home)) stubOpenDashboard() + stubObserveProfile() // TESTING vm = buildViewModel() vm.onViewCreated() - verify(getProfile, times(1)).invoke(any(), any(), any()) - runBlockingTest { verify(openDashboard, times(1)).invoke(eq(null)) } } - @Test fun `should start creating page when requested from UI`() { @@ -395,7 +395,24 @@ class HomeDashboardViewModelTest { private fun stubGetDefaultObjectType(type: String? = null, name: String? = null) { getDefaultEditorType.stub { - onBlocking { invoke(Unit) } doReturn Either.Right(GetDefaultEditorType.Response(type, name)) + onBlocking { invoke(Unit) } doReturn Either.Right( + GetDefaultEditorType.Response( + type, + name + ) + ) + } + } + + private fun stubObserveProfile() { + getProfile.stub { + on { + observe( + subscription = any(), + keys = any(), + dispatcher = any() + ) + } doReturn emptyFlow() } } } \ No newline at end of file diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/extension/DashboardViewExtensionKtTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/extension/DashboardViewExtensionKtTest.kt index 041ca5a64b..fb202ac85e 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/extension/DashboardViewExtensionKtTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/extension/DashboardViewExtensionKtTest.kt @@ -670,7 +670,6 @@ class DashboardViewExtensionKtTest { val target1 = MockDataFactory.randomUuid() val id2 = MockDataFactory.randomUuid() val target2 = MockDataFactory.randomUuid() - val id3 = MockDataFactory.randomUuid() val views = listOf( DashboardView.Document( @@ -686,11 +685,6 @@ class DashboardViewExtensionKtTest { target = target2, title = "Title2" ), - DashboardView.Profile( - isArchived = false, - id = id3, - name = "Profile" - ), DashboardView.Document( isArchived = false, id = "profileId",