diff --git a/app/src/androidTest/java/com/anytypeio/anytype/features/editor/base/EditorTestSetup.kt b/app/src/androidTest/java/com/anytypeio/anytype/features/editor/base/EditorTestSetup.kt index bc34242820..e842c161a6 100644 --- a/app/src/androidTest/java/com/anytypeio/anytype/features/editor/base/EditorTestSetup.kt +++ b/app/src/androidTest/java/com/anytypeio/anytype/features/editor/base/EditorTestSetup.kt @@ -61,10 +61,8 @@ import com.anytypeio.anytype.domain.launch.GetDefaultObjectType import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider -import com.anytypeio.anytype.domain.networkmode.GetNetworkMode import com.anytypeio.anytype.domain.`object`.ConvertObjectToCollection import com.anytypeio.anytype.domain.`object`.ConvertObjectToSet -import com.anytypeio.anytype.domain.`object`.SetObjectInternalFlags import com.anytypeio.anytype.domain.`object`.UpdateDetail import com.anytypeio.anytype.domain.objects.DefaultStoreOfObjectTypes import com.anytypeio.anytype.domain.objects.DefaultStoreOfRelations @@ -83,8 +81,6 @@ import com.anytypeio.anytype.domain.relations.AddRelationToObject import com.anytypeio.anytype.domain.relations.SetRelationKey import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.domain.sets.FindObjectSetForType -import com.anytypeio.anytype.domain.status.InterceptThreadStatus -import com.anytypeio.anytype.domain.status.ThreadStatusChannel import com.anytypeio.anytype.domain.table.CreateTable import com.anytypeio.anytype.domain.table.FillTableRow import com.anytypeio.anytype.domain.templates.ApplyTemplate @@ -111,6 +107,7 @@ import com.anytypeio.anytype.presentation.editor.render.DefaultBlockViewRenderer import com.anytypeio.anytype.presentation.editor.selection.SelectionStateHolder import com.anytypeio.anytype.presentation.editor.toggle.ToggleStateHolder import com.anytypeio.anytype.presentation.search.ObjectSearchConstants +import com.anytypeio.anytype.presentation.sync.SpaceSyncAndP2PStatusProvider import com.anytypeio.anytype.presentation.templates.ObjectTypeTemplatesContainer import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory import com.anytypeio.anytype.presentation.util.Dispatcher @@ -206,7 +203,6 @@ open class EditorTestSetup { lateinit var applyTemplate: ApplyTemplate lateinit var createObjectAsMentionOrLink: CreateObjectAsMentionOrLink - lateinit var interceptThreadStatus: InterceptThreadStatus lateinit var setDocCoverImage: SetDocCoverImage lateinit var setDocImageIcon: SetDocumentImageIcon @@ -260,9 +256,6 @@ open class EditorTestSetup { @Mock lateinit var analytics: Analytics - @Mock - lateinit var threadStatusChannel: ThreadStatusChannel - @Mock lateinit var createTable: CreateTable @@ -278,9 +271,6 @@ open class EditorTestSetup { @Mock lateinit var fileLimitsEventChannel: FileLimitsEventChannel - @Mock - lateinit var setObjectInternalFlags: SetObjectInternalFlags - @Mock lateinit var spaceManager: SpaceManager @@ -290,15 +280,15 @@ open class EditorTestSetup { @Mock lateinit var templatesContainer: ObjectTypeTemplatesContainer - @Mock - lateinit var getNetworkMode: GetNetworkMode - @Mock lateinit var permissions: UserPermissionProvider @Mock lateinit var analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate + @Mock + lateinit var spaceSyncAndP2PStatusProvider: SpaceSyncAndP2PStatusProvider + lateinit var interceptFileLimitEvents: InterceptFileLimitEvents lateinit var addRelationToObject: AddRelationToObject @@ -351,7 +341,6 @@ open class EditorTestSetup { spaceManager = spaceManager ) getSearchObjects = SearchObjects(repo) - interceptThreadStatus = InterceptThreadStatus(channel = threadStatusChannel) downloadUnsplashImage = DownloadUnsplashImage(unsplashRepository) clearBlockContent = ClearBlockContent(repo) clearBlockStyle = ClearBlockStyle(repo) @@ -473,7 +462,6 @@ open class EditorTestSetup { spaceManager = spaceManager ), createObjectAsMentionOrLink = createObjectAsMentionOrLink, - interceptThreadStatus = interceptThreadStatus, analytics = analytics, dispatcher = Dispatcher.Default(), updateDetail = updateDetail, @@ -502,13 +490,13 @@ open class EditorTestSetup { templatesContainer = templatesContainer, storelessSubscriptionContainer = storelessSubscriptionContainer, dispatchers = appCoroutineDispatchers, - getNetworkMode = getNetworkMode, params = EditorViewModel.Params( ctx = root, space = SpaceId(defaultSpace) ), permissions = permissions, - analyticSpaceHelperDelegate = analyticSpaceHelperDelegate + analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, + syncStatusProvider = spaceSyncAndP2PStatusProvider ) } @@ -522,11 +510,9 @@ open class EditorTestSetup { } } - fun stubInterceptThreadStatus( - params: InterceptThreadStatus.Params = InterceptThreadStatus.Params(ctx = root) - ) { - interceptThreadStatus.stub { - onBlocking { build(params) } doReturn emptyFlow() + fun stubInterceptThreadStatus() { + spaceSyncAndP2PStatusProvider.stub { + onBlocking { observe() } doReturn emptyFlow() } } 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 7335c47df4..04e0bebd4f 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 @@ -49,8 +49,6 @@ import com.anytypeio.anytype.domain.search.DataViewSubscriptionContainer import com.anytypeio.anytype.domain.search.SubscriptionEventChannel import com.anytypeio.anytype.domain.sets.OpenObjectSet import com.anytypeio.anytype.domain.sets.SetQueryToObjectSet -import com.anytypeio.anytype.domain.status.InterceptThreadStatus -import com.anytypeio.anytype.domain.status.ThreadStatusChannel import com.anytypeio.anytype.domain.templates.CreateTemplate import com.anytypeio.anytype.domain.templates.GetTemplates import com.anytypeio.anytype.domain.unsplash.DownloadUnsplashImage @@ -69,6 +67,7 @@ import com.anytypeio.anytype.presentation.sets.ObjectSetViewModelFactory import com.anytypeio.anytype.presentation.sets.state.DefaultObjectStateReducer import com.anytypeio.anytype.presentation.sets.subscription.DefaultDataViewSubscription import com.anytypeio.anytype.presentation.sets.viewer.ViewerDelegate +import com.anytypeio.anytype.presentation.sync.SpaceSyncAndP2PStatusProvider import com.anytypeio.anytype.presentation.templates.ObjectTypeTemplatesContainer import com.anytypeio.anytype.presentation.util.Dispatcher import com.anytypeio.anytype.presentation.widgets.collection.DateProviderImpl @@ -90,7 +89,6 @@ abstract class TestObjectSetSetup { private lateinit var updateText: UpdateText private lateinit var createDataViewObject: CreateDataViewObject private lateinit var closeBlock: CloseBlock - private lateinit var interceptThreadStatus: InterceptThreadStatus private lateinit var setDocCoverImage: SetDocCoverImage private lateinit var downloadUnsplashImage: DownloadUnsplashImage private lateinit var setDataViewQuery: SetDataViewQuery @@ -125,7 +123,7 @@ abstract class TestObjectSetSetup { @Mock lateinit var interceptEvents: InterceptEvents @Mock - lateinit var threadStatusChannel: ThreadStatusChannel + lateinit var spaceSyncAndP2PStatusProvider: SpaceSyncAndP2PStatusProvider @Mock lateinit var subscriptionEventChannel: SubscriptionEventChannel @Mock @@ -249,7 +247,6 @@ abstract class TestObjectSetSetup { ) setObjectDetails = UpdateDetail(repo) updateDataViewViewer = UpdateDataViewViewer(repo, dispatchers) - interceptThreadStatus = InterceptThreadStatus(channel = threadStatusChannel) closeBlock = CloseBlock(repo, dispatchers) urlBuilder = UrlBuilder(gateway) downloadUnsplashImage = DownloadUnsplashImage(unsplashRepo) @@ -270,7 +267,6 @@ abstract class TestObjectSetSetup { openObjectSet = openObjectSet, closeBlock = closeBlock, interceptEvents = interceptEvents, - interceptThreadStatus = interceptThreadStatus, createDataViewObject = createDataViewObject, setObjectDetails = setObjectDetails, updateText = updateText, @@ -283,7 +279,6 @@ abstract class TestObjectSetSetup { setDocCoverImage = setDocCoverImage, delegator = delegator, createObject = createObject, - cancelSearchSubscription = cancelSearchSubscription, paginator = paginator, database = database, dataViewSubscriptionContainer = dataViewSubscriptionContainer, @@ -304,14 +299,14 @@ abstract class TestObjectSetSetup { getObjectTypes = getObjectTypes, storelessSubscriptionContainer = storelessSubscriptionContainer, dispatchers = appCoroutineDispatchers, - getNetworkMode = getNetworkMode, dateProvider = dateProvider, params = ObjectSetViewModel.Params( ctx = ctx, space = SpaceId(defaultSpace) ), permissions = permissions, - analyticSpaceHelperDelegate = analyticSpaceHelperDelegate + analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, + spaceSyncAndP2PStatusProvider = spaceSyncAndP2PStatusProvider, ) } @@ -322,8 +317,8 @@ abstract class TestObjectSetSetup { } fun stubInterceptThreadStatus() { - threadStatusChannel.stub { - onBlocking { observe(any()) } doReturn emptyFlow() + spaceSyncAndP2PStatusProvider.stub { + onBlocking { observe() } doReturn emptyFlow() } } diff --git a/app/src/main/java/com/anytypeio/anytype/app/DefaultFeatureToggles.kt b/app/src/main/java/com/anytypeio/anytype/app/DefaultFeatureToggles.kt index e632882506..5519f086b8 100644 --- a/app/src/main/java/com/anytypeio/anytype/app/DefaultFeatureToggles.kt +++ b/app/src/main/java/com/anytypeio/anytype/app/DefaultFeatureToggles.kt @@ -1,16 +1,12 @@ package com.anytypeio.anytype.app -import android.content.Context -import android.content.SharedPreferences import com.anytypeio.anytype.BuildConfig import com.anytypeio.anytype.core_utils.tools.FeatureToggles import com.anytypeio.anytype.device.BuildProvider import javax.inject.Inject class DefaultFeatureToggles @Inject constructor( - private val context: Context, - @TogglePrefs private val prefs: SharedPreferences, - private val buildProvider: BuildProvider + buildProvider: BuildProvider ) : FeatureToggles { override val isLogFromGoProcess = @@ -18,8 +14,6 @@ class DefaultFeatureToggles @Inject constructor( override val isLogMiddlewareInteraction = BuildConfig.LOG_MW_INTERACTION && buildProvider.isDebug() - override val excludeThreadStatusLogging: Boolean = true - override val isLogEditorViewModelEvents = BuildConfig.LOG_EDITOR_VIEWMODEL_EVENTS && buildProvider.isDebug() 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 b18cdff42e..6f443a36ea 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 @@ -54,8 +54,8 @@ import com.anytypeio.anytype.domain.icon.SetDocumentImageIcon import com.anytypeio.anytype.domain.launch.GetDefaultObjectType import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer import com.anytypeio.anytype.domain.misc.UrlBuilder +import com.anytypeio.anytype.domain.multiplayer.ActiveSpaceMemberSubscriptionContainer import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider -import com.anytypeio.anytype.domain.networkmode.GetNetworkMode import com.anytypeio.anytype.domain.`object`.ConvertObjectToCollection import com.anytypeio.anytype.domain.`object`.ConvertObjectToSet import com.anytypeio.anytype.domain.`object`.DuplicateObject @@ -79,8 +79,6 @@ import com.anytypeio.anytype.domain.relations.AddRelationToObject import com.anytypeio.anytype.domain.relations.SetRelationKey import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.domain.sets.FindObjectSetForType -import com.anytypeio.anytype.domain.status.InterceptThreadStatus -import com.anytypeio.anytype.domain.status.ThreadStatusChannel import com.anytypeio.anytype.domain.table.CreateTable import com.anytypeio.anytype.domain.table.CreateTableColumn import com.anytypeio.anytype.domain.table.CreateTableRow @@ -99,7 +97,9 @@ import com.anytypeio.anytype.domain.unsplash.DownloadUnsplashImage import com.anytypeio.anytype.domain.unsplash.UnsplashRepository import com.anytypeio.anytype.domain.workspace.FileLimitsEventChannel import com.anytypeio.anytype.domain.workspace.InterceptFileLimitEvents +import com.anytypeio.anytype.domain.workspace.P2PStatusChannel import com.anytypeio.anytype.domain.workspace.SpaceManager +import com.anytypeio.anytype.domain.workspace.SpaceSyncStatusChannel import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate import com.anytypeio.anytype.presentation.common.Action import com.anytypeio.anytype.presentation.common.Delegator @@ -124,6 +124,7 @@ import com.anytypeio.anytype.presentation.relations.providers.ObjectRelationProv import com.anytypeio.anytype.presentation.relations.providers.ObjectRelationProvider.Companion.INTRINSIC_PROVIDER_TYPE import com.anytypeio.anytype.presentation.relations.providers.ObjectValueProvider import com.anytypeio.anytype.presentation.relations.providers.RelationListProvider +import com.anytypeio.anytype.presentation.sync.SpaceSyncAndP2PStatusProvider import com.anytypeio.anytype.presentation.templates.ObjectTypeTemplatesContainer import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory import com.anytypeio.anytype.presentation.util.DefaultCopyFileToCacheDirectory @@ -251,7 +252,6 @@ object EditorSessionModule { openPage: OpenPage, closePage: CloseBlock, interceptEvents: InterceptEvents, - interceptThreadStatus: InterceptThreadStatus, updateLinkMarks: UpdateLinkMarks, removeLinkMark: RemoveLinkMark, createObjectSet: CreateObjectSet, @@ -288,8 +288,8 @@ object EditorSessionModule { templatesContainer: ObjectTypeTemplatesContainer, storelessSubscriptionContainer: StorelessSubscriptionContainer, dispatchers: AppCoroutineDispatchers, - getNetworkMode: GetNetworkMode, - analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate + analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, + syncStatusProvider: SpaceSyncAndP2PStatusProvider ): EditorViewModelFactory = EditorViewModelFactory( params = params, permissions = permissions, @@ -298,7 +298,6 @@ object EditorSessionModule { createBlockLinkWithObject = createBlockLinkWithObject, createObjectAsMentionOrLink = createObjectAsMentionOrLink, interceptEvents = interceptEvents, - interceptThreadStatus = interceptThreadStatus, updateLinkMarks = updateLinkMarks, removeLinkMark = removeLinkMark, documentEventReducer = documentExternalEventReducer, @@ -333,8 +332,8 @@ object EditorSessionModule { templatesContainer = templatesContainer, dispatchers = dispatchers, storelessSubscriptionContainer = storelessSubscriptionContainer, - getNetworkMode = getNetworkMode, - analyticSpaceHelperDelegate = analyticSpaceHelperDelegate + analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, + syncStatusProvider = syncStatusProvider ) @JvmStatic @@ -561,16 +560,6 @@ object EditorUseCaseModule { context = Dispatchers.IO ) - @JvmStatic - @Provides - @PerScreen - fun providesInterceptThreadStatusUseCase( - channel: ThreadStatusChannel - ): InterceptThreadStatus = InterceptThreadStatus( - channel = channel, - context = Dispatchers.IO - ) - @JvmStatic @Provides @PerScreen @@ -1207,6 +1196,18 @@ object EditorUseCaseModule { spaceManager = spaceManager ) + @Provides + @PerScreen + fun provideSpaceSyncStatusProvider( + activeSpace: ActiveSpaceMemberSubscriptionContainer, + syncChannel: SpaceSyncStatusChannel, + p2PStatusChannel: P2PStatusChannel + ): SpaceSyncAndP2PStatusProvider = SpaceSyncAndP2PStatusProvider.Impl( + activeSpace = activeSpace, + spaceSyncStatusChannel = syncChannel, + p2PStatusChannel = p2PStatusChannel + ) + @Module interface Bindings { 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 d7924838b4..1df3355ca6 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 @@ -36,8 +36,8 @@ import com.anytypeio.anytype.domain.launch.GetDefaultObjectType import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer import com.anytypeio.anytype.domain.misc.DateProvider import com.anytypeio.anytype.domain.misc.UrlBuilder +import com.anytypeio.anytype.domain.multiplayer.ActiveSpaceMemberSubscriptionContainer import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider -import com.anytypeio.anytype.domain.networkmode.GetNetworkMode import com.anytypeio.anytype.domain.`object`.ConvertObjectToCollection import com.anytypeio.anytype.domain.`object`.DuplicateObject import com.anytypeio.anytype.domain.`object`.DuplicateObjects @@ -60,13 +60,13 @@ import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.domain.search.SubscriptionEventChannel import com.anytypeio.anytype.domain.sets.OpenObjectSet import com.anytypeio.anytype.domain.sets.SetQueryToObjectSet -import com.anytypeio.anytype.domain.status.InterceptThreadStatus -import com.anytypeio.anytype.domain.status.ThreadStatusChannel import com.anytypeio.anytype.domain.templates.CreateTemplate import com.anytypeio.anytype.domain.templates.GetTemplates import com.anytypeio.anytype.domain.unsplash.DownloadUnsplashImage import com.anytypeio.anytype.domain.unsplash.UnsplashRepository +import com.anytypeio.anytype.domain.workspace.P2PStatusChannel import com.anytypeio.anytype.domain.workspace.SpaceManager +import com.anytypeio.anytype.domain.workspace.SpaceSyncStatusChannel import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate import com.anytypeio.anytype.presentation.common.Action import com.anytypeio.anytype.presentation.common.Delegator @@ -94,6 +94,7 @@ import com.anytypeio.anytype.presentation.sets.subscription.DataViewSubscription import com.anytypeio.anytype.presentation.sets.subscription.DefaultDataViewSubscription import com.anytypeio.anytype.presentation.sets.viewer.DefaultViewerDelegate import com.anytypeio.anytype.presentation.sets.viewer.ViewerDelegate +import com.anytypeio.anytype.presentation.sync.SpaceSyncAndP2PStatusProvider import com.anytypeio.anytype.presentation.templates.ObjectTypeTemplatesContainer import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory import com.anytypeio.anytype.presentation.util.DefaultCopyFileToCacheDirectory @@ -212,7 +213,6 @@ object ObjectSetModule { setObjectDetails: UpdateDetail, updateText: UpdateText, interceptEvents: InterceptEvents, - interceptThreadStatus: InterceptThreadStatus, createDataViewObject: CreateDataViewObject, createObject: CreateObject, dispatcher: Dispatcher, @@ -224,7 +224,6 @@ object ObjectSetModule { downloadUnsplashImage: DownloadUnsplashImage, setDocCoverImage: SetDocCoverImage, dataViewSubscriptionContainer: DataViewSubscriptionContainer, - cancelSearchSubscription: CancelSearchSubscription, setQueryToObjectSet: SetQueryToObjectSet, database: ObjectSetDatabase, paginator: ObjectSetPaginator, @@ -244,10 +243,10 @@ object ObjectSetModule { spaceManager: SpaceManager, storelessSubscriptionContainer: StorelessSubscriptionContainer, dispatchers: AppCoroutineDispatchers, - getNetworkMode: GetNetworkMode, dateProvider: DateProvider, permissions: UserPermissionProvider, - analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate + analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, + spaceSyncAndP2PStatusProvider: SpaceSyncAndP2PStatusProvider ): ObjectSetViewModelFactory = ObjectSetViewModelFactory( params = params, openObjectSet = openObjectSet, @@ -256,7 +255,6 @@ object ObjectSetModule { createDataViewObject = createDataViewObject, updateText = updateText, interceptEvents = interceptEvents, - interceptThreadStatus = interceptThreadStatus, dispatcher = dispatcher, delegator = delegator, coverImageHashProvider = coverImageHashProvider, @@ -267,7 +265,6 @@ object ObjectSetModule { setDocCoverImage = setDocCoverImage, createObject = createObject, dataViewSubscriptionContainer = dataViewSubscriptionContainer, - cancelSearchSubscription = cancelSearchSubscription, setQueryToObjectSet = setQueryToObjectSet, database = database, paginator = paginator, @@ -287,10 +284,10 @@ object ObjectSetModule { createTemplate = createTemplate, storelessSubscriptionContainer = storelessSubscriptionContainer, dispatchers = dispatchers, - getNetworkMode = getNetworkMode, dateProvider = dateProvider, permissions = permissions, - analyticSpaceHelperDelegate = analyticSpaceHelperDelegate + analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, + spaceSyncAndP2PStatusProvider = spaceSyncAndP2PStatusProvider ) @JvmStatic @@ -372,15 +369,6 @@ object ObjectSetModule { context = Dispatchers.IO ) - @JvmStatic - @Provides - @PerScreen - fun provideInterceptThreadStatus( - channel: ThreadStatusChannel - ): InterceptThreadStatus = InterceptThreadStatus( - channel = channel - ) - @JvmStatic @Provides @PerScreen @@ -723,6 +711,18 @@ object ObjectSetModule { analytics = analytics, dispatcher = dispatcher ) + + @Provides + @PerScreen + fun provideSpaceSyncStatusProvider( + activeSpace: ActiveSpaceMemberSubscriptionContainer, + syncChannel: SpaceSyncStatusChannel, + p2PStatusChannel: P2PStatusChannel + ): SpaceSyncAndP2PStatusProvider = SpaceSyncAndP2PStatusProvider.Impl( + activeSpace = activeSpace, + spaceSyncStatusChannel = syncChannel, + p2PStatusChannel = p2PStatusChannel + ) } data class DefaultComponentParam( 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 b6f5e97643..9f5fd8a01e 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 @@ -9,12 +9,9 @@ import com.anytypeio.anytype.data.auth.event.FileLimitsDataChannel import com.anytypeio.anytype.data.auth.event.FileLimitsRemoteChannel 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.account.AccountStatusChannel 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.domain.workspace.FileLimitsEventChannel import com.anytypeio.anytype.middleware.EventProxy import com.anytypeio.anytype.middleware.interactor.AccountStatusMiddlewareChannel @@ -22,7 +19,6 @@ import com.anytypeio.anytype.middleware.interactor.EventHandler import com.anytypeio.anytype.middleware.interactor.FileLimitsMiddlewareChannel import com.anytypeio.anytype.middleware.interactor.MiddlewareEventChannel import com.anytypeio.anytype.middleware.interactor.MiddlewareSubscriptionEventChannel -import com.anytypeio.anytype.middleware.interactor.ThreadStatusMiddlewareChannel import com.anytypeio.anytype.presentation.common.PayloadDelegator import dagger.Binds import dagger.Module @@ -54,27 +50,6 @@ object EventModule { featureToggles: FeatureToggles ): EventRemoteChannel = MiddlewareEventChannel(events = proxy, featureToggles = featureToggles) - @JvmStatic - @Provides - @Singleton - fun provideThreadStatusChannel( - channel: ThreadStatusDataChannel - ): ThreadStatusChannel = channel - - @JvmStatic - @Provides - @Singleton - fun provideThreadStatusDataChannel( - remote: ThreadStatusRemoteChannel - ): ThreadStatusDataChannel = ThreadStatusDataChannel(remote) - - @JvmStatic - @Provides - @Singleton - fun provideThreadStatusRemoteChannel( - proxy: EventProxy - ): ThreadStatusRemoteChannel = ThreadStatusMiddlewareChannel(events = proxy) - @JvmStatic @Provides @Singleton diff --git a/app/src/main/java/com/anytypeio/anytype/di/main/WorkspaceModule.kt b/app/src/main/java/com/anytypeio/anytype/di/main/WorkspaceModule.kt index 53c8305349..37f4ee7a2a 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/main/WorkspaceModule.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/main/WorkspaceModule.kt @@ -1,11 +1,20 @@ package com.anytypeio.anytype.di.main +import com.anytypeio.anytype.data.auth.status.P2PStatusDataChannel +import com.anytypeio.anytype.data.auth.status.P2PStatusRemoteChannel +import com.anytypeio.anytype.data.auth.status.SpaceStatusDataChannel +import com.anytypeio.anytype.data.auth.status.SpaceStatusRemoteChannel import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers import com.anytypeio.anytype.domain.block.repo.BlockRepository import com.anytypeio.anytype.domain.config.ConfigStorage import com.anytypeio.anytype.domain.debugging.Logger +import com.anytypeio.anytype.domain.workspace.P2PStatusChannel import com.anytypeio.anytype.domain.workspace.SpaceManager +import com.anytypeio.anytype.domain.workspace.SpaceSyncStatusChannel import com.anytypeio.anytype.domain.workspace.WorkspaceManager +import com.anytypeio.anytype.middleware.EventProxy +import com.anytypeio.anytype.middleware.interactor.P2PStatusRemoteChannelImpl +import com.anytypeio.anytype.middleware.interactor.SpaceSyncStatusRemoteChannelImpl import dagger.Module import dagger.Provides import javax.inject.Singleton @@ -14,7 +23,7 @@ import javax.inject.Singleton object WorkspaceModule { @Provides @Singleton - fun manager() : WorkspaceManager = WorkspaceManager.DefaultWorkspaceManager() + fun manager(): WorkspaceManager = WorkspaceManager.DefaultWorkspaceManager() @Provides @Singleton @@ -23,10 +32,37 @@ object WorkspaceModule { dispatchers: AppCoroutineDispatchers, configStorage: ConfigStorage, logger: Logger - ) : SpaceManager = SpaceManager.Impl( + ): SpaceManager = SpaceManager.Impl( dispatchers = dispatchers, repo = repo, configStorage = configStorage, logger = logger ) + + @JvmStatic + @Provides + @Singleton + fun provideSyncStatusRemoteChannel( + proxy: EventProxy + ): SpaceStatusRemoteChannel = SpaceSyncStatusRemoteChannelImpl(events = proxy) + + @Provides + @Singleton + fun spaceSyncStatusChannel( + channel: SpaceStatusRemoteChannel + ): SpaceSyncStatusChannel = SpaceStatusDataChannel(channel) + + @JvmStatic + @Provides + @Singleton + fun p2pStatusRemoteChannel( + proxy: EventProxy + ): P2PStatusRemoteChannel = P2PStatusRemoteChannelImpl(events = proxy) + + @JvmStatic + @Provides + @Singleton + fun p2pStatusChannel( + channel: P2PStatusRemoteChannel + ): P2PStatusChannel = P2PStatusDataChannel(channel) } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt index 183ce4defa..0c2dd2da72 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt @@ -57,8 +57,6 @@ import com.anytypeio.anytype.core_models.primitives.SpaceId import com.anytypeio.anytype.core_ui.extensions.addTextFromSelectedStart import com.anytypeio.anytype.core_ui.extensions.color import com.anytypeio.anytype.core_ui.extensions.cursorYBottomCoordinate -import com.anytypeio.anytype.core_ui.extensions.getLabelText -import com.anytypeio.anytype.core_ui.extensions.getToastMsg import com.anytypeio.anytype.core_ui.features.editor.BlockAdapter import com.anytypeio.anytype.core_ui.features.editor.DragAndDropAdapterDelegate import com.anytypeio.anytype.core_ui.features.editor.scrollandmove.DefaultScrollAndMoveTargetDescriptor @@ -67,6 +65,7 @@ import com.anytypeio.anytype.core_ui.features.editor.scrollandmove.ScrollAndMove import com.anytypeio.anytype.core_ui.menu.ObjectTypePopupMenu import com.anytypeio.anytype.core_ui.reactive.clicks import com.anytypeio.anytype.core_ui.reactive.longClicks +import com.anytypeio.anytype.core_ui.syncstatus.SpaceSyncStatusScreen import com.anytypeio.anytype.core_ui.tools.ClipboardInterceptor import com.anytypeio.anytype.core_ui.tools.EditorHeaderOverlayDetector import com.anytypeio.anytype.core_ui.tools.LastItemBottomOffsetDecorator @@ -126,7 +125,7 @@ import com.anytypeio.anytype.presentation.editor.model.EditorFooter import com.anytypeio.anytype.presentation.editor.template.SelectTemplateViewState import com.anytypeio.anytype.presentation.objects.ObjectIcon import com.anytypeio.anytype.presentation.relations.value.tagstatus.RelationContext -import com.anytypeio.anytype.presentation.sync.SyncStatusView +import com.anytypeio.anytype.presentation.sync.SpaceSyncAndP2PStatusState import com.anytypeio.anytype.ui.alert.AlertUpdateAppFragment import com.anytypeio.anytype.ui.base.NavigationFragment import com.anytypeio.anytype.ui.base.navigation @@ -393,9 +392,6 @@ open class EditorFragment : NavigationFragment(R.layout.f ) { isHeaderOverlaid -> if (isHeaderOverlaid) { binding.topToolbar.setBackgroundColor(0) - binding.topToolbar.statusText.animate().alpha(1f) - .setDuration(DEFAULT_TOOLBAR_ANIM_DURATION) - .start() binding.topToolbar.container.animate().alpha(0f) .setDuration(DEFAULT_TOOLBAR_ANIM_DURATION) .start() @@ -409,9 +405,6 @@ open class EditorFragment : NavigationFragment(R.layout.f } } else { binding.topToolbar.setBackgroundColor(requireContext().color(R.color.defaultCanvasColor)) - binding.topToolbar.statusText.animate().alpha(0f) - .setDuration(DEFAULT_TOOLBAR_ANIM_DURATION) - .start() binding.topToolbar.container.animate().alpha(1f) .setDuration(DEFAULT_TOOLBAR_ANIM_DURATION) .start() @@ -746,6 +739,17 @@ open class EditorFragment : NavigationFragment(R.layout.f } } + binding.syncStatusWidget.apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + SpaceSyncStatusScreen( + uiState = vm.syncStatusWidget.collectAsStateWithLifecycle().value, + onDismiss = vm::onSyncWidgetDismiss, + scope = lifecycleScope + ) + } + } + BottomSheetBehavior.from(binding.styleToolbarMain).state = BottomSheetBehavior.STATE_HIDDEN BottomSheetBehavior.from(binding.styleToolbarOther).state = BottomSheetBehavior.STATE_HIDDEN BottomSheetBehavior.from(binding.styleToolbarColors).state = @@ -810,13 +814,7 @@ open class EditorFragment : NavigationFragment(R.layout.f } .launchIn(lifecycleScope) - vm.syncStatus.onEach { status -> bindSyncStatus(status) }.launchIn(lifecycleScope) - vm.isSyncStatusVisible.onEach { isSyncStatusVisible -> - if (isSyncStatusVisible) - binding.topToolbar.findViewById(R.id.statusContainer).visible() - else - binding.topToolbar.findViewById(R.id.statusContainer).invisible() - }.launchIn(lifecycleScope) + vm.spaceSyncStatus.onEach { status -> bindSyncStatus(status) }.launchIn(lifecycleScope) vm.isUndoEnabled.onEach { // TODO @@ -866,18 +864,16 @@ open class EditorFragment : NavigationFragment(R.layout.f } } - private fun bindSyncStatus(status: SyncStatusView?) { + private fun bindSyncStatus(status: SpaceSyncAndP2PStatusState) { binding.topToolbar.status.bind(status) - if (status == null) { + if (status is SpaceSyncAndP2PStatusState.Initial) { binding.topToolbar.hideStatusContainer() } else { binding.topToolbar.showStatusContainer() } - val tvStatus = binding.topToolbar.statusText - binding.topToolbar.statusContainer.setOnClickListener { - toast(status.getToastMsg(requireContext())) + binding.topToolbar.status.setOnClickListener { + vm.onSyncStatusBadgeClicked() } - tvStatus.text = status?.getLabelText(requireContext()) } override fun onDestroyView() { diff --git a/app/src/main/java/com/anytypeio/anytype/ui/sets/ObjectSetFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/sets/ObjectSetFragment.kt index c66875581c..9785aba303 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/sets/ObjectSetFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/sets/ObjectSetFragment.kt @@ -45,8 +45,6 @@ import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.Key import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.core_models.primitives.SpaceId -import com.anytypeio.anytype.core_ui.extensions.getLabelText -import com.anytypeio.anytype.core_ui.extensions.getToastMsg import com.anytypeio.anytype.core_ui.extensions.setEmojiOrNull import com.anytypeio.anytype.core_ui.features.dataview.ViewerGridAdapter import com.anytypeio.anytype.core_ui.features.dataview.ViewerGridHeaderAdapter @@ -56,6 +54,7 @@ import com.anytypeio.anytype.core_ui.reactive.clicks import com.anytypeio.anytype.core_ui.reactive.editorActionEvents import com.anytypeio.anytype.core_ui.reactive.longClicks import com.anytypeio.anytype.core_ui.reactive.touches +import com.anytypeio.anytype.core_ui.syncstatus.SpaceSyncStatusScreen import com.anytypeio.anytype.core_ui.tools.DefaultTextWatcher import com.anytypeio.anytype.core_ui.views.ButtonPrimarySmallIcon import com.anytypeio.anytype.core_ui.widgets.FeaturedRelationGroupWidget @@ -101,7 +100,7 @@ import com.anytypeio.anytype.presentation.sets.ViewerLayoutWidgetUi import com.anytypeio.anytype.presentation.sets.ViewersWidgetUi import com.anytypeio.anytype.presentation.sets.isVisible import com.anytypeio.anytype.presentation.sets.model.Viewer -import com.anytypeio.anytype.presentation.sync.SyncStatusView +import com.anytypeio.anytype.presentation.sync.SpaceSyncAndP2PStatusState import com.anytypeio.anytype.ui.base.NavigationFragment import com.anytypeio.anytype.ui.editor.cover.SelectCoverObjectSetFragment import com.anytypeio.anytype.ui.editor.modals.IconPickerFragmentBase @@ -446,6 +445,17 @@ open class ObjectSetFragment : ) } } + + binding.syncStatusWidget.apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + SpaceSyncStatusScreen( + uiState = vm.syncStatusWidget.collectAsStateWithLifecycle().value, + onDismiss = vm::onSyncWidgetDismiss, + scope = lifecycleScope + ) + } + } } private fun setupWindowInsetAnimation() { @@ -524,7 +534,6 @@ open class ObjectSetFragment : override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) vm.navigation.observe(viewLifecycleOwner, navObserver) - lifecycleScope.subscribe(vm.status) { setStatus(it) } lifecycleScope.subscribe(vm.isCustomizeViewPanelVisible) { isCustomizeViewPanelVisible -> if (isCustomizeViewPanelVisible) showBottomPanel() else hideBottomPanel() } @@ -539,12 +548,10 @@ open class ObjectSetFragment : } } - private fun setStatus(status: SyncStatusView?) { + private fun setStatus(status: SpaceSyncAndP2PStatusState?) { binding.topToolbar.root.findViewById(R.id.statusBadge).bind(status) - val tvStatus = binding.topToolbar.root.findViewById(R.id.tvStatus) - tvStatus.text = status?.getLabelText(requireContext()) topToolbarStatusContainer.setOnClickListener { - toast(status.getToastMsg(requireContext())) + vm.onSyncStatusBadgeClicked() } } @@ -1319,6 +1326,8 @@ open class ObjectSetFragment : } jobs += lifecycleScope.subscribe(vm.toasts) { toast(it) } + jobs += lifecycleScope.subscribe(vm.spaceSyncStatus) { setStatus(it) } + subscribe(vm.icon) { icon -> binding.bottomToolbar.bind(icon) } diff --git a/app/src/main/java/com/anytypeio/anytype/ui/templates/EditorTemplateFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/templates/EditorTemplateFragment.kt index 776af25e33..1e9e477e50 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/templates/EditorTemplateFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/templates/EditorTemplateFragment.kt @@ -76,7 +76,7 @@ class EditorTemplateFragment : EditorFragment() { topMargin = dimen(R.dimen.default_toolbar_height) } binding.topToolbar.title.text = getString(R.string.templates_menu_edit) - binding.topToolbar.statusContainer.hide() + binding.topToolbar.status.hide() } else { binding.topToolbar.hide() } diff --git a/app/src/main/res/layout/fragment_editor.xml b/app/src/main/res/layout/fragment_editor.xml index 526eb99628..59d90c2f52 100644 --- a/app/src/main/res/layout/fragment_editor.xml +++ b/app/src/main/res/layout/fragment_editor.xml @@ -299,4 +299,10 @@ android:layout_height="wrap_content" android:layout_gravity="bottom|center_horizontal" /> + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_object_set.xml b/app/src/main/res/layout/fragment_object_set.xml index 87de2681d2..c3b2b84143 100644 --- a/app/src/main/res/layout/fragment_object_set.xml +++ b/app/src/main/res/layout/fragment_object_set.xml @@ -188,4 +188,12 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toBottomOf="parent"/> + + \ No newline at end of file diff --git a/app/src/main/res/xml/fragment_object_set_scene.xml b/app/src/main/res/xml/fragment_object_set_scene.xml index 79899fbcc8..29b2d55d97 100644 --- a/app/src/main/res/xml/fragment_object_set_scene.xml +++ b/app/src/main/res/xml/fragment_object_set_scene.xml @@ -97,6 +97,12 @@ app:applyMotionScene="false" app:visibilityMode="ignore"/> + + + @@ -185,6 +191,12 @@ app:applyMotionScene="false" app:visibilityMode="ignore"/> + + + \ No newline at end of file diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/SyncStatus.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/SyncStatus.kt deleted file mode 100644 index 78f1d44399..0000000000 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/SyncStatus.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.anytypeio.anytype.core_models - -enum class SyncStatus { - UNKNOWN, - OFFLINE, - SYNCING, - SYNCED, - FAILED, - INCOMPATIBLE_VERSION -} \ No newline at end of file diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/multiplayer/Multiplayer.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/multiplayer/Multiplayer.kt index d66d4b0a7c..ecd3afb0c2 100644 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/multiplayer/Multiplayer.kt +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/multiplayer/Multiplayer.kt @@ -60,13 +60,16 @@ sealed class SpaceInviteError : Exception() { class InvalidInvite : SpaceInviteError() } -data class SpaceSyncUpdate( - val id: String, - val status: SpaceSyncStatus, - val network: SpaceSyncNetwork, - val error: SpaceSyncError?, - val syncingObjectsCounter: Long -) +sealed class SpaceSyncUpdate { + data object Initial : SpaceSyncUpdate() + data class Update( + val id: String, + val status: SpaceSyncStatus, + val network: SpaceSyncNetwork, + val error: SpaceSyncError, + val syncingObjectsCounter: Long + ) : SpaceSyncUpdate() +} enum class SpaceSyncStatus { SYNCED, @@ -88,11 +91,14 @@ enum class SpaceSyncError { NETWORK_ERROR } -data class P2PStatusUpdate( - val spaceId: String, - val status: P2PStatus, - val devicesCounter: Long -) +sealed class P2PStatusUpdate { + data object Initial : P2PStatusUpdate() + data class Update( + val spaceId: String, + val status: P2PStatus, + val devicesCounter: Long + ) : P2PStatusUpdate() +} enum class P2PStatus { NOT_CONNECTED, diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/extensions/ResExtension.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/extensions/ResExtension.kt index bd4ecd0ae6..12bb413f77 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/extensions/ResExtension.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/extensions/ResExtension.kt @@ -13,7 +13,6 @@ import com.anytypeio.anytype.core_ui.R import com.anytypeio.anytype.core_utils.const.MimeTypes import com.anytypeio.anytype.presentation.objects.ObjectLayoutView import com.anytypeio.anytype.presentation.sets.model.ColumnView -import com.anytypeio.anytype.presentation.sync.SyncStatusView fun Context.drawable( @DrawableRes id: Int @@ -293,30 +292,4 @@ fun RelationFormat.getPrettyName(): Int = when (this) { RelationFormat.OBJECT -> R.string.relation_format_object RelationFormat.RELATIONS -> R.string.relation_format_relation RelationFormat.UNDEFINED -> R.string.undefined -} - -fun SyncStatusView?.getLabelText(context: Context): String = when (this) { - SyncStatusView.Unknown -> context.getString(R.string.sync_status_unknown) - SyncStatusView.Offline -> context.getString(R.string.sync_status_offline) - SyncStatusView.Syncing -> context.getString(R.string.sync_status_syncing) - SyncStatusView.Failed -> context.getString(R.string.sync_status_failed) - SyncStatusView.IncompatibleVersion -> context.getString(R.string.sync_status_incompatible) - SyncStatusView.Synced.LocalOnly -> context.getString(R.string.sync_status_local_only) - SyncStatusView.Synced.AnyNetwork -> context.getString(R.string.sync_status_synced) - SyncStatusView.Synced.SelfHostedNetwork -> context.getString(R.string.sync_status_synced) - SyncStatusView.Synced.StagingNetwork -> context.getString(R.string.sync_status_synced) - else -> "" -} - -fun SyncStatusView?.getToastMsg(context: Context): String = when (this) { - SyncStatusView.Failed -> context.getString(R.string.sync_status_toast_failed) - SyncStatusView.IncompatibleVersion -> context.getString(R.string.sync_status_toast_incompatible) - SyncStatusView.Offline -> context.getString(R.string.sync_status_toast_offline) - SyncStatusView.Synced.AnyNetwork -> context.getString(R.string.sync_status_toast_synced) - SyncStatusView.Synced.LocalOnly -> context.getString(R.string.sync_status_toast_synced_local) - SyncStatusView.Synced.SelfHostedNetwork -> context.getString(R.string.sync_status_toast_synced_self_hosted) - SyncStatusView.Synced.StagingNetwork -> context.getString(R.string.sync_status_toast_synced_staging) - SyncStatusView.Syncing -> context.getString(R.string.sync_status_toast_syncing) - SyncStatusView.Unknown -> context.getString(R.string.sync_status_toast_unknown) - else -> "" } \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/syncstatus/SpaceSyncStatusScreen.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/syncstatus/SpaceSyncStatusScreen.kt new file mode 100644 index 0000000000..759fcca993 --- /dev/null +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/syncstatus/SpaceSyncStatusScreen.kt @@ -0,0 +1,543 @@ +package com.anytypeio.anytype.core_ui.syncstatus + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.Image +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.FractionalThreshold +import androidx.compose.material.Text +import androidx.compose.material.rememberSwipeableState +import androidx.compose.material.swipeable +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ElevatedCard +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.pluralStringResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp +import com.anytypeio.anytype.core_models.multiplayer.P2PStatus +import com.anytypeio.anytype.core_models.multiplayer.P2PStatusUpdate +import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncError +import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncNetwork +import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncStatus +import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncUpdate +import com.anytypeio.anytype.core_ui.R +import com.anytypeio.anytype.core_ui.foundation.Divider +import com.anytypeio.anytype.core_ui.views.BodyRegular +import com.anytypeio.anytype.core_ui.views.Relations3 +import com.anytypeio.anytype.core_ui.views.Title2 +import com.anytypeio.anytype.core_ui.widgets.DragStates +import com.anytypeio.anytype.presentation.sync.SyncStatusWidgetState +import kotlin.math.roundToInt +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterialApi::class) +@Composable +fun SpaceSyncStatusScreen( + uiState: SyncStatusWidgetState, + onDismiss: () -> Unit, + scope: CoroutineScope +) { + val isVisible = uiState is SyncStatusWidgetState.Success || uiState is SyncStatusWidgetState.Error + + val swappableState = rememberSwipeableState(DragStates.VISIBLE) + if (swappableState.isAnimationRunning && swappableState.targetValue == DragStates.DISMISSED) { + DisposableEffect(Unit) { + onDispose { + onDismiss() + } + } + } + + if (!isVisible) { + DisposableEffect(Unit) { + onDispose { + scope.launch { swappableState.snapTo(DragStates.VISIBLE) } + } + } + } + + val sizePx = with(LocalDensity.current) { LocalConfiguration.current.screenHeightDp.dp.toPx() } + + AnimatedVisibility( + visible = isVisible, + enter = slideInVertically { it }, + exit = slideOutVertically { it }, + modifier = Modifier + .swipeable( + state = swappableState, + orientation = Orientation.Vertical, + anchors = mapOf(0f to DragStates.VISIBLE, sizePx to DragStates.DISMISSED), + thresholds = { _, _ -> FractionalThreshold(0.3f) }) + .offset { IntOffset(0, swappableState.offset.value.roundToInt()) } + ) { + ElevatedCard( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(start = 8.dp, end = 8.dp, bottom = 10.dp), + colors = CardDefaults.cardColors( + containerColor = colorResource(id = R.color.background_secondary) + ), + elevation = CardDefaults.elevatedCardElevation( + defaultElevation = 16.dp + ) + ) { + when (uiState) { + is SyncStatusWidgetState.Error -> ErrorState() + SyncStatusWidgetState.Hidden -> LoadingState() + is SyncStatusWidgetState.Success -> SuccessState( + spaceSyncUpdate = uiState.spaceSyncUpdate, + p2pStatus = uiState.p2PStatusUpdate + ) + } + } + } +} + +enum class MenuVisibilityState(val fraction: Float) { Hidden(0f), Visible(1f) } + +@Composable +private fun ColumnScope.LoadingState() { + CircularProgressIndicator( + modifier = Modifier + .size(24.dp) + .padding(bottom = 16.dp) + .align(Alignment.CenterHorizontally), + color = colorResource(R.color.shape_secondary) + ) +} + +@Composable +private fun ColumnScope.ErrorState() { + Text( + text = stringResource(id = R.string.sync_status_get_error), + style = BodyRegular, + color = colorResource(R.color.palette_dark_red), + modifier = Modifier + .padding(8.dp) + .align(Alignment.CenterHorizontally) + ) +} + +@Composable +private fun SuccessState(spaceSyncUpdate: SpaceSyncUpdate, p2pStatus: P2PStatusUpdate) { + if (spaceSyncUpdate is SpaceSyncUpdate.Update) { + SpaceSyncStatusItem(spaceSyncUpdate) + } + Divider() + if (p2pStatus is P2PStatusUpdate.Update) { + P2PStatusItem(p2pStatus) + } + Spacer(modifier = Modifier.height(8.dp)) +} + +@Composable +private fun P2PStatusItem( + p2pStatus: P2PStatusUpdate.Update +) { + val networkCardSettings = getP2PCardSettings(p2pStatus = p2pStatus) + Row( + modifier = Modifier + .fillMaxWidth() + .height(72.dp) + .padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Image( + modifier = Modifier.wrapContentSize(), + painter = networkCardSettings.icon, + contentDescription = "p2p status icon", + alpha = networkCardSettings.alpha + ) + Column( + modifier = Modifier + .fillMaxWidth() + .padding(start = 12.dp) + ) { + Text( + text = networkCardSettings.mainText, + style = Title2, + color = colorResource(R.color.text_primary) + ) + if (networkCardSettings.secondaryText != null) { + Text( + text = networkCardSettings.secondaryText, + style = Relations3, + color = colorResource(R.color.text_secondary) + ) + } + } + } +} + +@Composable +private fun SpaceSyncStatusItem( + spaceSyncUpdate: SpaceSyncUpdate.Update +) { + val networkCardSettings = getNetworkCardSettings( + syncStatus = spaceSyncUpdate.status, + network = spaceSyncUpdate.network, + error = spaceSyncUpdate.error, + syncingObjectsCounter = spaceSyncUpdate.syncingObjectsCounter + ) + Row( + modifier = Modifier + .fillMaxWidth() + .height(72.dp) + .padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Image( + modifier = Modifier.wrapContentSize(), + painter = networkCardSettings.icon, + contentDescription = "dfas", + alpha = networkCardSettings.alpha + ) + Column( + modifier = Modifier + .fillMaxWidth() + .padding(start = 12.dp) + ) { + Text( + text = networkCardSettings.mainText, + style = Title2, + color = colorResource(R.color.text_primary) + ) + if (networkCardSettings.secondaryText != null) { + Text( + text = networkCardSettings.secondaryText, + style = Relations3, + color = colorResource(R.color.text_secondary) + ) + } + } + } +} + +@Composable +private fun getP2PCardSettings( + p2pStatus: P2PStatusUpdate.Update +): CardSettings { + return when (p2pStatus.status) { + P2PStatus.NOT_CONNECTED -> { + CardSettings( + icon = painterResource(R.drawable.ic_sync_p2p_default), + mainText = stringResource(id = R.string.sync_status_p2p_connecting), + ) + } + + P2PStatus.NOT_POSSIBLE -> { + CardSettings( + icon = painterResource(R.drawable.ic_sync_p2p_error), + mainText = stringResource(id = R.string.sync_status_p2p), + secondaryText = stringResource(id = R.string.sync_status_p2p_disabled) + ) + } + + P2PStatus.CONNECTED -> { + CardSettings( + icon = painterResource(R.drawable.ic_sync_p2p_connected), + mainText = stringResource(id = R.string.sync_status_p2p), + secondaryText = pluralStringResource( + id = R.plurals.sync_status_p2p_devices, + count = p2pStatus.devicesCounter.toInt(), + formatArgs = arrayOf(p2pStatus.devicesCounter.toInt()) + ) + ) + } + } +} + +@Composable +private fun getNetworkCardSettings( + syncStatus: SpaceSyncStatus, + network: SpaceSyncNetwork, + error: SpaceSyncError, + syncingObjectsCounter: Long +): CardSettings { + return when (network) { + SpaceSyncNetwork.ANYTYPE -> when (syncStatus) { + SpaceSyncStatus.SYNCED -> { + CardSettings( + icon = painterResource(R.drawable.ic_sync_net_connected), + mainText = stringResource(id = R.string.sync_status_anytype_network), + secondaryText = stringResource(id = R.string.sync_status_anytype_end_to_end) + ) + } + + SpaceSyncStatus.SYNCING -> { + CardSettings( + icon = painterResource(R.drawable.ic_sync_net_connected), + alpha = 0.5f, + withAnimation = true, + mainText = stringResource(id = R.string.sync_status_anytype_network), + secondaryText = pluralStringResource( + id = R.plurals.sync_status_network_items, + count = syncingObjectsCounter.toInt(), + formatArgs = arrayOf(syncingObjectsCounter.toInt()) + ) + ) + } + + SpaceSyncStatus.ERROR -> { + val errorText = getErrorText(error) + CardSettings( + icon = painterResource(R.drawable.ic_sync_net_error), + mainText = stringResource(id = R.string.sync_status_anytype_network), + secondaryText = stringResource(id = errorText) + ) + } + + SpaceSyncStatus.OFFLINE -> { + CardSettings( + icon = painterResource(R.drawable.ic_sync_net_default), + mainText = stringResource(id = R.string.sync_status_anytype_network), + secondaryText = stringResource(id = R.string.sync_status_anytype_network_no_connecting) + ) + } + } + + SpaceSyncNetwork.SELF_HOST -> { + when (syncStatus) { + SpaceSyncStatus.SYNCED -> { + CardSettings( + icon = painterResource(R.drawable.ic_sync_self_connected), + mainText = stringResource(id = R.string.sync_status_self_host), + secondaryText = stringResource(id = R.string.sync_status_self_host_synced) + ) + } + + SpaceSyncStatus.SYNCING -> { + CardSettings( + icon = painterResource(R.drawable.ic_sync_self_connected), + alpha = 0.5f, + withAnimation = true, + mainText = stringResource(id = R.string.sync_status_self_host), + secondaryText = pluralStringResource( + id = R.plurals.sync_status_self_host_syncing, + count = syncingObjectsCounter.toInt(), + formatArgs = arrayOf(syncingObjectsCounter.toInt()) + ) + ) + } + + SpaceSyncStatus.ERROR -> { + val errorText = getErrorText(error) + CardSettings( + icon = painterResource(R.drawable.ic_sync_self_error), + mainText = stringResource(id = R.string.sync_status_self_host), + secondaryText = stringResource(id = errorText) + ) + } + + SpaceSyncStatus.OFFLINE -> { + CardSettings( + icon = painterResource(R.drawable.ic_sync_self_default), + mainText = stringResource(id = R.string.sync_status_self_host), + secondaryText = stringResource(id = R.string.sync_status_anytype_network_no_connecting) + ) + } + } + } + + SpaceSyncNetwork.LOCAL_ONLY -> { + CardSettings( + icon = painterResource(R.drawable.ic_sync_local_only), + mainText = stringResource(id = R.string.sync_status_local_only_title), + secondaryText = stringResource(id = R.string.sync_status_data_backup) + ) + } + } +} + +private fun getErrorText(error: SpaceSyncError): Int { + return when (error) { + SpaceSyncError.NULL -> R.string.sync_status_unrecognized + SpaceSyncError.STORAGE_LIMIT_EXCEED -> R.string.sync_status_storage_limit_exceed + SpaceSyncError.INCOMPATIBLE_VERSION -> R.string.sync_status_incompatible_version + SpaceSyncError.NETWORK_ERROR -> R.string.sync_status_network_error + } +} + +private data class CardSettings( + val icon: Painter, + val alpha: Float = 1f, + val withAnimation: Boolean = false, + val mainText: String, + val secondaryText: String? = null +) + +@Preview(name = "AnytypeNetworkSynced", showBackground = true) +@Composable +fun SpaceSyncStatusPreview1() { + val spaceSyncUpdate = SpaceSyncUpdate.Update( + id = "1", + status = SpaceSyncStatus.SYNCED, + network = SpaceSyncNetwork.ANYTYPE, + error = SpaceSyncError.NULL, + syncingObjectsCounter = 0 + ) + SpaceSyncStatusItem(spaceSyncUpdate = spaceSyncUpdate) +} + +@Preview(name = "AnytypeNetworkSyncing", showBackground = true) +@Composable +fun SpaceSyncStatusPreview2() { + val spaceSyncUpdate = SpaceSyncUpdate.Update( + id = "1", + status = SpaceSyncStatus.SYNCING, + network = SpaceSyncNetwork.ANYTYPE, + error = SpaceSyncError.NULL, + syncingObjectsCounter = 2 + ) + SpaceSyncStatusItem(spaceSyncUpdate = spaceSyncUpdate) +} + +@Preview(name = "AnytypeNetworkError", showBackground = true) +@Composable +fun SpaceSyncStatusPreview3() { + val spaceSyncUpdate = SpaceSyncUpdate.Update( + id = "1", + status = SpaceSyncStatus.ERROR, + network = SpaceSyncNetwork.ANYTYPE, + error = SpaceSyncError.NETWORK_ERROR, + syncingObjectsCounter = 0 + ) + SpaceSyncStatusItem(spaceSyncUpdate = spaceSyncUpdate) +} + +@Preview(name = "AnytypeNetworkOffline", showBackground = true) +@Composable +fun SpaceSyncStatusPreview4() { + val spaceSyncUpdate = SpaceSyncUpdate.Update( + id = "1", + status = SpaceSyncStatus.OFFLINE, + network = SpaceSyncNetwork.ANYTYPE, + error = SpaceSyncError.NULL, + syncingObjectsCounter = 0 + ) + SpaceSyncStatusItem(spaceSyncUpdate = spaceSyncUpdate) +} + +@Preview(name = "SelfHostSynced", showBackground = true) +@Composable +fun SpaceSyncStatusPreview5() { + val spaceSyncUpdate = SpaceSyncUpdate.Update( + id = "1", + status = SpaceSyncStatus.SYNCED, + network = SpaceSyncNetwork.SELF_HOST, + error = SpaceSyncError.NULL, + syncingObjectsCounter = 0 + ) + SpaceSyncStatusItem(spaceSyncUpdate = spaceSyncUpdate) +} + +@Preview(name = "SelfHostSyncing", showBackground = true) +@Composable +fun SpaceSyncStatusPreview6() { + val spaceSyncUpdate = SpaceSyncUpdate.Update( + id = "1", + status = SpaceSyncStatus.SYNCING, + network = SpaceSyncNetwork.SELF_HOST, + error = SpaceSyncError.NULL, + syncingObjectsCounter = 2 + ) + SpaceSyncStatusItem(spaceSyncUpdate = spaceSyncUpdate) +} + +@Preview(name = "SelfHostError", showBackground = true) +@Composable +fun SpaceSyncStatusPreview7() { + val spaceSyncUpdate = SpaceSyncUpdate.Update( + id = "1", + status = SpaceSyncStatus.ERROR, + network = SpaceSyncNetwork.SELF_HOST, + error = SpaceSyncError.NETWORK_ERROR, + syncingObjectsCounter = 0 + ) + SpaceSyncStatusItem(spaceSyncUpdate = spaceSyncUpdate) +} + +@Preview(name = "SelfHostOffline", showBackground = true) +@Composable +fun SpaceSyncStatusPreview8() { + val spaceSyncUpdate = SpaceSyncUpdate.Update( + id = "1", + status = SpaceSyncStatus.OFFLINE, + network = SpaceSyncNetwork.SELF_HOST, + error = SpaceSyncError.NULL, + syncingObjectsCounter = 0 + ) + SpaceSyncStatusItem(spaceSyncUpdate = spaceSyncUpdate) +} + +@Preview(name = "LocalOnly", showBackground = true) +@Composable +fun SpaceSyncStatusPreview9() { + val spaceSyncUpdate = SpaceSyncUpdate.Update( + id = "1", + status = SpaceSyncStatus.SYNCING, + network = SpaceSyncNetwork.LOCAL_ONLY, + error = SpaceSyncError.NULL, + syncingObjectsCounter = 0 + ) + SpaceSyncStatusItem(spaceSyncUpdate = spaceSyncUpdate) +} + +@Preview(name = "P2PNotConnected", showBackground = true) +@Composable +fun SpaceSyncStatusPreview10() { + val p2pStatus = P2PStatusUpdate.Update( + status = P2PStatus.NOT_CONNECTED, + devicesCounter = 0, + spaceId = "1" + ) + P2PStatusItem(p2pStatus = p2pStatus) +} + +@Preview(name = "P2PNotPossible", showBackground = true) +@Composable +fun SpaceSyncStatusPreview11() { + val p2pStatus = P2PStatusUpdate.Update( + status = P2PStatus.NOT_POSSIBLE, + devicesCounter = 0, + spaceId = "1" + ) + P2PStatusItem(p2pStatus = p2pStatus) +} + +@Preview(name = "P2PConnected", showBackground = true) +@Composable +fun SpaceSyncStatusPreview12() { + val p2pStatus = P2PStatusUpdate.Update( + status = P2PStatus.CONNECTED, + devicesCounter = 3, + spaceId = "1" + ) + P2PStatusItem(p2pStatus = p2pStatus) +} \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/StatusBadgeWidget.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/StatusBadgeWidget.kt index 6d0d1a071a..a7e0680003 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/StatusBadgeWidget.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/StatusBadgeWidget.kt @@ -2,46 +2,69 @@ package com.anytypeio.anytype.core_ui.widgets import android.content.Context import android.util.AttributeSet -import android.view.View +import androidx.appcompat.widget.AppCompatImageView +import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncError +import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncStatus +import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncUpdate import com.anytypeio.anytype.core_ui.R -import com.anytypeio.anytype.core_ui.extensions.color -import com.anytypeio.anytype.core_ui.extensions.tint import com.anytypeio.anytype.core_utils.ext.gone import com.anytypeio.anytype.core_utils.ext.visible -import com.anytypeio.anytype.presentation.sync.SyncStatusView +import com.anytypeio.anytype.presentation.sync.SpaceSyncAndP2PStatusState class StatusBadgeWidget @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null -) : View(context, attrs) { +) : AppCompatImageView(context, attrs) { - init { - setBackgroundResource(R.drawable.circle_solid_default) - tint(color = context.color(R.color.palette_dark_grey)) - } - - fun bind(status: SyncStatusView?) { + fun bind(status: SpaceSyncAndP2PStatusState?) { when (status) { - SyncStatusView.Failed, - SyncStatusView.IncompatibleVersion -> { + is SpaceSyncAndP2PStatusState.Error -> { visible() - tint(color = context.color(R.color.palette_system_red)) + setImageResource(R.drawable.ic_sync_error_10) } - SyncStatusView.Syncing -> { - visible() - tint(color = context.color(R.color.palette_system_amber_100)) - } - SyncStatusView.Unknown, SyncStatusView.Offline, null -> { + SpaceSyncAndP2PStatusState.Initial -> { gone() } - SyncStatusView.Synced.LocalOnly -> { - gone() + is SpaceSyncAndP2PStatusState.Success -> { + when (val spaceSyncUpdate = status.spaceSyncUpdate) { + is SpaceSyncUpdate.Update -> { + val error = spaceSyncUpdate.error + if (error != SpaceSyncError.NULL) { + visible() + setImageResource(R.drawable.ic_sync_error_10) + } else { + return when (spaceSyncUpdate.status) { + SpaceSyncStatus.SYNCED -> { + visible() + setImageResource(R.drawable.ic_synced_10) + } + + SpaceSyncStatus.SYNCING -> { + visible() + setImageResource(R.drawable.ic_syncing) + } + + SpaceSyncStatus.ERROR -> { + visible() + setImageResource(R.drawable.ic_sync_error_10) + } + + SpaceSyncStatus.OFFLINE -> { + visible() + setImageResource(R.drawable.ic_sync_grey_10) + } + } + } + } + + SpaceSyncUpdate.Initial -> { + gone() + } + } } - SyncStatusView.Synced.AnyNetwork, - SyncStatusView.Synced.SelfHostedNetwork, - SyncStatusView.Synced.StagingNetwork -> { - visible() - tint(color = context.color(R.color.palette_system_green)) + + null -> { + gone() } } } diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/ObjectTopToolbar.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/ObjectTopToolbar.kt index b0fc6856b8..0427761d7d 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/ObjectTopToolbar.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/ObjectTopToolbar.kt @@ -26,8 +26,6 @@ class ObjectTopToolbar @JvmOverloads constructor( ) val status: StatusBadgeWidget get() = binding.statusBadge - val statusText: TextView get() = binding.tvStatus - val statusContainer: ViewGroup get() = binding.statusContainer val menu: View get() = binding.threeDotsButton val container: ViewGroup get() = binding.titleContainer val title: TextView get() = binding.tvTopToolbarTitle @@ -44,22 +42,20 @@ class ObjectTopToolbar @JvmOverloads constructor( if (overCover) { menu.setBackgroundResource(R.drawable.rect_object_menu_button_default) ivThreeDots.imageTintList = ColorStateList.valueOf(Color.WHITE) - statusContainer.setBackgroundResource(R.drawable.rect_object_menu_button_default) - statusText.setTextColor(Color.WHITE) + statusBadge.setBackgroundResource(R.drawable.rect_object_menu_button_default) } else { menu.background = null ivThreeDots.imageTintList = null - statusContainer.background = null - statusText.setTextColor(context.getColor(R.color.default_status_text_color)) + statusBadge.background = null } } fun hideStatusContainer() { - binding.statusContainer.alpha = 0f + binding.statusBadge.alpha = 0f } fun showStatusContainer() { - binding.statusContainer.animate().alpha(1f).setDuration(300).start() + binding.statusBadge.animate().alpha(1f).setDuration(300).start() } fun setIsLocked(isLocked: Boolean) { diff --git a/core-ui/src/main/res/drawable/circle_solid_default.xml b/core-ui/src/main/res/drawable/circle_solid_default.xml index 75d8e376bc..c31e537e38 100644 --- a/core-ui/src/main/res/drawable/circle_solid_default.xml +++ b/core-ui/src/main/res/drawable/circle_solid_default.xml @@ -1,5 +1,7 @@ - + \ No newline at end of file diff --git a/core-ui/src/main/res/drawable/ic_sync_error_10.xml b/core-ui/src/main/res/drawable/ic_sync_error_10.xml new file mode 100644 index 0000000000..0c03839291 --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_sync_error_10.xml @@ -0,0 +1,9 @@ + + + diff --git a/core-ui/src/main/res/drawable/ic_sync_grey_10.xml b/core-ui/src/main/res/drawable/ic_sync_grey_10.xml new file mode 100644 index 0000000000..3a3b51bf82 --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_sync_grey_10.xml @@ -0,0 +1,9 @@ + + + diff --git a/core-ui/src/main/res/drawable/ic_sync_local_only.xml b/core-ui/src/main/res/drawable/ic_sync_local_only.xml new file mode 100644 index 0000000000..ee8dacef59 --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_sync_local_only.xml @@ -0,0 +1,13 @@ + + + + diff --git a/core-ui/src/main/res/drawable/ic_sync_net_connected.xml b/core-ui/src/main/res/drawable/ic_sync_net_connected.xml new file mode 100644 index 0000000000..d2809f6a04 --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_sync_net_connected.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/core-ui/src/main/res/drawable/ic_sync_net_default.xml b/core-ui/src/main/res/drawable/ic_sync_net_default.xml new file mode 100644 index 0000000000..65b487c105 --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_sync_net_default.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/core-ui/src/main/res/drawable/ic_sync_net_error.xml b/core-ui/src/main/res/drawable/ic_sync_net_error.xml new file mode 100644 index 0000000000..038553d333 --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_sync_net_error.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/core-ui/src/main/res/drawable/ic_sync_offline.xml b/core-ui/src/main/res/drawable/ic_sync_offline.xml new file mode 100644 index 0000000000..65b487c105 --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_sync_offline.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/core-ui/src/main/res/drawable/ic_sync_p2p_connected.xml b/core-ui/src/main/res/drawable/ic_sync_p2p_connected.xml new file mode 100644 index 0000000000..10d3910e52 --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_sync_p2p_connected.xml @@ -0,0 +1,13 @@ + + + + diff --git a/core-ui/src/main/res/drawable/ic_sync_p2p_default.xml b/core-ui/src/main/res/drawable/ic_sync_p2p_default.xml new file mode 100644 index 0000000000..727bd99981 --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_sync_p2p_default.xml @@ -0,0 +1,13 @@ + + + + diff --git a/core-ui/src/main/res/drawable/ic_sync_p2p_error.xml b/core-ui/src/main/res/drawable/ic_sync_p2p_error.xml new file mode 100644 index 0000000000..4c91521c71 --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_sync_p2p_error.xml @@ -0,0 +1,13 @@ + + + + diff --git a/core-ui/src/main/res/drawable/ic_sync_self_connected.xml b/core-ui/src/main/res/drawable/ic_sync_self_connected.xml new file mode 100644 index 0000000000..bc3c07d836 --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_sync_self_connected.xml @@ -0,0 +1,13 @@ + + + + diff --git a/core-ui/src/main/res/drawable/ic_sync_self_default.xml b/core-ui/src/main/res/drawable/ic_sync_self_default.xml new file mode 100644 index 0000000000..b6c0d6a5ad --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_sync_self_default.xml @@ -0,0 +1,13 @@ + + + + diff --git a/core-ui/src/main/res/drawable/ic_sync_self_error.xml b/core-ui/src/main/res/drawable/ic_sync_self_error.xml new file mode 100644 index 0000000000..42713f918b --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_sync_self_error.xml @@ -0,0 +1,13 @@ + + + + diff --git a/core-ui/src/main/res/drawable/ic_synced_10.xml b/core-ui/src/main/res/drawable/ic_synced_10.xml new file mode 100644 index 0000000000..33bb4d439d --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_synced_10.xml @@ -0,0 +1,9 @@ + + + diff --git a/core-ui/src/main/res/drawable/ic_syncing.xml b/core-ui/src/main/res/drawable/ic_syncing.xml new file mode 100644 index 0000000000..810fbf1af4 --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_syncing.xml @@ -0,0 +1,19 @@ + + + + + diff --git a/core-ui/src/main/res/drawable/rect_object_menu_button_default.xml b/core-ui/src/main/res/drawable/rect_object_menu_button_default.xml index b8a73d7db2..8433ad689e 100644 --- a/core-ui/src/main/res/drawable/rect_object_menu_button_default.xml +++ b/core-ui/src/main/res/drawable/rect_object_menu_button_default.xml @@ -5,7 +5,7 @@ - + \ No newline at end of file diff --git a/core-ui/src/main/res/layout/widget_object_top_toolbar.xml b/core-ui/src/main/res/layout/widget_object_top_toolbar.xml index 090c579eea..a330d842ab 100644 --- a/core-ui/src/main/res/layout/widget_object_top_toolbar.xml +++ b/core-ui/src/main/res/layout/widget_object_top_toolbar.xml @@ -3,40 +3,16 @@ xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto"> - - - - - - - + android:scaleType="center" + android:layout_marginStart="12dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"/> > + fun observe(activeSpaceId: Id): Flow } class P2PStatusDataChannel( private val channel: P2PStatusRemoteChannel ) : P2PStatusChannel { - override fun observe(activeSpaceId: Id): Flow> { + override fun observe(activeSpaceId: Id): Flow { return channel.observe(activeSpaceId) } } \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/status/SpaceStatusRemoteChannel.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/status/SpaceStatusRemoteChannel.kt index d78496c518..16b59f9499 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/status/SpaceStatusRemoteChannel.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/status/SpaceStatusRemoteChannel.kt @@ -6,14 +6,14 @@ import com.anytypeio.anytype.domain.workspace.SpaceSyncStatusChannel import kotlinx.coroutines.flow.Flow interface SpaceStatusRemoteChannel { - fun observe(activeSpaceId: String): Flow> + fun observe(activeSpaceId: String): Flow } class SpaceStatusDataChannel( private val channel: SpaceStatusRemoteChannel ) : SpaceSyncStatusChannel { - override fun observe(activeSpaceId: Id): Flow> { + override fun observe(activeSpaceId: Id): Flow { return channel.observe(activeSpaceId) } } \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/status/ThreadStatusDataChannel.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/status/ThreadStatusDataChannel.kt deleted file mode 100644 index 5e9bc6abb3..0000000000 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/status/ThreadStatusDataChannel.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.anytypeio.anytype.data.auth.status - -import com.anytypeio.anytype.core_models.SyncStatus -import com.anytypeio.anytype.domain.status.ThreadStatusChannel -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map - -class ThreadStatusDataChannel( - private val remote: ThreadStatusRemoteChannel, -) : ThreadStatusChannel { - - override fun observe(ctx: String): Flow { - return remote.observe(ctx) - } -} \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/status/ThreadStatusRemoteChannel.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/status/ThreadStatusRemoteChannel.kt deleted file mode 100644 index 2f3d22c74d..0000000000 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/status/ThreadStatusRemoteChannel.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.anytypeio.anytype.data.auth.status - -import com.anytypeio.anytype.core_models.Id -import com.anytypeio.anytype.core_models.SyncStatus -import kotlinx.coroutines.flow.Flow - -interface ThreadStatusRemoteChannel { - fun observe(ctx: Id): Flow -} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/status/InterceptThreadStatus.kt b/domain/src/main/java/com/anytypeio/anytype/domain/status/InterceptThreadStatus.kt deleted file mode 100644 index fc2e5efbdf..0000000000 --- a/domain/src/main/java/com/anytypeio/anytype/domain/status/InterceptThreadStatus.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.anytypeio.anytype.domain.status - -import com.anytypeio.anytype.core_models.Id -import com.anytypeio.anytype.core_models.SyncStatus -import com.anytypeio.anytype.domain.base.FlowUseCase -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOn -import kotlin.coroutines.CoroutineContext - -class InterceptThreadStatus( - private val context: CoroutineContext = Dispatchers.IO, - private val channel: ThreadStatusChannel, -) : FlowUseCase() { - - override fun build(params: Params?): Flow { - checkNotNull(params) { "Params are required for this use-case." } - return channel.observe(params.ctx).flowOn(context) - } - - data class Params(val ctx: Id) -} diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/status/SyncAccount.kt b/domain/src/main/java/com/anytypeio/anytype/domain/status/SyncAccount.kt deleted file mode 100644 index 53021f05fd..0000000000 --- a/domain/src/main/java/com/anytypeio/anytype/domain/status/SyncAccount.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.anytypeio.anytype.domain.status - -import com.anytypeio.anytype.core_models.Hash -import com.anytypeio.anytype.core_models.Id - -data class SyncAccount( - val id: Id, - val name: String, - val image: Hash, - val isOnline: Boolean, - val lastPulled: Int, - val lastEdited: Int, - val devices: List, -) { - data class Device( - val name: String, - val isOnline: Boolean, - val lastPulled: Int, - val lastEdited: Int, - ) -} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/status/ThreadStatusChannel.kt b/domain/src/main/java/com/anytypeio/anytype/domain/status/ThreadStatusChannel.kt deleted file mode 100644 index 8fb713218e..0000000000 --- a/domain/src/main/java/com/anytypeio/anytype/domain/status/ThreadStatusChannel.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.anytypeio.anytype.domain.status - -import com.anytypeio.anytype.core_models.SyncStatus -import kotlinx.coroutines.flow.Flow - -interface ThreadStatusChannel { - fun observe(ctx: String): Flow -} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/workspace/P2PStatusChannel.kt b/domain/src/main/java/com/anytypeio/anytype/domain/workspace/P2PStatusChannel.kt index 8d2d80ad5e..ce1fd1e69a 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/workspace/P2PStatusChannel.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/workspace/P2PStatusChannel.kt @@ -5,5 +5,5 @@ import com.anytypeio.anytype.core_models.multiplayer.P2PStatusUpdate import kotlinx.coroutines.flow.Flow interface P2PStatusChannel { - fun observe(activeSpaceId: Id): Flow> + fun observe(activeSpaceId: Id): Flow } \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/workspace/SpaceSyncStatusChannel.kt b/domain/src/main/java/com/anytypeio/anytype/domain/workspace/SpaceSyncStatusChannel.kt index 6bb12f7867..14e1e5b93b 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/workspace/SpaceSyncStatusChannel.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/workspace/SpaceSyncStatusChannel.kt @@ -5,5 +5,5 @@ import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncUpdate import kotlinx.coroutines.flow.Flow interface SpaceSyncStatusChannel { - fun observe(activeSpaceId: Id): Flow> + fun observe(activeSpaceId: Id): Flow } \ No newline at end of file diff --git a/localization/src/main/res/values/strings.xml b/localization/src/main/res/values/strings.xml index 5293632d26..2c5db9960b 100644 --- a/localization/src/main/res/values/strings.xml +++ b/localization/src/main/res/values/strings.xml @@ -1698,4 +1698,39 @@ Please provide specific details of your needs here. On top See all objects + Error getting sync status + + Local Only + Data backup is disabled + + Self Host + Synced + + %1$d item syncing + %1$d items syncing... + + + P2P Connection + + %1$d device connected + %1$d devices connected + + P2P Connecting... + Disabled + Restricted. Check device settings. + + Anytype Network + End-to-end encrypted + + %1$d item syncing... + %1$d items syncing... + + Network Connecting... + No connection + + Storage limit reached + Incompatible version + No access to the space + Unrecognized error + \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/MiddlewareEventChannel.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/MiddlewareEventChannel.kt index ac6f3b1bc8..c6e0370541 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/MiddlewareEventChannel.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/MiddlewareEventChannel.kt @@ -57,7 +57,7 @@ class MiddlewareEventChannel( blockDataviewIsCollectionSet != null -> true blockSetWidget != null -> true else -> false.also { - if (featureToggles.isLogMiddlewareInteraction && threadStatus == null) + if (featureToggles.isLogMiddlewareInteraction) Timber.w("Ignored event: $this") } } diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/MiddlewareProtobufLogger.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/MiddlewareProtobufLogger.kt index ee522d67ed..63001dfe81 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/MiddlewareProtobufLogger.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/MiddlewareProtobufLogger.kt @@ -1,8 +1,6 @@ package com.anytypeio.anytype.middleware.interactor -import anytype.Event import com.anytypeio.anytype.core_utils.tools.FeatureToggles -import com.google.gson.Gson import timber.log.Timber import javax.inject.Inject @@ -33,16 +31,7 @@ interface MiddlewareProtobufLogger { override fun logEvent(any: Any) { if (featureToggles.isLogMiddlewareInteraction) { - if (featureToggles.excludeThreadStatusLogging) { - if (any is Event && containsOnlyThreadStatusEvents(any)) { - // Do nothing. - } else { - Timber.d("event -> ${any.toLogMessage()}") - } - } else { - Timber.d("event -> ${any.toLogMessage()}") - } - + Timber.d("event -> ${any.toLogMessage()}") } } @@ -55,11 +44,5 @@ interface MiddlewareProtobufLogger { }" } } - - private fun containsOnlyThreadStatusEvents(event: Event) : Boolean { - return event.messages.all { msg -> - msg.threadStatus != null - } - } } } diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/P2PStatusRemoteChannelImpl.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/P2PStatusRemoteChannelImpl.kt index 2b89d1586e..82c95f65b3 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/P2PStatusRemoteChannelImpl.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/P2PStatusRemoteChannelImpl.kt @@ -6,31 +6,24 @@ import com.anytypeio.anytype.data.auth.status.P2PStatusRemoteChannel import com.anytypeio.anytype.middleware.EventProxy import com.anytypeio.anytype.middleware.mappers.toCoreModel import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull class P2PStatusRemoteChannelImpl(private val events: EventProxy) : P2PStatusRemoteChannel { - override fun observe(activeSpaceId: Id): Flow> { - return events.flow().mapNotNull { emission -> - emission.messages.mapNotNull { message -> - when { - message.p2pStatusUpdate != null -> { - val event = message.p2pStatusUpdate - checkNotNull(event) - if (event.spaceId == activeSpaceId) { - P2PStatusUpdate( - spaceId = event.spaceId, - status = event.status.toCoreModel(), - devicesCounter = event.devicesCounter - ) - } else { - null - } - } - - else -> null - } + override fun observe(activeSpaceId: Id): Flow { + return events.flow() + .mapNotNull { emission -> + emission.messages.lastOrNull()?.p2pStatusUpdate + } + .filter { event -> event.spaceId == activeSpaceId } + .map { event -> + P2PStatusUpdate.Update( + spaceId = event.spaceId, + status = event.status.toCoreModel(), + devicesCounter = event.devicesCounter + ) } - } } } \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/SpaceSyncStatusRemoteChannelImpl.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/SpaceSyncStatusRemoteChannelImpl.kt index d938e6d444..8ad3480231 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/SpaceSyncStatusRemoteChannelImpl.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/SpaceSyncStatusRemoteChannelImpl.kt @@ -6,33 +6,26 @@ import com.anytypeio.anytype.data.auth.status.SpaceStatusRemoteChannel import com.anytypeio.anytype.middleware.EventProxy import com.anytypeio.anytype.middleware.mappers.toCoreModel import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull class SpaceSyncStatusRemoteChannelImpl(private val events: EventProxy) : SpaceStatusRemoteChannel { - override fun observe(activeSpaceId: Id): Flow> { - return events.flow().mapNotNull { emission -> - emission.messages.mapNotNull { message -> - when { - message.spaceSyncStatusUpdate != null -> { - val event = message.spaceSyncStatusUpdate - checkNotNull(event) - if (event.id == activeSpaceId) { - SpaceSyncUpdate( - id = event.id, - status = event.status.toCoreModel(), - network = event.network.toCoreModel(), - error = event.error.toCoreModel(), - syncingObjectsCounter = event.syncingObjectsCounter - ) - } else { - null - } - } - - else -> null - } + override fun observe(activeSpaceId: Id): Flow { + return events.flow() + .mapNotNull { emission -> + emission.messages.lastOrNull()?.spaceSyncStatusUpdate + } + .filter { event -> event.id == activeSpaceId } + .map { event -> + SpaceSyncUpdate.Update( + id = event.id, + status = event.status.toCoreModel(), + network = event.network.toCoreModel(), + error = event.error.toCoreModel(), + syncingObjectsCounter = event.syncingObjectsCounter + ) } - } } } \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/ThreadStatusMiddlewareChannel.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/ThreadStatusMiddlewareChannel.kt deleted file mode 100644 index 52dc7b36db..0000000000 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/ThreadStatusMiddlewareChannel.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.anytypeio.anytype.middleware.interactor - -import com.anytypeio.anytype.core_models.SyncStatus -import com.anytypeio.anytype.data.auth.status.ThreadStatusRemoteChannel -import com.anytypeio.anytype.middleware.EventProxy -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.mapLatest -import kotlinx.coroutines.flow.mapNotNull -import anytype.Event.Status.Thread.SyncStatus as MSyncStatus - -class ThreadStatusMiddlewareChannel( - private val events: EventProxy, -) : ThreadStatusRemoteChannel { - - override fun observe(ctx: String): Flow = events.flow() - .filter { it.contextId == ctx } - .mapNotNull { emission -> - emission - .messages - .lastOrNull { it.threadStatus != null } - ?.threadStatus - ?.summary - ?.status - } - .mapLatest { status -> - when (status) { - MSyncStatus.Unknown -> SyncStatus.UNKNOWN - MSyncStatus.Offline -> SyncStatus.OFFLINE - MSyncStatus.Syncing -> SyncStatus.SYNCING - MSyncStatus.Synced -> SyncStatus.SYNCED - MSyncStatus.Failed -> SyncStatus.FAILED - MSyncStatus.IncompatibleVersion -> SyncStatus.INCOMPATIBLE_VERSION - } - } -} \ 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 368c0ed973..6c3dfbb0a3 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 @@ -97,7 +97,6 @@ import com.anytypeio.anytype.domain.page.OpenPage import com.anytypeio.anytype.domain.relations.AddRelationToObject import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.domain.sets.FindObjectSetForType -import com.anytypeio.anytype.domain.status.InterceptThreadStatus import com.anytypeio.anytype.domain.templates.ApplyTemplate import com.anytypeio.anytype.domain.unsplash.DownloadUnsplashImage import com.anytypeio.anytype.domain.workspace.InterceptFileLimitEvents @@ -251,8 +250,11 @@ import com.anytypeio.anytype.presentation.relations.getObjectRelations import com.anytypeio.anytype.presentation.relations.views import com.anytypeio.anytype.presentation.search.ObjectSearchConstants import com.anytypeio.anytype.presentation.search.ObjectSearchViewModel -import com.anytypeio.anytype.presentation.sync.SyncStatusView -import com.anytypeio.anytype.presentation.sync.toView +import com.anytypeio.anytype.presentation.sync.SpaceSyncAndP2PStatusProvider +import com.anytypeio.anytype.presentation.sync.SpaceSyncAndP2PStatusState +import com.anytypeio.anytype.presentation.sync.SyncStatusWidgetState +import com.anytypeio.anytype.presentation.sync.toSyncStatusWidgetState +import com.anytypeio.anytype.presentation.sync.updateStatus import com.anytypeio.anytype.presentation.templates.ObjectTypeTemplatesContainer import com.anytypeio.anytype.presentation.util.CopyFileStatus import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory @@ -291,7 +293,6 @@ class EditorViewModel( private val createBlockLinkWithObject: CreateBlockLinkWithObject, private val createObjectAsMentionOrLink: CreateObjectAsMentionOrLink, private val interceptEvents: InterceptEvents, - private val interceptThreadStatus: InterceptThreadStatus, private val updateLinkMarks: UpdateLinkMarks, private val removeLinkMark: RemoveLinkMark, private val reducer: StateReducer, Event>, @@ -326,8 +327,8 @@ class EditorViewModel( private val templatesContainer: ObjectTypeTemplatesContainer, private val storelessSubscriptionContainer: StorelessSubscriptionContainer, private val dispatchers: AppCoroutineDispatchers, - private val getNetworkMode: GetNetworkMode, - private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate + private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, + private val spaceSyncAndP2PStatusProvider: SpaceSyncAndP2PStatusProvider ) : ViewStateViewModel(), PickerListener, SupportNavigation>, @@ -341,9 +342,6 @@ class EditorViewModel( val actions = MutableStateFlow(ActionItemType.defaultSorting) - val isSyncStatusVisible = MutableStateFlow(true) - val syncStatus = MutableStateFlow(null) - val icon = MutableStateFlow(ProfileIconView.Loading) val isUndoEnabled = MutableStateFlow(false) @@ -1054,18 +1052,7 @@ class EditorViewModel( } } - jobs += viewModelScope.launch { - val networkMode = getNetworkMode.run(Unit).networkMode - interceptThreadStatus - .build(InterceptThreadStatus.Params(context)) - .collect { status -> - val statusView = status.toView( - networkId = spaceManager.getConfig(space = vmParams.space)?.network, - networkMode = networkMode - ) - syncStatus.value = statusView - } - } + proceedWithCollectingSyncStatus() jobs += viewModelScope.launch { dispatcher @@ -1092,9 +1079,6 @@ class EditorViewModel( orchestrator.proxies.payloads.send(result.data) result.data.events.forEach { event -> if (event is Event.Command.ShowObject) { - if (event.details.details[context]?.type?.contains(ObjectTypeIds.FILE) == true) { - isSyncStatusVisible.value = false - } sendAnalyticsObjectShowEvent( analytics = analytics, startTime = startTime, @@ -7353,6 +7337,31 @@ class EditorViewModel( return mode == EditorMode.Locked || objRestrictions.contains(ObjectRestriction.DETAILS) } + //region SYNC STATUS + val spaceSyncStatus = MutableStateFlow(SpaceSyncAndP2PStatusState.Initial) + val syncStatusWidget = MutableStateFlow(SyncStatusWidgetState.Hidden) + + fun onSyncStatusBadgeClicked() { + Timber.d("onSyncStatusBadgeClicked, ") + syncStatusWidget.value = spaceSyncStatus.value.toSyncStatusWidgetState() + } + + private fun proceedWithCollectingSyncStatus() { + jobs += viewModelScope.launch { + spaceSyncAndP2PStatusProvider + .observe() + .collect { syncAndP2pState -> + spaceSyncStatus.value = syncAndP2pState + syncStatusWidget.value = syncStatusWidget.value.updateStatus(syncAndP2pState) + } + } + } + + fun onSyncWidgetDismiss() { + syncStatusWidget.value = SyncStatusWidgetState.Hidden + } + //endregion + data class Params( val ctx: Id, val space: SpaceId diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModelFactory.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModelFactory.kt index 20c44c93ea..c2124b3ba5 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModelFactory.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModelFactory.kt @@ -20,7 +20,6 @@ import com.anytypeio.anytype.domain.launch.GetDefaultObjectType import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider -import com.anytypeio.anytype.domain.networkmode.GetNetworkMode import com.anytypeio.anytype.domain.`object`.ConvertObjectToCollection import com.anytypeio.anytype.domain.`object`.ConvertObjectToSet import com.anytypeio.anytype.domain.`object`.UpdateDetail @@ -34,7 +33,6 @@ import com.anytypeio.anytype.domain.page.OpenPage import com.anytypeio.anytype.domain.relations.AddRelationToObject import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.domain.sets.FindObjectSetForType -import com.anytypeio.anytype.domain.status.InterceptThreadStatus import com.anytypeio.anytype.domain.templates.ApplyTemplate import com.anytypeio.anytype.domain.unsplash.DownloadUnsplashImage import com.anytypeio.anytype.domain.workspace.InterceptFileLimitEvents @@ -46,6 +44,7 @@ import com.anytypeio.anytype.presentation.common.StateReducer import com.anytypeio.anytype.presentation.editor.editor.Orchestrator import com.anytypeio.anytype.presentation.editor.editor.table.EditorTableDelegate import com.anytypeio.anytype.presentation.editor.render.DefaultBlockViewRenderer +import com.anytypeio.anytype.presentation.sync.SpaceSyncAndP2PStatusProvider import com.anytypeio.anytype.presentation.templates.ObjectTypeTemplatesContainer import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory import com.anytypeio.anytype.presentation.util.Dispatcher @@ -60,7 +59,6 @@ open class EditorViewModelFactory @Inject constructor( private val createBlockLinkWithObject: CreateBlockLinkWithObject, private val createObjectAsMentionOrLink: CreateObjectAsMentionOrLink, private val interceptEvents: InterceptEvents, - private val interceptThreadStatus: InterceptThreadStatus, private val updateLinkMarks: UpdateLinkMarks, private val removeLinkMark: RemoveLinkMark, private val documentEventReducer: StateReducer, Event>, @@ -94,8 +92,8 @@ open class EditorViewModelFactory @Inject constructor( private val templatesContainer: ObjectTypeTemplatesContainer, private val storelessSubscriptionContainer: StorelessSubscriptionContainer, private val dispatchers: AppCoroutineDispatchers, - private val getNetworkMode: GetNetworkMode, - private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate + private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, + private val syncStatusProvider: SpaceSyncAndP2PStatusProvider ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") @@ -108,7 +106,6 @@ open class EditorViewModelFactory @Inject constructor( createBlockLinkWithObject = createBlockLinkWithObject, createObjectAsMentionOrLink = createObjectAsMentionOrLink, interceptEvents = interceptEvents, - interceptThreadStatus = interceptThreadStatus, updateLinkMarks = updateLinkMarks, removeLinkMark = removeLinkMark, reducer = documentEventReducer, @@ -143,8 +140,8 @@ open class EditorViewModelFactory @Inject constructor( templatesContainer = templatesContainer, dispatchers = dispatchers, storelessSubscriptionContainer = storelessSubscriptionContainer, - getNetworkMode = getNetworkMode, - analyticSpaceHelperDelegate = analyticSpaceHelperDelegate + analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, + spaceSyncAndP2PStatusProvider = syncStatusProvider ) as T } } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModel.kt index 390ec1cbd7..6582bac521 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModel.kt @@ -43,7 +43,6 @@ import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer import com.anytypeio.anytype.domain.misc.DateProvider import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider -import com.anytypeio.anytype.domain.networkmode.GetNetworkMode import com.anytypeio.anytype.domain.`object`.ConvertObjectToCollection import com.anytypeio.anytype.domain.`object`.DuplicateObjects import com.anytypeio.anytype.domain.`object`.UpdateDetail @@ -53,12 +52,10 @@ import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.objects.StoreOfRelations import com.anytypeio.anytype.domain.page.CloseBlock import com.anytypeio.anytype.domain.page.CreateObject -import com.anytypeio.anytype.domain.search.CancelSearchSubscription import com.anytypeio.anytype.domain.search.DataViewState import com.anytypeio.anytype.domain.search.DataViewSubscriptionContainer import com.anytypeio.anytype.domain.sets.OpenObjectSet import com.anytypeio.anytype.domain.sets.SetQueryToObjectSet -import com.anytypeio.anytype.domain.status.InterceptThreadStatus import com.anytypeio.anytype.domain.templates.CreateTemplate import com.anytypeio.anytype.domain.unsplash.DownloadUnsplashImage import com.anytypeio.anytype.domain.workspace.SpaceManager @@ -98,8 +95,11 @@ import com.anytypeio.anytype.presentation.sets.subscription.DefaultDataViewSubsc import com.anytypeio.anytype.presentation.sets.viewer.ViewerDelegate import com.anytypeio.anytype.presentation.sets.viewer.ViewerEvent import com.anytypeio.anytype.presentation.sets.viewer.ViewerView -import com.anytypeio.anytype.presentation.sync.SyncStatusView -import com.anytypeio.anytype.presentation.sync.toView +import com.anytypeio.anytype.presentation.sync.SpaceSyncAndP2PStatusProvider +import com.anytypeio.anytype.presentation.sync.SpaceSyncAndP2PStatusState +import com.anytypeio.anytype.presentation.sync.SyncStatusWidgetState +import com.anytypeio.anytype.presentation.sync.toSyncStatusWidgetState +import com.anytypeio.anytype.presentation.sync.updateStatus import com.anytypeio.anytype.presentation.templates.ObjectTypeTemplatesContainer import com.anytypeio.anytype.presentation.templates.TemplateMenuClick import com.anytypeio.anytype.presentation.templates.TemplateObjectTypeView @@ -148,7 +148,6 @@ class ObjectSetViewModel( private val setDocCoverImage: SetDocCoverImage, private val updateText: UpdateText, private val interceptEvents: InterceptEvents, - private val interceptThreadStatus: InterceptThreadStatus, private val dispatcher: Dispatcher, private val delegator: Delegator, private val urlBuilder: UrlBuilder, @@ -158,7 +157,6 @@ class ObjectSetViewModel( private val createDataViewObject: CreateDataViewObject, private val createObject: CreateObject, private val dataViewSubscriptionContainer: DataViewSubscriptionContainer, - private val cancelSearchSubscription: CancelSearchSubscription, private val setQueryToObjectSet: SetQueryToObjectSet, private val paginator: ObjectSetPaginator, private val storeOfRelations: StoreOfRelations, @@ -177,9 +175,9 @@ class ObjectSetViewModel( private val createTemplate: CreateTemplate, private val storelessSubscriptionContainer: StorelessSubscriptionContainer, private val dispatchers: AppCoroutineDispatchers, - private val getNetworkMode: GetNetworkMode, private val dateProvider: DateProvider, - private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate + private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, + private val spaceSyncAndP2PStatusProvider: SpaceSyncAndP2PStatusProvider ) : ViewModel(), SupportNavigation>, ViewerDelegate by viewerDelegate, AnalyticSpaceHelperDelegate by analyticSpaceHelperDelegate @@ -191,7 +189,6 @@ class ObjectSetViewModel( private val isOwnerOrEditor get() = permission.value?.isOwnerOrEditor() == true - val status = MutableStateFlow(null) val error = MutableStateFlow(null) val featured = MutableStateFlow(null) @@ -455,9 +452,9 @@ class ObjectSetViewModel( session.currentViewerId.value = view } subscribeToEvents(ctx = ctx) - subscribeToThreadStatus(ctx = ctx) proceedWithOpeningCurrentObject(ctx = ctx) proceedWithObservingProfileIcon() + proceedWithObservingSyncStatus() } private fun subscribeToEvents(ctx: Id) { @@ -468,21 +465,6 @@ class ObjectSetViewModel( } } - private fun subscribeToThreadStatus(ctx: Id) { - jobs += viewModelScope.launch { - val networkMode = getNetworkMode.run(Unit).networkMode - interceptThreadStatus - .build(InterceptThreadStatus.Params(ctx)) - .collect { - val statusView = it.toView( - networkId = spaceManager.getConfig(vmParams.space)?.network, - networkMode = networkMode - ) - status.value = statusView - } - } - } - @OptIn(ExperimentalCoroutinesApi::class) private fun subscribeToObjectState() { Timber.d("subscribeToObjectState, ctx:[$context]") @@ -2830,6 +2812,31 @@ class ObjectSetViewModel( } //endregion + //region SYNC STATUS + val spaceSyncStatus = MutableStateFlow(SpaceSyncAndP2PStatusState.Initial) + val syncStatusWidget = MutableStateFlow(SyncStatusWidgetState.Hidden) + + fun onSyncStatusBadgeClicked() { + Timber.d("onSyncStatusBadgeClicked, ") + syncStatusWidget.value = spaceSyncStatus.value.toSyncStatusWidgetState() + } + + private fun proceedWithObservingSyncStatus() { + jobs += viewModelScope.launch { + spaceSyncAndP2PStatusProvider + .observe() + .collect { syncAndP2pState -> + spaceSyncStatus.value = syncAndP2pState + syncStatusWidget.value = syncStatusWidget.value.updateStatus(syncAndP2pState) + } + } + } + + fun onSyncWidgetDismiss() { + syncStatusWidget.value = SyncStatusWidgetState.Hidden + } + //endregion + companion object { const val NOT_ALLOWED = "Not allowed for this set" const val NOT_ALLOWED_CELL = "Not allowed for this cell" diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModelFactory.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModelFactory.kt index bdb2dda54a..4bd5cd1a6f 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModelFactory.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModelFactory.kt @@ -15,7 +15,6 @@ import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer import com.anytypeio.anytype.domain.misc.DateProvider import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider -import com.anytypeio.anytype.domain.networkmode.GetNetworkMode import com.anytypeio.anytype.domain.`object`.ConvertObjectToCollection import com.anytypeio.anytype.domain.`object`.DuplicateObjects import com.anytypeio.anytype.domain.`object`.UpdateDetail @@ -25,11 +24,9 @@ import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes import com.anytypeio.anytype.domain.objects.StoreOfRelations import com.anytypeio.anytype.domain.page.CloseBlock import com.anytypeio.anytype.domain.page.CreateObject -import com.anytypeio.anytype.domain.search.CancelSearchSubscription import com.anytypeio.anytype.domain.search.DataViewSubscriptionContainer import com.anytypeio.anytype.domain.sets.OpenObjectSet import com.anytypeio.anytype.domain.sets.SetQueryToObjectSet -import com.anytypeio.anytype.domain.status.InterceptThreadStatus import com.anytypeio.anytype.domain.templates.CreateTemplate import com.anytypeio.anytype.domain.unsplash.DownloadUnsplashImage import com.anytypeio.anytype.domain.workspace.SpaceManager @@ -40,6 +37,7 @@ import com.anytypeio.anytype.presentation.editor.cover.CoverImageHashProvider import com.anytypeio.anytype.presentation.sets.state.ObjectStateReducer import com.anytypeio.anytype.presentation.sets.subscription.DataViewSubscription import com.anytypeio.anytype.presentation.sets.viewer.ViewerDelegate +import com.anytypeio.anytype.presentation.sync.SpaceSyncAndP2PStatusProvider import com.anytypeio.anytype.presentation.templates.ObjectTypeTemplatesContainer import com.anytypeio.anytype.presentation.util.Dispatcher @@ -54,7 +52,6 @@ class ObjectSetViewModelFactory( private val setDocCoverImage: SetDocCoverImage, private val updateText: UpdateText, private val interceptEvents: InterceptEvents, - private val interceptThreadStatus: InterceptThreadStatus, private val dispatcher: Dispatcher, private val delegator: Delegator, private val coverImageHashProvider: CoverImageHashProvider, @@ -63,7 +60,6 @@ class ObjectSetViewModelFactory( private val analytics: Analytics, private val createObject: CreateObject, private val dataViewSubscriptionContainer: DataViewSubscriptionContainer, - private val cancelSearchSubscription: CancelSearchSubscription, private val setQueryToObjectSet: SetQueryToObjectSet, private val database: ObjectSetDatabase, private val paginator: ObjectSetPaginator, @@ -83,9 +79,9 @@ class ObjectSetViewModelFactory( private val spaceManager: SpaceManager, private val storelessSubscriptionContainer: StorelessSubscriptionContainer, private val dispatchers: AppCoroutineDispatchers, - private val getNetworkMode: GetNetworkMode, private val dateProvider: DateProvider, - private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate + private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate, + private val spaceSyncAndP2PStatusProvider: SpaceSyncAndP2PStatusProvider ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { @@ -100,7 +96,6 @@ class ObjectSetViewModelFactory( downloadUnsplashImage = downloadUnsplashImage, updateText = updateText, interceptEvents = interceptEvents, - interceptThreadStatus = interceptThreadStatus, dispatcher = dispatcher, delegator = delegator, coverImageHashProvider = coverImageHashProvider, @@ -109,7 +104,6 @@ class ObjectSetViewModelFactory( analytics = analytics, createObject = createObject, dataViewSubscriptionContainer = dataViewSubscriptionContainer, - cancelSearchSubscription = cancelSearchSubscription, setQueryToObjectSet = setQueryToObjectSet, database = database, paginator = paginator, @@ -129,9 +123,9 @@ class ObjectSetViewModelFactory( createTemplate = createTemplate, dispatchers = dispatchers, storelessSubscriptionContainer = storelessSubscriptionContainer, - getNetworkMode = getNetworkMode, dateProvider = dateProvider, - analyticSpaceHelperDelegate = analyticSpaceHelperDelegate + analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, + spaceSyncAndP2PStatusProvider = spaceSyncAndP2PStatusProvider, ) as T } } \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/sync/SpaceSyncAndP2PStatusProvider.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/sync/SpaceSyncAndP2PStatusProvider.kt new file mode 100644 index 0000000000..0ce009f67d --- /dev/null +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/sync/SpaceSyncAndP2PStatusProvider.kt @@ -0,0 +1,112 @@ +package com.anytypeio.anytype.presentation.sync + +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.multiplayer.P2PStatusUpdate +import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncUpdate +import com.anytypeio.anytype.domain.multiplayer.ActiveSpaceMemberSubscriptionContainer +import com.anytypeio.anytype.domain.workspace.P2PStatusChannel +import com.anytypeio.anytype.domain.workspace.SpaceSyncStatusChannel +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.onStart + +interface SpaceSyncAndP2PStatusProvider { + + suspend fun observe(): Flow + + class Impl @Inject constructor( + private val activeSpace: ActiveSpaceMemberSubscriptionContainer, + private val spaceSyncStatusChannel: SpaceSyncStatusChannel, + private val p2PStatusChannel: P2PStatusChannel + ) : SpaceSyncAndP2PStatusProvider { + + @OptIn(ExperimentalCoroutinesApi::class) + override suspend fun observe(): Flow { + return activeSpace + .observe() + .flatMapLatest { activeSpace -> + when (activeSpace) { + is ActiveSpaceMemberSubscriptionContainer.Store.Data -> { + observeSpaceSyncStatus(spaceId = activeSpace.config.space) + } + + ActiveSpaceMemberSubscriptionContainer.Store.Empty -> { + emptyFlow() + } + } + } + } + + private fun observeSpaceSyncStatus(spaceId: Id): Flow { + val syncFlow = + spaceSyncStatusChannel.observe(spaceId).onStart { emit(SpaceSyncUpdate.Initial) } + val p2pFlow = + p2PStatusChannel.observe(spaceId).onStart { emit(P2PStatusUpdate.Initial) } + + return combine(syncFlow, p2pFlow) { syncStatus, p2PStatus -> + if (syncStatus is SpaceSyncUpdate.Initial && p2PStatus is P2PStatusUpdate.Initial) { + SpaceSyncAndP2PStatusState.Initial + } else { + SpaceSyncAndP2PStatusState.Success( + spaceSyncUpdate = syncStatus, + p2PStatusUpdate = p2PStatus + ) + } + } + } + } +} + +sealed class SpaceSyncAndP2PStatusState { + data object Initial : SpaceSyncAndP2PStatusState() + data class Error(val message: String) : SpaceSyncAndP2PStatusState() + data class Success( + val spaceSyncUpdate: SpaceSyncUpdate, + val p2PStatusUpdate: P2PStatusUpdate + ) : SpaceSyncAndP2PStatusState() +} + +fun SyncStatusWidgetState.updateStatus(newState: SpaceSyncAndP2PStatusState): SyncStatusWidgetState { + return when (this) { + is SyncStatusWidgetState.Error -> { + newState.toSyncStatusWidgetState() + } + + SyncStatusWidgetState.Hidden -> SyncStatusWidgetState.Hidden + is SyncStatusWidgetState.Success -> { + newState.toSyncStatusWidgetState() + } + } +} + +fun SpaceSyncAndP2PStatusState.toSyncStatusWidgetState(): SyncStatusWidgetState { + return when (this) { + is SpaceSyncAndP2PStatusState.Error -> { + SyncStatusWidgetState.Error(message = message) + } + + SpaceSyncAndP2PStatusState.Initial -> { + SyncStatusWidgetState.Hidden + } + + is SpaceSyncAndP2PStatusState.Success -> { + SyncStatusWidgetState.Success( + spaceSyncUpdate = spaceSyncUpdate, + p2PStatusUpdate = p2PStatusUpdate + ) + } + } +} + +sealed class SyncStatusWidgetState { + data object Hidden : SyncStatusWidgetState() + data class Error(val message: String) : SyncStatusWidgetState() + data class Success( + val spaceSyncUpdate: SpaceSyncUpdate, + val p2PStatusUpdate: P2PStatusUpdate + ) : SyncStatusWidgetState() +} \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/sync/SyncStatusView.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/sync/SyncStatusView.kt deleted file mode 100644 index d7021f5131..0000000000 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/sync/SyncStatusView.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.anytypeio.anytype.presentation.sync - -import com.anytypeio.anytype.core_models.Id -import com.anytypeio.anytype.core_models.NetworkMode -import com.anytypeio.anytype.core_models.SyncStatus -import com.anytypeio.anytype.core_models.NetworkModeConst.NODE_STAGING_ID - -sealed class SyncStatusView { - object Unknown : SyncStatusView() - object Offline : SyncStatusView() - object Syncing : SyncStatusView() - sealed class Synced : SyncStatusView() { - object AnyNetwork : Synced() - object StagingNetwork : Synced() - object LocalOnly : Synced() - object SelfHostedNetwork : Synced() - } - object Failed : SyncStatusView() - object IncompatibleVersion : SyncStatusView() -} - -fun SyncStatus.toView(networkId: Id?, networkMode: NetworkMode): SyncStatusView { - return when (this) { - SyncStatus.UNKNOWN -> { - when (networkMode) { - NetworkMode.LOCAL -> SyncStatusView.Synced.LocalOnly - else -> SyncStatusView.Unknown - } - } - SyncStatus.OFFLINE -> SyncStatusView.Offline - SyncStatus.SYNCING -> SyncStatusView.Syncing - SyncStatus.SYNCED -> { - networkMode.syncedStatusToView(networkId) - } - SyncStatus.FAILED -> SyncStatusView.Failed - SyncStatus.INCOMPATIBLE_VERSION -> SyncStatusView.IncompatibleVersion - } -} - -fun NetworkMode.syncedStatusToView(networkId: String?): SyncStatusView { - when (this) { - NetworkMode.DEFAULT -> return SyncStatusView.Synced.AnyNetwork - NetworkMode.LOCAL -> { - return if (networkId.isNullOrEmpty()) { - SyncStatusView.Synced.LocalOnly - } else { - SyncStatusView.Unknown - } - } - NetworkMode.CUSTOM -> { - return if (networkId == NODE_STAGING_ID) { - SyncStatusView.Synced.StagingNetwork - } else { - SyncStatusView.Synced.SelfHostedNetwork - } - } - } -} diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/collections/CollectionCreateAndAddObjectTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/collections/CollectionCreateAndAddObjectTest.kt index 4e0a51fda5..7511973caf 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/collections/CollectionCreateAndAddObjectTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/collections/CollectionCreateAndAddObjectTest.kt @@ -81,7 +81,6 @@ class CollectionCreateAndAddObjectTest: ObjectSetViewModelTestSetup() { closeBlock = closeBlock, updateText = updateText, interceptEvents = interceptEvents, - interceptThreadStatus = interceptThreadStatus, createDataViewObject = CreateDataViewObject( repo = repo, spaceManager = spaceManager, @@ -98,7 +97,6 @@ class CollectionCreateAndAddObjectTest: ObjectSetViewModelTestSetup() { createObject = createObject, setObjectDetails = setObjectDetails, paginator = paginator, - cancelSearchSubscription = cancelSearchSubscription, database = database, dataViewSubscriptionContainer = dataViewSubscriptionContainer, storeOfRelations = storeOfRelations, @@ -118,14 +116,14 @@ class CollectionCreateAndAddObjectTest: ObjectSetViewModelTestSetup() { getObjectTypes = getObjectTypes, storelessSubscriptionContainer = storelessSubscriptionContainer, dispatchers = dispatchers, - getNetworkMode = getNetworkMode, dateProvider = dateProvider, vmParams = ObjectSetViewModel.Params( ctx = root, space = SpaceId(defaultSpace) ), permissions = permissions, - analyticSpaceHelperDelegate = analyticSpaceHelperDelegate + analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, + spaceSyncAndP2PStatusProvider = spaceSyncAndP2PStatusProvider ) stubNetworkMode() stubObservePermissions() diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/EditorViewModelTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/EditorViewModelTest.kt index b3893b4583..4f9d26a26c 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/EditorViewModelTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/EditorViewModelTest.kt @@ -87,7 +87,6 @@ import com.anytypeio.anytype.domain.relations.AddRelationToObject import com.anytypeio.anytype.domain.relations.SetRelationKey import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.domain.sets.FindObjectSetForType -import com.anytypeio.anytype.domain.status.InterceptThreadStatus import com.anytypeio.anytype.domain.table.CreateTable import com.anytypeio.anytype.domain.table.FillTableRow import com.anytypeio.anytype.domain.templates.ApplyTemplate @@ -120,6 +119,7 @@ import com.anytypeio.anytype.presentation.editor.render.parseThemeBackgroundColo import com.anytypeio.anytype.presentation.editor.selection.SelectionStateHolder import com.anytypeio.anytype.presentation.editor.toggle.ToggleStateHolder import com.anytypeio.anytype.presentation.navigation.AppNavigation +import com.anytypeio.anytype.presentation.sync.SpaceSyncAndP2PStatusProvider import com.anytypeio.anytype.presentation.templates.ObjectTypeTemplatesContainer import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory import com.anytypeio.anytype.presentation.util.CoroutinesTestRule @@ -185,9 +185,6 @@ open class EditorViewModelTest { @Mock lateinit var interceptEvents: InterceptEvents - @Mock - lateinit var interceptThreadStatus: InterceptThreadStatus - @Mock lateinit var createBlock: CreateBlock @@ -320,9 +317,6 @@ open class EditorViewModelTest { @Mock lateinit var copyFileToCacheDirectory: CopyFileToCacheDirectory - @Mock - lateinit var getTemplates: GetTemplates - @Mock lateinit var applyTemplate: ApplyTemplate @@ -364,6 +358,9 @@ open class EditorViewModelTest { @Mock lateinit var permissions: UserPermissionProvider + @Mock + lateinit var spaceSyncAndP2PStatusProvider: SpaceSyncAndP2PStatusProvider + lateinit var vm: EditorViewModel private lateinit var builder: UrlBuilder @@ -3739,8 +3736,8 @@ open class EditorViewModelTest { } fun stubInterceptThreadStatus() { - interceptThreadStatus.stub { - onBlocking { build(any()) } doReturn emptyFlow() + spaceSyncAndP2PStatusProvider.stub { + onBlocking { observe() } doReturn emptyFlow() } } @@ -3868,7 +3865,6 @@ open class EditorViewModelTest { createBlockLinkWithObject = createBlockLinkWithObject, createObjectAsMentionOrLink = createObjectAsMentionOrLink, interceptEvents = interceptEvents, - interceptThreadStatus = interceptThreadStatus, updateLinkMarks = updateLinkMark, removeLinkMark = removeLinkMark, reducer = DocumentExternalEventReducer(), @@ -3953,13 +3949,13 @@ open class EditorViewModelTest { templatesContainer = templatesContainer, storelessSubscriptionContainer = storelessSubscriptionContainer, dispatchers = dispatchers, - getNetworkMode = getNetworkMode, vmParams = EditorViewModel.Params( ctx = root, space = spaceId ), permissions = permissions, - analyticSpaceHelperDelegate = analyticSpaceHelperDelegate + analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, + spaceSyncAndP2PStatusProvider = spaceSyncAndP2PStatusProvider ) } diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorPresentationTestSetup.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorPresentationTestSetup.kt index d1b081f211..0637a05da4 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorPresentationTestSetup.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorPresentationTestSetup.kt @@ -78,7 +78,6 @@ import com.anytypeio.anytype.domain.relations.AddRelationToObject import com.anytypeio.anytype.domain.relations.SetRelationKey import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.domain.sets.FindObjectSetForType -import com.anytypeio.anytype.domain.status.InterceptThreadStatus import com.anytypeio.anytype.domain.table.CreateTable import com.anytypeio.anytype.domain.table.CreateTableColumn import com.anytypeio.anytype.domain.table.CreateTableRow @@ -112,6 +111,7 @@ import com.anytypeio.anytype.presentation.editor.render.DefaultBlockViewRenderer import com.anytypeio.anytype.presentation.editor.selection.SelectionStateHolder import com.anytypeio.anytype.presentation.editor.toggle.ToggleStateHolder import com.anytypeio.anytype.presentation.home.UserPermissionProviderStub +import com.anytypeio.anytype.presentation.sync.SpaceSyncAndP2PStatusProvider import com.anytypeio.anytype.presentation.templates.ObjectTypeTemplatesContainer import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory import com.anytypeio.anytype.presentation.util.Dispatcher @@ -142,9 +142,6 @@ open class EditorPresentationTestSetup { @Mock lateinit var interceptEvents: InterceptEvents - @Mock - lateinit var interceptThreadStatus: InterceptThreadStatus - @Mock lateinit var createBlock: CreateBlock @@ -352,9 +349,6 @@ open class EditorPresentationTestSetup { @Mock lateinit var getObjectTypes: GetObjectTypes - @Mock - lateinit var fileLimitsEventChannel: FileLimitsEventChannel - @Mock lateinit var interceptFileLimitEvents: InterceptFileLimitEvents @@ -373,6 +367,9 @@ open class EditorPresentationTestSetup { @Mock lateinit var getNetworkMode: GetNetworkMode + @Mock + lateinit var spaceSyncAndP2PStatusProvider: SpaceSyncAndP2PStatusProvider + var permissions: UserPermissionProvider = UserPermissionProviderStub() open fun buildViewModel(urlBuilder: UrlBuilder = builder): EditorViewModel { @@ -451,7 +448,6 @@ open class EditorPresentationTestSetup { createBlockLinkWithObject = createBlockLinkWithObject, createObjectAsMentionOrLink = createObjectAsMentionOrLink, interceptEvents = interceptEvents, - interceptThreadStatus = interceptThreadStatus, updateLinkMarks = updateLinkMark, removeLinkMark = removeLinkMark, reducer = DocumentExternalEventReducer(), @@ -492,13 +488,13 @@ open class EditorPresentationTestSetup { templatesContainer = templatesContainer, storelessSubscriptionContainer = storelessSubscriptionContainer, dispatchers = dispatchers, - getNetworkMode = getNetworkMode, vmParams = EditorViewModel.Params( ctx = root, space = SpaceId(defaultSpace) ), permissions = permissions, - analyticSpaceHelperDelegate = analyticSpaceHelperDelegate + analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, + spaceSyncAndP2PStatusProvider = spaceSyncAndP2PStatusProvider ) } @@ -569,8 +565,8 @@ open class EditorPresentationTestSetup { } fun stubInterceptThreadStatus() { - interceptThreadStatus.stub { - onBlocking { build(any()) } doReturn emptyFlow() + spaceSyncAndP2PStatusProvider.stub { + onBlocking { observe() } doReturn emptyFlow() } } diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetViewModelTestSetup.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetViewModelTestSetup.kt index 4ac035c6ac..edd73f2061 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetViewModelTestSetup.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/sets/main/ObjectSetViewModelTestSetup.kt @@ -58,7 +58,6 @@ import com.anytypeio.anytype.domain.search.DataViewSubscriptionContainer import com.anytypeio.anytype.domain.search.SubscriptionEventChannel import com.anytypeio.anytype.domain.sets.OpenObjectSet import com.anytypeio.anytype.domain.sets.SetQueryToObjectSet -import com.anytypeio.anytype.domain.status.InterceptThreadStatus import com.anytypeio.anytype.domain.templates.CreateTemplate import com.anytypeio.anytype.domain.unsplash.DownloadUnsplashImage import com.anytypeio.anytype.domain.workspace.SpaceManager @@ -82,6 +81,7 @@ import com.anytypeio.anytype.presentation.sets.subscription.DataViewSubscription import com.anytypeio.anytype.presentation.sets.subscription.DefaultDataViewSubscription import com.anytypeio.anytype.presentation.sets.updateFormatForSubscription import com.anytypeio.anytype.presentation.sets.viewer.ViewerDelegate +import com.anytypeio.anytype.presentation.sync.SpaceSyncAndP2PStatusProvider import com.anytypeio.anytype.presentation.templates.ObjectTypeTemplatesContainer import com.anytypeio.anytype.presentation.util.DefaultCoroutineTestRule import com.anytypeio.anytype.presentation.util.Dispatcher @@ -131,9 +131,6 @@ open class ObjectSetViewModelTestSetup { @Mock lateinit var interceptEvents: InterceptEvents - @Mock - lateinit var interceptThreadStatus: InterceptThreadStatus - @Mock lateinit var gateway: Gateway @@ -210,6 +207,9 @@ open class ObjectSetViewModelTestSetup { @Mock lateinit var analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate + @Mock + lateinit var spaceSyncAndP2PStatusProvider: SpaceSyncAndP2PStatusProvider + var permissions: UserPermissionProvider = UserPermissionProviderStub() lateinit var spaceConfig: Config @@ -262,7 +262,6 @@ open class ObjectSetViewModelTestSetup { closeBlock = closeBlock, updateText = updateText, interceptEvents = interceptEvents, - interceptThreadStatus = interceptThreadStatus, createDataViewObject = createDataViewObject, dispatcher = dispatcher, delegator = delegator, @@ -275,7 +274,6 @@ open class ObjectSetViewModelTestSetup { createObject = createObject, setObjectDetails = setObjectDetails, paginator = paginator, - cancelSearchSubscription = cancelSearchSubscription, database = database, dataViewSubscriptionContainer = dataViewSubscriptionContainer, storeOfRelations = storeOfRelations, @@ -295,14 +293,14 @@ open class ObjectSetViewModelTestSetup { getObjectTypes = getObjectTypes, storelessSubscriptionContainer = storelessSubscriptionContainer, dispatchers = dispatchers, - getNetworkMode = getNetworkMode, dateProvider = DateProviderImpl(), vmParams = ObjectSetViewModel.Params( ctx = root, space = SpaceId(defaultSpace) ), permissions = permissions, - analyticSpaceHelperDelegate = analyticSpaceHelperDelegate + analyticSpaceHelperDelegate = analyticSpaceHelperDelegate, + spaceSyncAndP2PStatusProvider = spaceSyncAndP2PStatusProvider ) } @@ -315,11 +313,9 @@ open class ObjectSetViewModelTestSetup { } } - fun stubInterceptThreadStatus( - params: InterceptThreadStatus.Params = InterceptThreadStatus.Params(ctx = root) - ) { - interceptThreadStatus.stub { - onBlocking { build(params) } doReturn emptyFlow() + fun stubInterceptThreadStatus() { + spaceSyncAndP2PStatusProvider.stub { + onBlocking { observe() } doReturn emptyFlow() } }