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

DROID-2644 Sync Status | Editor + sets implementation (#1422)

This commit is contained in:
Konstantin Ivanov 2024-07-25 12:59:47 +02:00 committed by GitHub
parent ad96048321
commit e67704f7f2
Signed by: github
GPG key ID: B5690EEEBB952194
63 changed files with 1289 additions and 605 deletions

View file

@ -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()
}
}

View file

@ -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()
}
}

View file

@ -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()

View file

@ -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 {

View file

@ -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<Payload>,
@ -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(

View file

@ -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

View file

@ -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)
}

View file

@ -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<FragmentEditorBinding>(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<FragmentEditorBinding>(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<FragmentEditorBinding>(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<FragmentEditorBinding>(R.layout.f
}
.launchIn(lifecycleScope)
vm.syncStatus.onEach { status -> bindSyncStatus(status) }.launchIn(lifecycleScope)
vm.isSyncStatusVisible.onEach { isSyncStatusVisible ->
if (isSyncStatusVisible)
binding.topToolbar.findViewById<ViewGroup>(R.id.statusContainer).visible()
else
binding.topToolbar.findViewById<ViewGroup>(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<FragmentEditorBinding>(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() {

View file

@ -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<StatusBadgeWidget>(R.id.statusBadge).bind(status)
val tvStatus = binding.topToolbar.root.findViewById<TextView>(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)
}

View file

@ -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()
}

View file

@ -299,4 +299,10 @@
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal" />
<androidx.compose.ui.platform.ComposeView
android:id="@+id/syncStatusWidget"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal" />
</FrameLayout>

View file

@ -188,4 +188,12 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<androidx.compose.ui.platform.ComposeView
android:id="@+id/syncStatusWidget"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.motion.widget.MotionLayout>

View file

@ -97,6 +97,12 @@
app:applyMotionScene="false"
app:visibilityMode="ignore"/>
</Constraint>
<Constraint
android:id="@id/syncStatusWidget">
<PropertySet
app:applyMotionScene="false"
app:visibilityMode="ignore"/>
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
@ -185,6 +191,12 @@
app:applyMotionScene="false"
app:visibilityMode="ignore"/>
</Constraint>
<Constraint
android:id="@id/syncStatusWidget">
<PropertySet
app:applyMotionScene="false"
app:visibilityMode="ignore"/>
</Constraint>
</ConstraintSet>
</MotionScene>

View file

@ -1,10 +0,0 @@
package com.anytypeio.anytype.core_models
enum class SyncStatus {
UNKNOWN,
OFFLINE,
SYNCING,
SYNCED,
FAILED,
INCOMPATIBLE_VERSION
}

View file

@ -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,

View file

@ -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 -> ""
}

View file

@ -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)
}

View file

@ -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()
}
}
}

View file

@ -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) {

View file

@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/orange" />
<size
android:width="10dp"
android:height="10dp" />
</shape>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="10dp"
android:height="10dp"
android:viewportWidth="10"
android:viewportHeight="10">
<path
android:pathData="M5,5m-5,0a5,5 0,1 1,10 0a5,5 0,1 1,-10 0"
android:fillColor="#F55522"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="10dp"
android:height="10dp"
android:viewportWidth="10"
android:viewportHeight="10">
<path
android:pathData="M5,5m-5,0a5,5 0,1 1,10 0a5,5 0,1 1,-10 0"
android:fillColor="#A7A7A7"/>
</vector>

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="M24,0L24,0A24,24 0,0 1,48 24L48,24A24,24 0,0 1,24 48L24,48A24,24 0,0 1,0 24L0,24A24,24 0,0 1,24 0z"
android:fillColor="@color/shape_secondary"/>
<path
android:pathData="M17,16.5C17,15.119 18.119,14 19.5,14H29.5C30.881,14 32,15.119 32,16.5V31.5C32,32.881 30.881,34 29.5,34H19.5C18.119,34 17,32.881 17,31.5V16.5ZM19.5,15C18.672,15 18,15.672 18,16.5V31.5C18,32.328 18.672,33 19.5,33H29.5C30.328,33 31,32.328 31,31.5V16.5C31,15.672 30.328,15 29.5,15H19.5ZM22.5,17.25C22.5,16.836 22.836,16.5 23.25,16.5H25.75C26.164,16.5 26.5,16.836 26.5,17.25C26.5,17.664 26.164,18 25.75,18H23.25C22.836,18 22.5,17.664 22.5,17.25ZM21.25,30C20.836,30 20.5,30.336 20.5,30.75C20.5,31.164 20.836,31.5 21.25,31.5H27.75C28.164,31.5 28.5,31.164 28.5,30.75C28.5,30.336 28.164,30 27.75,30H21.25Z"
android:fillColor="@color/glyph_active"
android:fillType="evenOdd"/>
</vector>

View file

@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="M24,0L24,0A24,24 0,0 1,48 24L48,24A24,24 0,0 1,24 48L24,48A24,24 0,0 1,0 24L0,24A24,24 0,0 1,24 0z"
android:fillColor="@color/palette_light_lime"/>
<path
android:pathData="M20.5,16L20.5,16A0.5,0.5 0,0 1,21 16.5L21,19.5A0.5,0.5 0,0 1,20.5 20L20.5,20A0.5,0.5 0,0 1,20 19.5L20,16.5A0.5,0.5 0,0 1,20.5 16z"
android:fillColor="@color/palette_dark_lime"/>
<path
android:pathData="M27.5,16L27.5,16A0.5,0.5 0,0 1,28 16.5L28,19.5A0.5,0.5 0,0 1,27.5 20L27.5,20A0.5,0.5 0,0 1,27 19.5L27,16.5A0.5,0.5 0,0 1,27.5 16z"
android:fillColor="@color/palette_dark_lime"/>
<path
android:pathData="M16.667,25C17.035,25 17.329,25.3 17.376,25.665C17.753,28.574 20.575,30.833 24,30.833C27.425,30.833 30.247,28.574 30.624,25.665C30.671,25.3 30.965,25 31.333,25C31.701,25 32.004,25.3 31.964,25.666C31.581,29.219 28.162,32 24,32C19.838,32 16.419,29.219 16.036,25.666C15.996,25.3 16.299,25 16.667,25Z"
android:fillColor="@color/palette_dark_lime"
android:fillType="evenOdd"/>
</vector>

View file

@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="M24,0L24,0A24,24 0,0 1,48 24L48,24A24,24 0,0 1,24 48L24,48A24,24 0,0 1,0 24L0,24A24,24 0,0 1,24 0z"
android:fillColor="@color/shape_secondary"/>
<path
android:pathData="M20.5,16L20.5,16A0.5,0.5 0,0 1,21 16.5L21,19.5A0.5,0.5 0,0 1,20.5 20L20.5,20A0.5,0.5 0,0 1,20 19.5L20,16.5A0.5,0.5 0,0 1,20.5 16z"
android:fillColor="@color/glyph_active"/>
<path
android:pathData="M27.5,16L27.5,16A0.5,0.5 0,0 1,28 16.5L28,19.5A0.5,0.5 0,0 1,27.5 20L27.5,20A0.5,0.5 0,0 1,27 19.5L27,16.5A0.5,0.5 0,0 1,27.5 16z"
android:fillColor="@color/glyph_active"/>
<path
android:strokeWidth="1"
android:pathData="M16,29H32"
android:fillColor="#00000000"
android:strokeColor="@color/glyph_active"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="M24,0L24,0A24,24 0,0 1,48 24L48,24A24,24 0,0 1,24 48L24,48A24,24 0,0 1,0 24L0,24A24,24 0,0 1,24 0z"
android:fillColor="@color/palette_light_red"/>
<path
android:pathData="M20.5,16L20.5,16A0.5,0.5 0,0 1,21 16.5L21,19.5A0.5,0.5 0,0 1,20.5 20L20.5,20A0.5,0.5 0,0 1,20 19.5L20,16.5A0.5,0.5 0,0 1,20.5 16z"
android:fillColor="@color/palette_system_red"/>
<path
android:pathData="M27.5,16L27.5,16A0.5,0.5 0,0 1,28 16.5L28,19.5A0.5,0.5 0,0 1,27.5 20L27.5,20A0.5,0.5 0,0 1,27 19.5L27,16.5A0.5,0.5 0,0 1,27.5 16z"
android:fillColor="@color/palette_system_red"/>
<path
android:strokeWidth="1"
android:pathData="M16,30L31.912,28.327"
android:fillColor="#00000000"
android:strokeColor="@color/palette_system_red"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="M24,0L24,0A24,24 0,0 1,48 24L48,24A24,24 0,0 1,24 48L24,48A24,24 0,0 1,0 24L0,24A24,24 0,0 1,24 0z"
android:fillColor="@color/shape_secondary"/>
<path
android:pathData="M20.5,16L20.5,16A0.5,0.5 0,0 1,21 16.5L21,19.5A0.5,0.5 0,0 1,20.5 20L20.5,20A0.5,0.5 0,0 1,20 19.5L20,16.5A0.5,0.5 0,0 1,20.5 16z"
android:fillColor="@color/glyph_active"/>
<path
android:pathData="M27.5,16L27.5,16A0.5,0.5 0,0 1,28 16.5L28,19.5A0.5,0.5 0,0 1,27.5 20L27.5,20A0.5,0.5 0,0 1,27 19.5L27,16.5A0.5,0.5 0,0 1,27.5 16z"
android:fillColor="@color/glyph_active"/>
<path
android:strokeWidth="1"
android:pathData="M16,29H32"
android:fillColor="#00000000"
android:strokeColor="@color/glyph_active"
android:strokeLineCap="round"/>
</vector>

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="M24,0L24,0A24,24 0,0 1,48 24L48,24A24,24 0,0 1,24 48L24,48A24,24 0,0 1,0 24L0,24A24,24 0,0 1,24 0z"
android:fillColor="@color/palette_light_lime"/>
<path
android:pathData="M26,18C26,19.105 25.105,20 24,20C22.895,20 22,19.105 22,18C22,16.895 22.895,16 24,16C25.105,16 26,16.895 26,18ZM27,18C27,19.657 25.657,21 24,21C22.343,21 21,19.657 21,18C21,16.343 22.343,15 24,15C25.657,15 27,16.343 27,18ZM26,30C26,31.105 25.105,32 24,32C22.895,32 22,31.105 22,30C22,28.895 22.895,28 24,28C25.105,28 26,28.895 26,30ZM27,30C27,31.657 25.657,33 24,33C22.343,33 21,31.657 21,30C21,28.343 22.343,27 24,27C25.657,27 27,28.343 27,30ZM18,26C19.105,26 20,25.105 20,24C20,22.895 19.105,22 18,22C16.895,22 16,22.895 16,24C16,25.105 16.895,26 18,26ZM18,27C19.657,27 21,25.657 21,24C21,22.343 19.657,21 18,21C16.343,21 15,22.343 15,24C15,25.657 16.343,27 18,27ZM32,24C32,25.105 31.105,26 30,26C28.895,26 28,25.105 28,24C28,22.895 28.895,22 30,22C31.105,22 32,22.895 32,24ZM33,24C33,25.657 31.657,27 30,27C28.343,27 27,25.657 27,24C27,22.343 28.343,21 30,21C31.657,21 33,22.343 33,24Z"
android:fillColor="@color/palette_dark_lime"
android:fillType="evenOdd"/>
</vector>

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="M24,0L24,0A24,24 0,0 1,48 24L48,24A24,24 0,0 1,24 48L24,48A24,24 0,0 1,0 24L0,24A24,24 0,0 1,24 0z"
android:fillColor="@color/shape_secondary"/>
<path
android:pathData="M26,18C26,19.105 25.105,20 24,20C22.895,20 22,19.105 22,18C22,16.895 22.895,16 24,16C25.105,16 26,16.895 26,18ZM27,18C27,19.657 25.657,21 24,21C22.343,21 21,19.657 21,18C21,16.343 22.343,15 24,15C25.657,15 27,16.343 27,18ZM26,30C26,31.105 25.105,32 24,32C22.895,32 22,31.105 22,30C22,28.895 22.895,28 24,28C25.105,28 26,28.895 26,30ZM27,30C27,31.657 25.657,33 24,33C22.343,33 21,31.657 21,30C21,28.343 22.343,27 24,27C25.657,27 27,28.343 27,30ZM18,26C19.105,26 20,25.105 20,24C20,22.895 19.105,22 18,22C16.895,22 16,22.895 16,24C16,25.105 16.895,26 18,26ZM18,27C19.657,27 21,25.657 21,24C21,22.343 19.657,21 18,21C16.343,21 15,22.343 15,24C15,25.657 16.343,27 18,27ZM32,24C32,25.105 31.105,26 30,26C28.895,26 28,25.105 28,24C28,22.895 28.895,22 30,22C31.105,22 32,22.895 32,24ZM33,24C33,25.657 31.657,27 30,27C28.343,27 27,25.657 27,24C27,22.343 28.343,21 30,21C31.657,21 33,22.343 33,24Z"
android:fillColor="@color/glyph_active"
android:fillType="evenOdd"/>
</vector>

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="M24,0L24,0A24,24 0,0 1,48 24L48,24A24,24 0,0 1,24 48L24,48A24,24 0,0 1,0 24L0,24A24,24 0,0 1,24 0z"
android:fillColor="@color/palette_light_red"/>
<path
android:pathData="M26,18C26,19.105 25.105,20 24,20C22.895,20 22,19.105 22,18C22,16.895 22.895,16 24,16C25.105,16 26,16.895 26,18ZM27,18C27,19.657 25.657,21 24,21C22.343,21 21,19.657 21,18C21,16.343 22.343,15 24,15C25.657,15 27,16.343 27,18ZM26,30C26,31.105 25.105,32 24,32C22.895,32 22,31.105 22,30C22,28.895 22.895,28 24,28C25.105,28 26,28.895 26,30ZM27,30C27,31.657 25.657,33 24,33C22.343,33 21,31.657 21,30C21,28.343 22.343,27 24,27C25.657,27 27,28.343 27,30ZM18,26C19.105,26 20,25.105 20,24C20,22.895 19.105,22 18,22C16.895,22 16,22.895 16,24C16,25.105 16.895,26 18,26ZM18,27C19.657,27 21,25.657 21,24C21,22.343 19.657,21 18,21C16.343,21 15,22.343 15,24C15,25.657 16.343,27 18,27ZM32,24C32,25.105 31.105,26 30,26C28.895,26 28,25.105 28,24C28,22.895 28.895,22 30,22C31.105,22 32,22.895 32,24ZM33,24C33,25.657 31.657,27 30,27C28.343,27 27,25.657 27,24C27,22.343 28.343,21 30,21C31.657,21 33,22.343 33,24Z"
android:fillColor="@color/palette_dark_red"
android:fillType="evenOdd"/>
</vector>

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="M24,0L24,0A24,24 0,0 1,48 24L48,24A24,24 0,0 1,24 48L24,48A24,24 0,0 1,0 24L0,24A24,24 0,0 1,24 0z"
android:fillColor="@color/palette_light_lime"/>
<path
android:pathData="M17,15.5C15.619,15.5 14.5,16.619 14.5,18V21C14.5,22.381 15.619,23.5 17,23.5H31C32.381,23.5 33.5,22.381 33.5,21V18C33.5,16.619 32.381,15.5 31,15.5H17ZM15.5,18C15.5,17.172 16.172,16.5 17,16.5H31C31.828,16.5 32.5,17.172 32.5,18V21C32.5,21.828 31.828,22.5 31,22.5H17C16.172,22.5 15.5,21.828 15.5,21V18ZM17,24.5C15.619,24.5 14.5,25.619 14.5,27V30C14.5,31.381 15.619,32.5 17,32.5H31C32.381,32.5 33.5,31.381 33.5,30V27C33.5,25.619 32.381,24.5 31,24.5H17ZM15.5,27C15.5,26.172 16.172,25.5 17,25.5H31C31.828,25.5 32.5,26.172 32.5,27V30C32.5,30.828 31.828,31.5 31,31.5H17C16.172,31.5 15.5,30.828 15.5,30V27ZM17,19.5C17,18.672 17.672,18 18.5,18C19.328,18 20,18.672 20,19.5C20,20.328 19.328,21 18.5,21C17.672,21 17,20.328 17,19.5ZM18.5,27C17.672,27 17,27.672 17,28.5C17,29.328 17.672,30 18.5,30C19.328,30 20,29.328 20,28.5C20,27.672 19.328,27 18.5,27Z"
android:fillColor="@color/palette_dark_lime"
android:fillType="evenOdd"/>
</vector>

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="M24,0L24,0A24,24 0,0 1,48 24L48,24A24,24 0,0 1,24 48L24,48A24,24 0,0 1,0 24L0,24A24,24 0,0 1,24 0z"
android:fillColor="@color/shape_secondary"/>
<path
android:pathData="M17,15.5C15.619,15.5 14.5,16.619 14.5,18V21C14.5,22.381 15.619,23.5 17,23.5H31C32.381,23.5 33.5,22.381 33.5,21V18C33.5,16.619 32.381,15.5 31,15.5H17ZM15.5,18C15.5,17.172 16.172,16.5 17,16.5H31C31.828,16.5 32.5,17.172 32.5,18V21C32.5,21.828 31.828,22.5 31,22.5H17C16.172,22.5 15.5,21.828 15.5,21V18ZM17,24.5C15.619,24.5 14.5,25.619 14.5,27V30C14.5,31.381 15.619,32.5 17,32.5H31C32.381,32.5 33.5,31.381 33.5,30V27C33.5,25.619 32.381,24.5 31,24.5H17ZM15.5,27C15.5,26.172 16.172,25.5 17,25.5H31C31.828,25.5 32.5,26.172 32.5,27V30C32.5,30.828 31.828,31.5 31,31.5H17C16.172,31.5 15.5,30.828 15.5,30V27ZM17,19.5C17,18.672 17.672,18 18.5,18C19.328,18 20,18.672 20,19.5C20,20.328 19.328,21 18.5,21C17.672,21 17,20.328 17,19.5ZM18.5,27C17.672,27 17,27.672 17,28.5C17,29.328 17.672,30 18.5,30C19.328,30 20,29.328 20,28.5C20,27.672 19.328,27 18.5,27Z"
android:fillColor="@color/glyph_active"
android:fillType="evenOdd"/>
</vector>

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="M24,0L24,0A24,24 0,0 1,48 24L48,24A24,24 0,0 1,24 48L24,48A24,24 0,0 1,0 24L0,24A24,24 0,0 1,24 0z"
android:fillColor="@color/palette_light_red"/>
<path
android:pathData="M17,15.5C15.619,15.5 14.5,16.619 14.5,18V21C14.5,22.381 15.619,23.5 17,23.5H31C32.381,23.5 33.5,22.381 33.5,21V18C33.5,16.619 32.381,15.5 31,15.5H17ZM15.5,18C15.5,17.172 16.172,16.5 17,16.5H31C31.828,16.5 32.5,17.172 32.5,18V21C32.5,21.828 31.828,22.5 31,22.5H17C16.172,22.5 15.5,21.828 15.5,21V18ZM17,24.5C15.619,24.5 14.5,25.619 14.5,27V30C14.5,31.381 15.619,32.5 17,32.5H31C32.381,32.5 33.5,31.381 33.5,30V27C33.5,25.619 32.381,24.5 31,24.5H17ZM15.5,27C15.5,26.172 16.172,25.5 17,25.5H31C31.828,25.5 32.5,26.172 32.5,27V30C32.5,30.828 31.828,31.5 31,31.5H17C16.172,31.5 15.5,30.828 15.5,30V27ZM17,19.5C17,18.672 17.672,18 18.5,18C19.328,18 20,18.672 20,19.5C20,20.328 19.328,21 18.5,21C17.672,21 17,20.328 17,19.5ZM18.5,27C17.672,27 17,27.672 17,28.5C17,29.328 17.672,30 18.5,30C19.328,30 20,29.328 20,28.5C20,27.672 19.328,27 18.5,27Z"
android:fillColor="@color/palette_dark_red"
android:fillType="evenOdd"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="10dp"
android:height="10dp"
android:viewportWidth="10"
android:viewportHeight="10">
<path
android:pathData="M5,5m-5,0a5,5 0,1 1,10 0a5,5 0,1 1,-10 0"
android:fillColor="#5DD400"/>
</vector>

View file

@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:pathData="M10,10m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"
android:strokeAlpha="0.2"
android:fillColor="@color/palette_system_green"
android:fillAlpha="0.2"/>
<path
android:pathData="M10,10m-7,0a7,7 0,1 1,14 0a7,7 0,1 1,-14 0"
android:strokeAlpha="0.4"
android:fillColor="@color/palette_system_green"
android:fillAlpha="0.4"/>
<path
android:pathData="M10,10m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0"
android:fillColor="@color/palette_system_green"/>
</vector>

View file

@ -5,7 +5,7 @@
<shape
android:shape="rectangle">
<corners android:radius="7dp" />
<solid android:color="#59000000" />
<solid android:color="#26000000" />
</shape>
</item>
</ripple>

View file

@ -3,40 +3,16 @@
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/statusContainer"
android:layout_width="wrap_content"
<com.anytypeio.anytype.core_ui.widgets.StatusBadgeWidget
android:id="@+id/statusBadge"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
android:paddingStart="10dp"
android:paddingEnd="10dp">
<com.anytypeio.anytype.core_ui.widgets.StatusBadgeWidget
android:id="@+id/statusBadge"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_gravity="center_vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:backgroundTint="@color/orange" />
<TextView
android:id="@+id/tvStatus"
style="@style/TextView.UXStyle.Captions.1.Regular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="6dp"
android:textColor="@color/text_secondary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/statusBadge"
app:layout_constraintTop_toTopOf="parent"
app:layout_goneMarginStart="0dp"
tools:text="Syncing" />
</androidx.constraintlayout.widget.ConstraintLayout>
android:scaleType="center"
android:layout_marginStart="12dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<FrameLayout
android:layout_width="match_parent"

View file

@ -10,8 +10,6 @@ interface FeatureToggles {
val isConciseLogging: Boolean
val excludeThreadStatusLogging: Boolean
val isLogEditorViewModelEvents: Boolean
val isLogEditorControlPanelMachine: Boolean

View file

@ -6,14 +6,14 @@ import com.anytypeio.anytype.domain.workspace.P2PStatusChannel
import kotlinx.coroutines.flow.Flow
interface P2PStatusRemoteChannel {
fun observe(activeSpaceId: Id): Flow<List<P2PStatusUpdate>>
fun observe(activeSpaceId: Id): Flow<P2PStatusUpdate>
}
class P2PStatusDataChannel(
private val channel: P2PStatusRemoteChannel
) : P2PStatusChannel {
override fun observe(activeSpaceId: Id): Flow<List<P2PStatusUpdate>> {
override fun observe(activeSpaceId: Id): Flow<P2PStatusUpdate> {
return channel.observe(activeSpaceId)
}
}

View file

@ -6,14 +6,14 @@ import com.anytypeio.anytype.domain.workspace.SpaceSyncStatusChannel
import kotlinx.coroutines.flow.Flow
interface SpaceStatusRemoteChannel {
fun observe(activeSpaceId: String): Flow<List<SpaceSyncUpdate>>
fun observe(activeSpaceId: String): Flow<SpaceSyncUpdate>
}
class SpaceStatusDataChannel(
private val channel: SpaceStatusRemoteChannel
) : SpaceSyncStatusChannel {
override fun observe(activeSpaceId: Id): Flow<List<SpaceSyncUpdate>> {
override fun observe(activeSpaceId: Id): Flow<SpaceSyncUpdate> {
return channel.observe(activeSpaceId)
}
}

View file

@ -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<SyncStatus> {
return remote.observe(ctx)
}
}

View file

@ -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<SyncStatus>
}

View file

@ -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<SyncStatus, InterceptThreadStatus.Params>() {
override fun build(params: Params?): Flow<SyncStatus> {
checkNotNull(params) { "Params are required for this use-case." }
return channel.observe(params.ctx).flowOn(context)
}
data class Params(val ctx: Id)
}

View file

@ -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<Device>,
) {
data class Device(
val name: String,
val isOnline: Boolean,
val lastPulled: Int,
val lastEdited: Int,
)
}

View file

@ -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<SyncStatus>
}

View file

@ -5,5 +5,5 @@ import com.anytypeio.anytype.core_models.multiplayer.P2PStatusUpdate
import kotlinx.coroutines.flow.Flow
interface P2PStatusChannel {
fun observe(activeSpaceId: Id): Flow<List<P2PStatusUpdate>>
fun observe(activeSpaceId: Id): Flow<P2PStatusUpdate>
}

View file

@ -5,5 +5,5 @@ import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncUpdate
import kotlinx.coroutines.flow.Flow
interface SpaceSyncStatusChannel {
fun observe(activeSpaceId: Id): Flow<List<SpaceSyncUpdate>>
fun observe(activeSpaceId: Id): Flow<SpaceSyncUpdate>
}

View file

@ -1698,4 +1698,39 @@ Please provide specific details of your needs here.</string>
<string name="sort_empty_values_top">On top</string>
<string name="widget_view_see_all_objects">See all objects</string>
<string name="sync_status_get_error">Error getting sync status</string>
<string name="sync_status_local_only_title">Local Only</string>
<string name="sync_status_data_backup">Data backup is disabled</string>
<string name="sync_status_self_host">Self Host</string>
<string name="sync_status_self_host_synced">Synced</string>
<plurals name="sync_status_self_host_syncing">
<item quantity="one">%1$d item syncing</item>
<item quantity="other">%1$d items syncing...</item>
</plurals>
<string name="sync_status_p2p">P2P Connection</string>
<plurals name="sync_status_p2p_devices">
<item quantity="one">%1$d device connected</item>
<item quantity="other">%1$d devices connected</item>
</plurals>
<string name="sync_status_p2p_connecting">P2P Connecting...</string>
<string name="sync_status_p2p_disabled">Disabled</string>
<string name="sync_status_p2p_restricted">Restricted. Check device settings.</string>
<string name="sync_status_anytype_network">Anytype Network</string>
<string name="sync_status_anytype_end_to_end">End-to-end encrypted</string>
<plurals name="sync_status_network_items">
<item quantity="one">%1$d item syncing...</item>
<item quantity="other">%1$d items syncing...</item>
</plurals>
<string name="sync_status_anytype_network_connecting">Network Connecting...</string>
<string name="sync_status_anytype_network_no_connecting">No connection</string>
<string name="sync_status_storage_limit_exceed">Storage limit reached</string>
<string name="sync_status_incompatible_version">Incompatible version</string>
<string name="sync_status_network_error">No access to the space</string>
<string name="sync_status_unrecognized">Unrecognized error</string>
</resources>

View file

@ -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")
}
}

View file

@ -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
}
}
}
}

View file

@ -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<List<P2PStatusUpdate>> {
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<P2PStatusUpdate> {
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
)
}
}
}
}

View file

@ -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<List<SpaceSyncUpdate>> {
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<SpaceSyncUpdate> {
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
)
}
}
}
}

View file

@ -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<SyncStatus> = 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
}
}
}

View file

@ -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<List<Block>, 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<ViewState>(),
PickerListener,
SupportNavigation<EventWrapper<AppNavigation.Command>>,
@ -341,9 +342,6 @@ class EditorViewModel(
val actions = MutableStateFlow(ActionItemType.defaultSorting)
val isSyncStatusVisible = MutableStateFlow(true)
val syncStatus = MutableStateFlow<SyncStatusView?>(null)
val icon = MutableStateFlow<ProfileIconView>(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>(SpaceSyncAndP2PStatusState.Initial)
val syncStatusWidget = MutableStateFlow<SyncStatusWidgetState>(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

View file

@ -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<List<Block>, 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
}
}

View file

@ -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<Payload>,
private val delegator: Delegator<Action>,
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<EventWrapper<AppNavigation.Command>>,
ViewerDelegate by viewerDelegate,
AnalyticSpaceHelperDelegate by analyticSpaceHelperDelegate
@ -191,7 +189,6 @@ class ObjectSetViewModel(
private val isOwnerOrEditor get() = permission.value?.isOwnerOrEditor() == true
val status = MutableStateFlow<SyncStatusView?>(null)
val error = MutableStateFlow<String?>(null)
val featured = MutableStateFlow<BlockView.FeaturedRelation?>(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>(SpaceSyncAndP2PStatusState.Initial)
val syncStatusWidget = MutableStateFlow<SyncStatusWidgetState>(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"

View file

@ -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<Payload>,
private val delegator: Delegator<Action>,
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 <T : ViewModel> create(modelClass: Class<T>): 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
}
}

View file

@ -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<SpaceSyncAndP2PStatusState>
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<SpaceSyncAndP2PStatusState> {
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<SpaceSyncAndP2PStatusState> {
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()
}

View file

@ -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
}
}
}
}

View file

@ -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()

View file

@ -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
)
}

View file

@ -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()
}
}

View file

@ -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()
}
}