mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-07 21:37:02 +09:00
DROID-3557 Migration | Enhancement | Add process progress to migration screen (#2279)
This commit is contained in:
parent
4a3a4947c5
commit
338706ff52
15 changed files with 430 additions and 32 deletions
|
@ -5,6 +5,8 @@ import com.anytypeio.anytype.CrashReporter
|
|||
import com.anytypeio.anytype.analytics.base.Analytics
|
||||
import com.anytypeio.anytype.core_utils.di.scope.PerScreen
|
||||
import com.anytypeio.anytype.core_utils.tools.FeatureToggles
|
||||
import com.anytypeio.anytype.data.auth.event.EventProcessMigrationDateChannel
|
||||
import com.anytypeio.anytype.data.auth.event.EventProcessMigrationRemoteChannel
|
||||
import com.anytypeio.anytype.di.common.ComponentDependencies
|
||||
import com.anytypeio.anytype.domain.account.AwaitAccountStartManager
|
||||
import com.anytypeio.anytype.domain.auth.interactor.CheckAuthorizationStatus
|
||||
|
@ -31,7 +33,10 @@ import com.anytypeio.anytype.domain.search.RelationsSubscriptionManager
|
|||
import com.anytypeio.anytype.domain.spaces.SpaceDeletedStatusWatcher
|
||||
import com.anytypeio.anytype.domain.subscriptions.GlobalSubscriptionManager
|
||||
import com.anytypeio.anytype.domain.templates.GetTemplates
|
||||
import com.anytypeio.anytype.domain.workspace.EventProcessMigrationChannel
|
||||
import com.anytypeio.anytype.domain.workspace.SpaceManager
|
||||
import com.anytypeio.anytype.middleware.EventProxy
|
||||
import com.anytypeio.anytype.middleware.interactor.EventProcessMigrationMiddlewareChannel
|
||||
import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate
|
||||
import com.anytypeio.anytype.presentation.auth.account.MigrationHelperDelegate
|
||||
import com.anytypeio.anytype.presentation.splash.SplashViewModelFactory
|
||||
|
@ -185,6 +190,18 @@ object SplashModule {
|
|||
fun bindMigrationHelperDelegate(
|
||||
impl: MigrationHelperDelegate.Impl
|
||||
): MigrationHelperDelegate
|
||||
|
||||
@Binds
|
||||
@PerScreen
|
||||
fun bindMigrationChannel(
|
||||
impl: EventProcessMigrationDateChannel
|
||||
): EventProcessMigrationChannel
|
||||
|
||||
@Binds
|
||||
@PerScreen
|
||||
fun bindMigrationRemoteChannel(
|
||||
impl: EventProcessMigrationMiddlewareChannel
|
||||
): EventProcessMigrationRemoteChannel
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -211,4 +228,5 @@ interface SplashDependencies : ComponentDependencies {
|
|||
fun globalSubscriptionManager(): GlobalSubscriptionManager
|
||||
fun logger(): Logger
|
||||
fun spaceViewSubscriptionContainer(): SpaceViewSubscriptionContainer
|
||||
fun eventProxy(): EventProxy
|
||||
}
|
|
@ -5,6 +5,8 @@ import androidx.lifecycle.ViewModelProvider
|
|||
import com.anytypeio.anytype.CrashReporter
|
||||
import com.anytypeio.anytype.analytics.base.Analytics
|
||||
import com.anytypeio.anytype.core_utils.di.scope.PerScreen
|
||||
import com.anytypeio.anytype.data.auth.event.EventProcessMigrationDateChannel
|
||||
import com.anytypeio.anytype.data.auth.event.EventProcessMigrationRemoteChannel
|
||||
import com.anytypeio.anytype.di.common.ComponentDependencies
|
||||
import com.anytypeio.anytype.domain.account.AwaitAccountStartManager
|
||||
import com.anytypeio.anytype.domain.auth.repo.AuthRepository
|
||||
|
@ -21,7 +23,10 @@ import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider
|
|||
import com.anytypeio.anytype.domain.platform.InitialParamsProvider
|
||||
import com.anytypeio.anytype.domain.spaces.SpaceDeletedStatusWatcher
|
||||
import com.anytypeio.anytype.domain.subscriptions.GlobalSubscriptionManager
|
||||
import com.anytypeio.anytype.domain.workspace.EventProcessMigrationChannel
|
||||
import com.anytypeio.anytype.domain.workspace.SpaceManager
|
||||
import com.anytypeio.anytype.middleware.EventProxy
|
||||
import com.anytypeio.anytype.middleware.interactor.EventProcessMigrationMiddlewareChannel
|
||||
import com.anytypeio.anytype.presentation.auth.account.MigrationHelperDelegate
|
||||
import com.anytypeio.anytype.presentation.onboarding.login.OnboardingMnemonicLoginViewModel
|
||||
import com.anytypeio.anytype.presentation.util.downloader.UriFileProvider
|
||||
|
@ -81,6 +86,18 @@ object OnboardingMnemonicLoginModule {
|
|||
impl: MigrationHelperDelegate.Impl
|
||||
): MigrationHelperDelegate
|
||||
|
||||
@Binds
|
||||
@PerScreen
|
||||
fun bindMigrationChannel(
|
||||
impl: EventProcessMigrationDateChannel
|
||||
): EventProcessMigrationChannel
|
||||
|
||||
@Binds
|
||||
@PerScreen
|
||||
fun bindMigrationRemoteChannel(
|
||||
impl: EventProcessMigrationMiddlewareChannel
|
||||
): EventProcessMigrationRemoteChannel
|
||||
|
||||
@Binds
|
||||
@PerScreen
|
||||
fun bindViewModelFactory(
|
||||
|
@ -108,4 +125,5 @@ interface OnboardingMnemonicLoginDependencies : ComponentDependencies {
|
|||
fun globalSubscriptionManager(): GlobalSubscriptionManager
|
||||
fun debugAccountSelectTrace(): DebugAccountSelectTrace
|
||||
fun logger(): Logger
|
||||
fun eventProxy(): EventProxy
|
||||
}
|
|
@ -228,7 +228,7 @@ fun RecoveryScreen(
|
|||
)
|
||||
}
|
||||
is SetupState.Migration.InProgress -> {
|
||||
MigrationInProgressScreen()
|
||||
MigrationInProgressScreen(progress = state.progress.progress)
|
||||
}
|
||||
is SetupState.Migration.AwaitingStart -> {
|
||||
MigrationStartScreen(
|
||||
|
|
|
@ -90,7 +90,9 @@ class SplashFragment : BaseFragment<FragmentSplashBinding>(R.layout.fragment_spl
|
|||
)
|
||||
}
|
||||
is SplashViewModel.State.Migration.InProgress -> {
|
||||
MigrationInProgressScreen()
|
||||
MigrationInProgressScreen(
|
||||
progress = state.progress
|
||||
)
|
||||
}
|
||||
is SplashViewModel.State.Migration.Failed -> {
|
||||
MigrationFailedScreen(
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.anytypeio.anytype.ui.update
|
||||
|
||||
import androidx.annotation.FloatRange
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
|
@ -245,7 +246,9 @@ fun MigrationStartScreenPreview() {
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun MigrationInProgressScreen() {
|
||||
fun MigrationInProgressScreen(
|
||||
@FloatRange(from = 0.0, to = 1.0) progress: Float
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
@ -260,6 +263,7 @@ fun MigrationInProgressScreen() {
|
|||
.fillMaxWidth()
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
progress = { progress },
|
||||
modifier = Modifier
|
||||
.size(88.dp)
|
||||
.align(Alignment.Center),
|
||||
|
@ -370,7 +374,9 @@ fun MigrationFailedScreen(
|
|||
@DefaultPreviews
|
||||
@Composable
|
||||
fun MigrationInProgressScreenPreview() {
|
||||
MigrationInProgressScreen()
|
||||
MigrationInProgressScreen(
|
||||
progress = 0.2f
|
||||
)
|
||||
}
|
||||
|
||||
@DefaultPreviews
|
||||
|
|
|
@ -32,6 +32,12 @@ data class Process(
|
|||
|
||||
sealed class Event {
|
||||
|
||||
sealed class Migration : Event() {
|
||||
data class New(val process: Process) : Migration()
|
||||
data class Update(val process: Process) : Migration()
|
||||
data class Done(val process: Process) : Migration()
|
||||
}
|
||||
|
||||
sealed class DropFiles : Event() {
|
||||
data class New(
|
||||
val process: Process
|
||||
|
|
|
@ -9,6 +9,8 @@ dependencies {
|
|||
implementation libs.kotlin
|
||||
implementation libs.coroutines
|
||||
|
||||
compileOnly libs.javaxInject
|
||||
|
||||
testImplementation project(":test:utils")
|
||||
testImplementation project(":test:core-models-stub")
|
||||
testImplementation libs.junit
|
||||
|
|
|
@ -3,6 +3,8 @@ package com.anytypeio.anytype.data.auth.event
|
|||
import com.anytypeio.anytype.core_models.Process
|
||||
import com.anytypeio.anytype.domain.workspace.EventProcessDropFilesChannel
|
||||
import com.anytypeio.anytype.domain.workspace.EventProcessImportChannel
|
||||
import com.anytypeio.anytype.domain.workspace.EventProcessMigrationChannel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface EventProcessImportRemoteChannel {
|
||||
|
@ -29,4 +31,17 @@ class EventProcessDropFilesDateChannel(
|
|||
override fun observe(): Flow<List<Process.Event.DropFiles>> {
|
||||
return channel.observe()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface EventProcessMigrationRemoteChannel {
|
||||
fun observe(): Flow<List<Process.Event.Migration>>
|
||||
}
|
||||
|
||||
class EventProcessMigrationDateChannel @Inject constructor(
|
||||
private val channel: EventProcessMigrationRemoteChannel
|
||||
) : EventProcessMigrationChannel {
|
||||
|
||||
override fun observe(): Flow<List<Process.Event.Migration>> {
|
||||
return channel.observe()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,4 +37,5 @@ class MigrateAccount @Inject constructor(
|
|||
data object Current : Params()
|
||||
data class Other(val acc: Id) : Params()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,4 +9,8 @@ interface EventProcessImportChannel {
|
|||
|
||||
interface EventProcessDropFilesChannel {
|
||||
fun observe(): Flow<List<Process.Event.DropFiles>>
|
||||
}
|
||||
|
||||
interface EventProcessMigrationChannel {
|
||||
fun observe(): Flow<List<Process.Event.Migration>>
|
||||
}
|
|
@ -3,8 +3,10 @@ package com.anytypeio.anytype.middleware.interactor
|
|||
import com.anytypeio.anytype.core_models.Process
|
||||
import com.anytypeio.anytype.data.auth.event.EventProcessDropFilesRemoteChannel
|
||||
import com.anytypeio.anytype.data.auth.event.EventProcessImportRemoteChannel
|
||||
import com.anytypeio.anytype.data.auth.event.EventProcessMigrationRemoteChannel
|
||||
import com.anytypeio.anytype.middleware.EventProxy
|
||||
import com.anytypeio.anytype.middleware.mappers.toCoreModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
|
||||
|
@ -113,6 +115,59 @@ class EventProcessImportMiddlewareChannel(
|
|||
}
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EventProcessMigrationMiddlewareChannel @Inject constructor(
|
||||
private val events: EventProxy
|
||||
) : EventProcessMigrationRemoteChannel {
|
||||
|
||||
override fun observe(): Flow<List<Process.Event.Migration>> {
|
||||
return events.flow()
|
||||
.mapNotNull { emission ->
|
||||
emission.messages.mapNotNull { message ->
|
||||
val eventProcessNew = message.processNew
|
||||
val eventProcessUpdate = message.processUpdate
|
||||
val eventProcessDone = message.processDone
|
||||
|
||||
when {
|
||||
eventProcessNew != null -> {
|
||||
val process = eventProcessNew.process
|
||||
val processType = process?.migration
|
||||
if (processType != null) {
|
||||
Process.Event.Migration.New(
|
||||
process = process.toCoreModel()
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
eventProcessUpdate != null -> {
|
||||
val process = eventProcessUpdate.process
|
||||
val processType = process?.migration
|
||||
if (processType != null) {
|
||||
Process.Event.Migration.Update(
|
||||
process = process.toCoreModel()
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
eventProcessDone != null -> {
|
||||
val process = eventProcessDone.process
|
||||
val processType = process?.migration
|
||||
if (processType != null) {
|
||||
Process.Event.Migration.Done(
|
||||
process = process.toCoreModel()
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,22 @@
|
|||
package com.anytypeio.anytype.presentation.auth.account
|
||||
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Process
|
||||
import com.anytypeio.anytype.core_models.Process.Event
|
||||
import com.anytypeio.anytype.core_models.exceptions.MigrationFailedException
|
||||
import com.anytypeio.anytype.domain.auth.interactor.MigrateAccount
|
||||
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
|
||||
import com.anytypeio.anytype.domain.base.Resultat
|
||||
import com.anytypeio.anytype.domain.workspace.EventProcessMigrationChannel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.scan
|
||||
|
||||
interface MigrationHelperDelegate {
|
||||
|
||||
|
@ -15,26 +24,36 @@ interface MigrationHelperDelegate {
|
|||
|
||||
class Impl @Inject constructor(
|
||||
private val migrateAccount: MigrateAccount,
|
||||
private val dispatchers: AppCoroutineDispatchers
|
||||
private val dispatchers: AppCoroutineDispatchers,
|
||||
private val processProgressObserver: MigrationProgressObserver
|
||||
) : MigrationHelperDelegate {
|
||||
|
||||
override suspend fun proceedWithMigration(): Flow<State> {
|
||||
return migrateAccount
|
||||
.stream(MigrateAccount.Params.Current)
|
||||
.map { result ->
|
||||
when(result) {
|
||||
is Resultat.Failure -> {
|
||||
val exception = result.exception
|
||||
if (exception is MigrationFailedException.NotEnoughSpace) {
|
||||
State.Failed.NotEnoughSpace(
|
||||
requiredSpaceInMegabytes = (exception.requiredSpaceInBytes / 1_048_576)
|
||||
)
|
||||
} else {
|
||||
State.Failed.UnknownError(result.exception)
|
||||
.flatMapLatest { result ->
|
||||
flow {
|
||||
when(result) {
|
||||
is Resultat.Failure -> {
|
||||
val exception = result.exception
|
||||
if (exception is MigrationFailedException.NotEnoughSpace) {
|
||||
emit(
|
||||
State.Failed.NotEnoughSpace(
|
||||
requiredSpaceInMegabytes = (exception.requiredSpaceInBytes / 1_048_576)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
emit(State.Failed.UnknownError(result.exception))
|
||||
}
|
||||
}
|
||||
is Resultat.Loading -> {
|
||||
emitAll(processProgressObserver.state)
|
||||
}
|
||||
is Resultat.Success -> {
|
||||
emit(State.Migrated)
|
||||
}
|
||||
}
|
||||
is Resultat.Loading -> State.InProgress
|
||||
is Resultat.Success -> State.Migrated
|
||||
|
||||
}
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
|
@ -43,11 +62,79 @@ interface MigrationHelperDelegate {
|
|||
|
||||
sealed class State {
|
||||
data object Init: State()
|
||||
data object InProgress : State()
|
||||
sealed class InProgress : State() {
|
||||
abstract val progress: Float
|
||||
data object Idle : InProgress() {
|
||||
override val progress: Float = 0f
|
||||
}
|
||||
data class Progress(val processId: Id, override val progress: Float) : InProgress()
|
||||
}
|
||||
sealed class Failed : State() {
|
||||
data class UnknownError(val error: Throwable) : Failed()
|
||||
data class NotEnoughSpace(val requiredSpaceInMegabytes: Long) : Failed()
|
||||
}
|
||||
data object Migrated : State()
|
||||
}
|
||||
}
|
||||
|
||||
typealias MigrationEvents = List<Event.Migration>
|
||||
|
||||
class MigrationProgressObserver @Inject constructor(
|
||||
private val channel: EventProcessMigrationChannel
|
||||
) {
|
||||
val state : Flow<MigrationHelperDelegate.State> get() = channel
|
||||
.observe()
|
||||
.scan<MigrationEvents, MigrationHelperDelegate.State>(
|
||||
initial = MigrationHelperDelegate.State.InProgress.Idle
|
||||
) { state, events ->
|
||||
var result = state
|
||||
events.forEach { event ->
|
||||
when (event) {
|
||||
is Event.Migration.New -> {
|
||||
if (result is MigrationHelperDelegate.State.InProgress.Idle && event.process.state == Process.State.RUNNING) {
|
||||
result = MigrationHelperDelegate.State.InProgress.Progress(
|
||||
processId = event.process.id,
|
||||
progress = 0f
|
||||
)
|
||||
} else {
|
||||
// Some process is already running
|
||||
}
|
||||
}
|
||||
is Event.Migration.Update -> {
|
||||
val currentProgressState = result
|
||||
val newProcess = event.process
|
||||
if (currentProgressState is MigrationHelperDelegate.State.InProgress.Progress
|
||||
&& currentProgressState.processId == event.process.id
|
||||
&& newProcess.state == Process.State.RUNNING
|
||||
) {
|
||||
val progress = newProcess.progress
|
||||
val total = progress?.total
|
||||
val done = progress?.done
|
||||
result =
|
||||
if (total != null && total != 0L && done != null) {
|
||||
currentProgressState.copy(
|
||||
progress = (done.toFloat() / total).coerceIn(0f, 1f)
|
||||
)
|
||||
} else {
|
||||
currentProgressState.copy(progress = 0f)
|
||||
}
|
||||
}
|
||||
}
|
||||
is Event.Migration.Done -> {
|
||||
val currentProgressState = result
|
||||
if (currentProgressState is MigrationHelperDelegate.State.InProgress.Progress
|
||||
&& event.process.state == Process.State.DONE
|
||||
&& event.process.id == currentProgressState.processId
|
||||
) {
|
||||
result = MigrationHelperDelegate.State.Migrated
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.catch {
|
||||
emit(MigrationHelperDelegate.State.Failed.UnknownError(it))
|
||||
}
|
||||
}
|
|
@ -318,15 +318,16 @@ class OnboardingMnemonicLoginViewModel @Inject constructor(
|
|||
account = id
|
||||
)
|
||||
}
|
||||
MigrationHelperDelegate.State.InProgress -> {
|
||||
is MigrationHelperDelegate.State.InProgress -> {
|
||||
state.value = SetupState.Migration.InProgress(
|
||||
account = id
|
||||
account = id,
|
||||
progress = migrationState
|
||||
)
|
||||
}
|
||||
MigrationHelperDelegate.State.Migrated -> {
|
||||
is MigrationHelperDelegate.State.Migrated -> {
|
||||
proceedWithSelectingAccount(id)
|
||||
}
|
||||
MigrationHelperDelegate.State.Init -> {
|
||||
is MigrationHelperDelegate.State.Init -> {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
|
@ -435,7 +436,10 @@ class OnboardingMnemonicLoginViewModel @Inject constructor(
|
|||
sealed class Migration : SetupState() {
|
||||
abstract val account: Id
|
||||
data class AwaitingStart(override val account: Id) : Migration()
|
||||
data class InProgress(override val account: Id): Migration()
|
||||
data class InProgress(
|
||||
override val account: Id,
|
||||
val progress: MigrationHelperDelegate.State.InProgress
|
||||
): Migration()
|
||||
data class Failed(
|
||||
val state: MigrationHelperDelegate.State.Failed,
|
||||
override val account: Id
|
||||
|
|
|
@ -17,11 +17,9 @@ import com.anytypeio.anytype.core_models.ObjectType
|
|||
import com.anytypeio.anytype.core_models.ObjectTypeIds.COLLECTION
|
||||
import com.anytypeio.anytype.core_models.SupportedLayouts
|
||||
import com.anytypeio.anytype.core_models.exceptions.AccountMigrationNeededException
|
||||
import com.anytypeio.anytype.core_models.exceptions.MigrationFailedException
|
||||
import com.anytypeio.anytype.core_models.exceptions.NeedToUpdateApplicationException
|
||||
import com.anytypeio.anytype.core_models.primitives.SpaceId
|
||||
import com.anytypeio.anytype.core_models.primitives.TypeKey
|
||||
import com.anytypeio.anytype.core_utils.ui.ViewState
|
||||
import com.anytypeio.anytype.domain.auth.interactor.CheckAuthorizationStatus
|
||||
import com.anytypeio.anytype.domain.auth.interactor.GetLastOpenedObject
|
||||
import com.anytypeio.anytype.domain.auth.interactor.LaunchAccount
|
||||
|
@ -35,13 +33,11 @@ import com.anytypeio.anytype.domain.page.CreateObjectByTypeAndTemplate
|
|||
import com.anytypeio.anytype.domain.spaces.GetLastOpenedSpace
|
||||
import com.anytypeio.anytype.domain.subscriptions.GlobalSubscriptionManager
|
||||
import com.anytypeio.anytype.domain.workspace.SpaceManager
|
||||
import com.anytypeio.anytype.presentation.BuildConfig
|
||||
import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate
|
||||
import com.anytypeio.anytype.presentation.auth.account.MigrationHelperDelegate
|
||||
import com.anytypeio.anytype.presentation.confgs.ChatConfig
|
||||
import com.anytypeio.anytype.presentation.extension.sendAnalyticsObjectCreateEvent
|
||||
import com.anytypeio.anytype.presentation.search.ObjectSearchConstants
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
|
@ -116,7 +112,9 @@ class SplashViewModel(
|
|||
// Do nothing.
|
||||
}
|
||||
is MigrationHelperDelegate.State.InProgress -> {
|
||||
state.value = State.Migration.InProgress
|
||||
state.value = State.Migration.InProgress(
|
||||
progress = migrationState.progress
|
||||
)
|
||||
}
|
||||
is MigrationHelperDelegate.State.Migrated -> {
|
||||
proceedWithLaunchingAccount()
|
||||
|
@ -455,7 +453,7 @@ class SplashViewModel(
|
|||
data class Error(val msg: String): State()
|
||||
sealed class Migration : State() {
|
||||
data object AwaitingStart: Migration()
|
||||
data object InProgress: Migration()
|
||||
data class InProgress(val progress: Float): Migration()
|
||||
data class Failed(val state: MigrationHelperDelegate.State.Failed) : Migration()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
package com.anytypeio.anytype.presentation
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.anytypeio.anytype.core_models.Process.Event
|
||||
import com.anytypeio.anytype.domain.workspace.EventProcessMigrationChannel
|
||||
import com.anytypeio.anytype.presentation.auth.account.MigrationHelperDelegate
|
||||
import com.anytypeio.anytype.presentation.auth.account.MigrationProgressObserver
|
||||
import com.anytypeio.anytype.presentation.util.DefaultCoroutineTestRule
|
||||
import kotlin.test.assertEquals
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.stub
|
||||
|
||||
class MigrationProgressObserverTest {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@get:Rule
|
||||
val coroutineTestRule = DefaultCoroutineTestRule()
|
||||
|
||||
@Mock
|
||||
lateinit var channel: EventProcessMigrationChannel
|
||||
|
||||
lateinit var observer: MigrationProgressObserver
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.openMocks(this)
|
||||
observer = MigrationProgressObserver(channel)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test() = runTest {
|
||||
|
||||
val processId = "test-process"
|
||||
val initEvent = Event.Migration.New(
|
||||
process = com.anytypeio.anytype.core_models.Process(
|
||||
id = processId,
|
||||
spaceId = "space-id",
|
||||
type = com.anytypeio.anytype.core_models.Process.Type.MIGRATION,
|
||||
state = com.anytypeio.anytype.core_models.Process.State.RUNNING,
|
||||
progress = null
|
||||
)
|
||||
)
|
||||
|
||||
val updateEvent = Event.Migration.Update(
|
||||
process = com.anytypeio.anytype.core_models.Process(
|
||||
id = processId,
|
||||
spaceId = "space-id",
|
||||
type = com.anytypeio.anytype.core_models.Process.Type.MIGRATION,
|
||||
state = com.anytypeio.anytype.core_models.Process.State.RUNNING,
|
||||
progress = com.anytypeio.anytype.core_models.Process.Progress(done = 50, total = 100, message = "")
|
||||
)
|
||||
)
|
||||
|
||||
val eventsFlow = flow {
|
||||
emit(listOf(initEvent)) // Emit the initial event
|
||||
emit(listOf(updateEvent)) // Emit the update event
|
||||
}
|
||||
|
||||
channel.stub {
|
||||
on { observe() } doReturn eventsFlow
|
||||
}
|
||||
|
||||
observer.state.test {
|
||||
// Test initial state
|
||||
val firstState = awaitItem() // Should receive initial event's state
|
||||
|
||||
assertEquals(
|
||||
expected = MigrationHelperDelegate.State.InProgress.Idle,
|
||||
actual = firstState
|
||||
)
|
||||
|
||||
val secondState = awaitItem()
|
||||
|
||||
assertEquals(
|
||||
expected = MigrationHelperDelegate.State.InProgress.Progress(
|
||||
processId = processId,
|
||||
progress = 0f
|
||||
),
|
||||
actual = secondState
|
||||
)
|
||||
|
||||
// Test the updated state
|
||||
val updatedState = awaitItem() // Should receive the update event's state
|
||||
assert(updatedState is MigrationHelperDelegate.State.InProgress.Progress)
|
||||
assert((updatedState as MigrationHelperDelegate.State.InProgress.Progress).progress == 0.5f)
|
||||
|
||||
awaitComplete() // Ensure the flow completes properly
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIdleState() = runTest {
|
||||
|
||||
val eventsFlow = flow<List<Event.Migration>> {
|
||||
emit(listOf()) // Emit the initial event with no progress
|
||||
}
|
||||
|
||||
channel.stub {
|
||||
on { observe() } doReturn eventsFlow
|
||||
}
|
||||
|
||||
observer.state.test {
|
||||
// Test initial state
|
||||
val firstState = awaitItem() // Should receive initial event's state
|
||||
assertEquals(
|
||||
expected = MigrationHelperDelegate.State.InProgress.Idle,
|
||||
actual = firstState
|
||||
)
|
||||
|
||||
awaitComplete() // Ensure the flow completes properly
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCompletedProgressState() = runTest {
|
||||
val processId = "test-process"
|
||||
val initEvent = Event.Migration.New(
|
||||
process = com.anytypeio.anytype.core_models.Process(
|
||||
id = processId,
|
||||
spaceId = "space-id",
|
||||
type = com.anytypeio.anytype.core_models.Process.Type.MIGRATION,
|
||||
state = com.anytypeio.anytype.core_models.Process.State.RUNNING,
|
||||
progress = null
|
||||
)
|
||||
)
|
||||
|
||||
val completedEvent = Event.Migration.Done(
|
||||
process = com.anytypeio.anytype.core_models.Process(
|
||||
id = processId,
|
||||
spaceId = "space-id",
|
||||
type = com.anytypeio.anytype.core_models.Process.Type.MIGRATION,
|
||||
state = com.anytypeio.anytype.core_models.Process.State.DONE,
|
||||
progress = com.anytypeio.anytype.core_models.Process.Progress(done = 100, total = 100, message = "Migration complete")
|
||||
)
|
||||
)
|
||||
|
||||
val eventsFlow = flow {
|
||||
emit(listOf(initEvent)) // Emit the initial event
|
||||
delay(100) // Simulate some delay
|
||||
emit(listOf(completedEvent)) // Emit the completed event
|
||||
}
|
||||
|
||||
channel.stub {
|
||||
on { observe() } doReturn eventsFlow
|
||||
}
|
||||
|
||||
observer.state.test {
|
||||
// Test initial state
|
||||
val firstState = awaitItem() // Should receive initial event's state
|
||||
assertEquals(
|
||||
expected = MigrationHelperDelegate.State.InProgress.Idle,
|
||||
actual = firstState
|
||||
)
|
||||
|
||||
val secondState = awaitItem()
|
||||
|
||||
assertEquals(
|
||||
expected = MigrationHelperDelegate.State.InProgress.Progress(
|
||||
processId = processId,
|
||||
progress = 0f
|
||||
),
|
||||
actual = secondState
|
||||
)
|
||||
|
||||
val completedState = awaitItem()
|
||||
assertTrue(completedState is MigrationHelperDelegate.State.Migrated)
|
||||
|
||||
awaitComplete()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue