mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
DROID-1248 App | Enhancement | Storage limits (#3199)
* DROID-1248 mw * DROID-1246 Settings | Enhancement | Space usage and file limits (#3177) * DROID-1246 space usage command * DROID-1246 space usage response * DROID-1246 space usage useCase * DROID-1246 space storage * DROID-1246 use case di * DROID-1246 filelimit core model * DROID-1246 build provider * DROID-1246 subscribeToSpace * DROID-1246 file limits events * DROID-1246 screen state * DROID-1246 rename * DROID-1246 pr fix * DROID-1246 jobs added * DROID-1246 name fix * DROID-1247 App | Enhancement | Sync status filter (#3179) * DROID-1247 fileSyncStatus * DROID-1247 sync status relation * DROID-1247 search file objects by sync status * DROID-1248 Editor | Enhancement | Storage limits toast (#3181) * DROID-1248 file storage, unsubscribe * DROID-1248 show toast on file storage limit * DROID-1248 tests * DROID-1248 code style * DROID-1248 fix * DROID-1248 use case naming * DROID-1248 rename const * DROID-1248 jobs cancel * DROID-1248 rename * DROID-1248 tests * DROID-1248 mw version * DROID-1248 revert version
This commit is contained in:
parent
9cd1008c0a
commit
16f6666ad9
37 changed files with 642 additions and 221 deletions
|
@ -85,6 +85,8 @@ import com.anytypeio.anytype.domain.templates.ApplyTemplate
|
|||
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.FileLimitsEventChannel
|
||||
import com.anytypeio.anytype.domain.workspace.InterceptFileLimitEvents
|
||||
import com.anytypeio.anytype.domain.workspace.WorkspaceManager
|
||||
import com.anytypeio.anytype.presentation.common.Delegator
|
||||
import com.anytypeio.anytype.presentation.editor.DocumentExternalEventReducer
|
||||
|
@ -263,6 +265,11 @@ open class EditorTestSetup {
|
|||
@Mock
|
||||
lateinit var objectToCollection: ConvertObjectToCollection
|
||||
|
||||
@Mock
|
||||
lateinit var fileLimitsEventChannel: FileLimitsEventChannel
|
||||
|
||||
lateinit var interceptFileLimitEvents: InterceptFileLimitEvents
|
||||
|
||||
val root: String = "rootId123"
|
||||
val workspaceId = MockDataFactory.randomString()
|
||||
|
||||
|
@ -367,6 +374,8 @@ open class EditorTestSetup {
|
|||
workspaceManager.setCurrentWorkspace(workspaceId)
|
||||
}
|
||||
|
||||
interceptFileLimitEvents = InterceptFileLimitEvents(fileLimitsEventChannel, dispatchers)
|
||||
|
||||
TestEditorFragment.testViewModelFactory = EditorViewModelFactory(
|
||||
openPage = openPage,
|
||||
closeObject = closePage,
|
||||
|
@ -452,7 +461,8 @@ open class EditorTestSetup {
|
|||
tableDelegate = tableDelegate,
|
||||
workspaceManager = workspaceManager,
|
||||
getObjectTypes = getObjectTypes,
|
||||
objectToCollection = objectToCollection
|
||||
objectToCollection = objectToCollection,
|
||||
interceptFileLimitEvents = interceptFileLimitEvents
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -94,6 +94,8 @@ import com.anytypeio.anytype.domain.templates.ApplyTemplate
|
|||
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.FileLimitsEventChannel
|
||||
import com.anytypeio.anytype.domain.workspace.InterceptFileLimitEvents
|
||||
import com.anytypeio.anytype.domain.workspace.WorkspaceManager
|
||||
import com.anytypeio.anytype.presentation.common.Action
|
||||
import com.anytypeio.anytype.presentation.common.Delegator
|
||||
|
@ -224,6 +226,14 @@ object EditorSessionModule {
|
|||
storage = storage
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@PerScreen
|
||||
fun provideFileLimitEvents(
|
||||
channel: FileLimitsEventChannel,
|
||||
dispatchers: AppCoroutineDispatchers
|
||||
) : InterceptFileLimitEvents = InterceptFileLimitEvents(channel, dispatchers)
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
fun providePageViewModelFactory(
|
||||
|
@ -260,7 +270,8 @@ object EditorSessionModule {
|
|||
tableDelegate: EditorTableDelegate,
|
||||
workspaceManager: WorkspaceManager,
|
||||
getObjectTypes: GetObjectTypes,
|
||||
objectToCollection: ConvertObjectToCollection
|
||||
objectToCollection: ConvertObjectToCollection,
|
||||
interceptFileLimitEvents: InterceptFileLimitEvents
|
||||
): EditorViewModelFactory = EditorViewModelFactory(
|
||||
openPage = openPage,
|
||||
closeObject = closePage,
|
||||
|
@ -295,7 +306,8 @@ object EditorSessionModule {
|
|||
tableDelegate = tableDelegate,
|
||||
workspaceManager = workspaceManager,
|
||||
getObjectTypes = getObjectTypes,
|
||||
objectToCollection = objectToCollection
|
||||
objectToCollection = objectToCollection,
|
||||
interceptFileLimitEvents = interceptFileLimitEvents
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.anytypeio.anytype.di.feature.settings
|
|||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.anytypeio.anytype.analytics.base.Analytics
|
||||
import com.anytypeio.anytype.core_utils.di.scope.PerScreen
|
||||
import com.anytypeio.anytype.device.BuildProvider
|
||||
import com.anytypeio.anytype.di.common.ComponentDependencies
|
||||
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
|
@ -11,6 +12,9 @@ import com.anytypeio.anytype.domain.device.ClearFileCache
|
|||
import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer
|
||||
import com.anytypeio.anytype.domain.misc.UrlBuilder
|
||||
import com.anytypeio.anytype.domain.search.SubscriptionEventChannel
|
||||
import com.anytypeio.anytype.domain.workspace.FileLimitsEventChannel
|
||||
import com.anytypeio.anytype.domain.workspace.FileSpaceUsage
|
||||
import com.anytypeio.anytype.domain.workspace.InterceptFileLimitEvents
|
||||
import com.anytypeio.anytype.presentation.settings.FilesStorageViewModel
|
||||
import com.anytypeio.anytype.presentation.spaces.SpaceGradientProvider
|
||||
import com.anytypeio.anytype.ui.settings.FilesStorageFragment
|
||||
|
@ -65,6 +69,22 @@ object FilesStorageModule {
|
|||
@PerScreen
|
||||
fun clearFileCache(repo: BlockRepository): ClearFileCache = ClearFileCache(repo)
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@PerScreen
|
||||
fun provideSpaceUsage(
|
||||
repo: BlockRepository,
|
||||
dispatchers: AppCoroutineDispatchers
|
||||
): FileSpaceUsage = FileSpaceUsage(repo, dispatchers)
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@PerScreen
|
||||
fun provideFileLimitEvents(
|
||||
channel: FileLimitsEventChannel,
|
||||
dispatchers: AppCoroutineDispatchers
|
||||
) : InterceptFileLimitEvents = InterceptFileLimitEvents(channel, dispatchers)
|
||||
|
||||
@Module
|
||||
interface Declarations {
|
||||
|
||||
|
@ -81,4 +101,6 @@ interface FilesStorageDependencies : ComponentDependencies {
|
|||
fun analytics(): Analytics
|
||||
fun configStorage(): ConfigStorage
|
||||
fun channel(): SubscriptionEventChannel
|
||||
fun fileEventsChannel(): FileLimitsEventChannel
|
||||
fun buildProvider(): BuildProvider
|
||||
}
|
|
@ -22,6 +22,8 @@ import com.anytypeio.anytype.data.auth.repo.block.BlockRemoteDataStore
|
|||
import com.anytypeio.anytype.data.auth.repo.unsplash.UnsplashDataRepository
|
||||
import com.anytypeio.anytype.data.auth.repo.unsplash.UnsplashRemote
|
||||
import com.anytypeio.anytype.data.auth.types.DefaultObjectTypesProvider
|
||||
import com.anytypeio.anytype.device.BuildProvider
|
||||
import com.anytypeio.anytype.device.DefaultBuildProvider
|
||||
import com.anytypeio.anytype.device.DefaultPathProvider
|
||||
import com.anytypeio.anytype.domain.`object`.ObjectTypesProvider
|
||||
import com.anytypeio.anytype.domain.auth.repo.AuthRepository
|
||||
|
@ -284,6 +286,11 @@ object DataModule {
|
|||
)
|
||||
//endregion
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideBuildProvider(): BuildProvider = DefaultBuildProvider()
|
||||
|
||||
@Module
|
||||
interface Bindings {
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import com.anytypeio.anytype.data.auth.account.AccountStatusDataChannel
|
|||
import com.anytypeio.anytype.data.auth.account.AccountStatusRemoteChannel
|
||||
import com.anytypeio.anytype.data.auth.event.EventDataChannel
|
||||
import com.anytypeio.anytype.data.auth.event.EventRemoteChannel
|
||||
import com.anytypeio.anytype.data.auth.event.FileLimitsDataChannel
|
||||
import com.anytypeio.anytype.data.auth.event.SubscriptionDataChannel
|
||||
import com.anytypeio.anytype.data.auth.event.SubscriptionEventRemoteChannel
|
||||
import com.anytypeio.anytype.data.auth.status.ThreadStatusDataChannel
|
||||
|
@ -12,6 +13,8 @@ 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.data.auth.event.FileLimitsRemoteChannel
|
||||
import com.anytypeio.anytype.domain.workspace.FileLimitsEventChannel
|
||||
import com.anytypeio.anytype.middleware.EventProxy
|
||||
import com.anytypeio.anytype.middleware.interactor.*
|
||||
import dagger.Binds
|
||||
|
@ -110,6 +113,20 @@ object EventModule {
|
|||
proxy: EventProxy
|
||||
): SubscriptionEventRemoteChannel = MiddlewareSubscriptionEventChannel(events = proxy)
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFileLimitsRemoteChannel(
|
||||
proxy: EventProxy
|
||||
): FileLimitsRemoteChannel = FileLimitsMiddlewareChannel(events = proxy)
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFileLimitsDataChannel(
|
||||
channel: FileLimitsRemoteChannel
|
||||
): FileLimitsEventChannel = FileLimitsDataChannel(channel = channel)
|
||||
|
||||
@Module
|
||||
interface Bindings {
|
||||
|
||||
|
|
|
@ -17,7 +17,9 @@ import com.anytypeio.anytype.R
|
|||
import com.anytypeio.anytype.core_ui.common.ComposeDialogView
|
||||
import com.anytypeio.anytype.core_utils.ext.safeNavigate
|
||||
import com.anytypeio.anytype.core_utils.ext.setupBottomSheetBehavior
|
||||
import com.anytypeio.anytype.core_utils.ext.toast
|
||||
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment
|
||||
import com.anytypeio.anytype.core_utils.ui.proceed
|
||||
import com.anytypeio.anytype.di.common.componentManager
|
||||
import com.anytypeio.anytype.presentation.settings.FilesStorageViewModel
|
||||
import com.anytypeio.anytype.presentation.settings.FilesStorageViewModel.Event
|
||||
|
@ -61,6 +63,17 @@ class FilesStorageFragment : BaseBottomSheetComposeFragment() {
|
|||
collectCommands()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
proceed(vm.toasts) { toast(it) }
|
||||
vm.onStart()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
vm.onStop()
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
private fun collectCommands() {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
package com.anytypeio.anytype.core_models
|
||||
|
||||
data class FileLimits(
|
||||
val filesCount: Long?,
|
||||
val cidsCount: Long?,
|
||||
val bytesUsage: Long?,
|
||||
val bytesLeft: Long?,
|
||||
val bytesLimit: Long?,
|
||||
val localBytesUsage: Long?
|
||||
) {
|
||||
|
||||
companion object {
|
||||
fun empty(): FileLimits = FileLimits(
|
||||
filesCount = null,
|
||||
cidsCount = null,
|
||||
bytesUsage = null,
|
||||
bytesLeft = null,
|
||||
bytesLimit = null,
|
||||
localBytesUsage = null
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package com.anytypeio.anytype.core_models
|
||||
|
||||
sealed class FileLimitsEvent {
|
||||
data class SpaceUsage(val bytesUsage: Long) : FileLimitsEvent()
|
||||
data class LocalUsage(val bytesUsage: Long) : FileLimitsEvent()
|
||||
data class FileLimitReached(
|
||||
val spaceId: String,
|
||||
val fileId: String
|
||||
) : FileLimitsEvent()
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package com.anytypeio.anytype.core_models
|
||||
|
||||
enum class FileSyncStatus(val value: Int) {
|
||||
UNKNOWN(0), SYNCED(1), NOT_SYNCED(2)
|
||||
}
|
|
@ -46,6 +46,7 @@ object Relations {
|
|||
const val SOURCE_OBJECT = "sourceObject"
|
||||
const val INTERNAL_FLAGS = "internalFlags"
|
||||
const val SIZE_IN_BYTES = "sizeInBytes"
|
||||
const val FILE_SYNC_STATUS = "fileSyncStatus"
|
||||
|
||||
const val PAGE_COVER = "pageCover"
|
||||
|
||||
|
|
|
@ -395,7 +395,7 @@ fun getDeviceName(): String {
|
|||
}
|
||||
}
|
||||
|
||||
fun bytesToHumanReadableSize(bytes: Double): String = when {
|
||||
fun bytesToHumanReadableSize(bytes: Long): String = when {
|
||||
bytes >= 1 shl 30 -> "%d GB".format((bytes / (1 shl 30)).toInt())
|
||||
bytes >= 1 shl 20 -> "%d MB".format((bytes / (1 shl 20)).toInt())
|
||||
bytes >= 1 shl 10 -> "%d kB".format((bytes / (1 shl 10)).toInt())
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package com.anytypeio.anytype.data.auth.event
|
||||
|
||||
import com.anytypeio.anytype.core_models.FileLimitsEvent
|
||||
import com.anytypeio.anytype.domain.workspace.FileLimitsEventChannel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class FileLimitsDataChannel(
|
||||
private val channel: FileLimitsRemoteChannel
|
||||
) : FileLimitsEventChannel {
|
||||
|
||||
override fun observe(): Flow<List<FileLimitsEvent>> {
|
||||
return channel.observe()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package com.anytypeio.anytype.data.auth.event
|
||||
|
||||
import com.anytypeio.anytype.core_models.FileLimitsEvent
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface FileLimitsRemoteChannel {
|
||||
fun observe(): Flow<List<FileLimitsEvent>>
|
||||
}
|
|
@ -8,6 +8,7 @@ import com.anytypeio.anytype.core_models.DVFilter
|
|||
import com.anytypeio.anytype.core_models.DVSort
|
||||
import com.anytypeio.anytype.core_models.DVViewer
|
||||
import com.anytypeio.anytype.core_models.DVViewerType
|
||||
import com.anytypeio.anytype.core_models.FileLimits
|
||||
import com.anytypeio.anytype.core_models.Hash
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Key
|
||||
|
@ -796,4 +797,8 @@ class BlockDataRepository(
|
|||
override suspend fun setQueryToSet(command: Command.SetQueryToSet): Payload {
|
||||
return remote.setQueryToSet(command)
|
||||
}
|
||||
|
||||
override suspend fun fileSpaceUsage(): FileLimits {
|
||||
return remote.fileSpaceUsage()
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import com.anytypeio.anytype.core_models.DVFilter
|
|||
import com.anytypeio.anytype.core_models.DVSort
|
||||
import com.anytypeio.anytype.core_models.DVViewer
|
||||
import com.anytypeio.anytype.core_models.DVViewerType
|
||||
import com.anytypeio.anytype.core_models.FileLimits
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Key
|
||||
import com.anytypeio.anytype.core_models.ObjectType
|
||||
|
@ -348,4 +349,5 @@ interface BlockDataStore {
|
|||
suspend fun sortDataViewViewRelation(command: Command.SortRelations): Payload
|
||||
suspend fun addObjectToCollection(command: Command.AddObjectToCollection): Payload
|
||||
suspend fun setQueryToSet(command: Command.SetQueryToSet): Payload
|
||||
suspend fun fileSpaceUsage(): FileLimits
|
||||
}
|
|
@ -8,6 +8,7 @@ import com.anytypeio.anytype.core_models.DVFilter
|
|||
import com.anytypeio.anytype.core_models.DVSort
|
||||
import com.anytypeio.anytype.core_models.DVViewer
|
||||
import com.anytypeio.anytype.core_models.DVViewerType
|
||||
import com.anytypeio.anytype.core_models.FileLimits
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Key
|
||||
import com.anytypeio.anytype.core_models.ObjectType
|
||||
|
@ -342,4 +343,5 @@ interface BlockRemote {
|
|||
suspend fun sortDataViewViewRelation(command: Command.SortRelations): Payload
|
||||
suspend fun addObjectToCollection(command: Command.AddObjectToCollection): Payload
|
||||
suspend fun setQueryToSet(command: Command.SetQueryToSet): Payload
|
||||
suspend fun fileSpaceUsage(): FileLimits
|
||||
}
|
|
@ -8,6 +8,7 @@ import com.anytypeio.anytype.core_models.DVFilter
|
|||
import com.anytypeio.anytype.core_models.DVSort
|
||||
import com.anytypeio.anytype.core_models.DVViewer
|
||||
import com.anytypeio.anytype.core_models.DVViewerType
|
||||
import com.anytypeio.anytype.core_models.FileLimits
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Key
|
||||
import com.anytypeio.anytype.core_models.ObjectType
|
||||
|
@ -727,4 +728,8 @@ class BlockRemoteDataStore(private val remote: BlockRemote) : BlockDataStore {
|
|||
override suspend fun setQueryToSet(command: Command.SetQueryToSet): Payload {
|
||||
return remote.setQueryToSet(command)
|
||||
}
|
||||
|
||||
override suspend fun fileSpaceUsage(): FileLimits {
|
||||
return remote.fileSpaceUsage()
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import com.anytypeio.anytype.core_models.DVFilter
|
|||
import com.anytypeio.anytype.core_models.DVSort
|
||||
import com.anytypeio.anytype.core_models.DVViewer
|
||||
import com.anytypeio.anytype.core_models.DVViewerType
|
||||
import com.anytypeio.anytype.core_models.FileLimits
|
||||
import com.anytypeio.anytype.core_models.Hash
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Key
|
||||
|
@ -392,4 +393,5 @@ interface BlockRepository {
|
|||
suspend fun sortDataViewViewRelation(command: Command.SortRelations): Payload
|
||||
suspend fun addObjectToCollection(command: Command.AddObjectToCollection): Payload
|
||||
suspend fun setQueryToSet(command: Command.SetQueryToSet): Payload
|
||||
suspend fun fileSpaceUsage(): FileLimits
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package com.anytypeio.anytype.domain.workspace
|
||||
|
||||
import com.anytypeio.anytype.core_models.FileLimitsEvent
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface FileLimitsEventChannel {
|
||||
fun observe() : Flow<List<FileLimitsEvent>>
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package com.anytypeio.anytype.domain.workspace
|
||||
|
||||
import com.anytypeio.anytype.core_models.FileLimits
|
||||
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
|
||||
import com.anytypeio.anytype.domain.base.ResultInteractor
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
|
||||
class FileSpaceUsage(
|
||||
private val repo: BlockRepository,
|
||||
dispatchers: AppCoroutineDispatchers
|
||||
) : ResultInteractor<Unit, FileLimits>(dispatchers.io) {
|
||||
|
||||
override suspend fun doWork(params: Unit): FileLimits {
|
||||
return repo.fileSpaceUsage()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package com.anytypeio.anytype.domain.workspace
|
||||
|
||||
import com.anytypeio.anytype.core_models.FileLimitsEvent
|
||||
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
|
||||
import com.anytypeio.anytype.domain.base.ResultInteractor
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class InterceptFileLimitEvents(
|
||||
private val fileLimitsEventChannel: FileLimitsEventChannel,
|
||||
dispatchers: AppCoroutineDispatchers
|
||||
) : ResultInteractor<Unit, Flow<List<FileLimitsEvent>>>(dispatchers.io) {
|
||||
|
||||
override suspend fun doWork(params: Unit): Flow<List<FileLimitsEvent>> {
|
||||
return fileLimitsEventChannel.observe()
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ import com.anytypeio.anytype.core_models.DVFilter
|
|||
import com.anytypeio.anytype.core_models.DVSort
|
||||
import com.anytypeio.anytype.core_models.DVViewer
|
||||
import com.anytypeio.anytype.core_models.DVViewerType
|
||||
import com.anytypeio.anytype.core_models.FileLimits
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Key
|
||||
import com.anytypeio.anytype.core_models.ObjectType
|
||||
|
@ -760,4 +761,8 @@ class BlockMiddleware(
|
|||
override suspend fun setQueryToSet(command: Command.SetQueryToSet): Payload {
|
||||
return middleware.setQueryToSet(command)
|
||||
}
|
||||
|
||||
override suspend fun fileSpaceUsage(): FileLimits {
|
||||
return middleware.fileSpaceUsage()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package com.anytypeio.anytype.middleware.interactor
|
||||
|
||||
import com.anytypeio.anytype.core_models.FileLimitsEvent
|
||||
import com.anytypeio.anytype.data.auth.event.FileLimitsRemoteChannel
|
||||
import com.anytypeio.anytype.middleware.EventProxy
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
|
||||
class FileLimitsMiddlewareChannel(
|
||||
private val events: EventProxy
|
||||
) : FileLimitsRemoteChannel {
|
||||
|
||||
override fun observe(): Flow<List<FileLimitsEvent>> =
|
||||
events.flow()
|
||||
.mapNotNull { emission ->
|
||||
emission.messages.mapNotNull { message ->
|
||||
when {
|
||||
message.fileSpaceUsage != null -> {
|
||||
val event = message.fileSpaceUsage
|
||||
checkNotNull(event)
|
||||
FileLimitsEvent.SpaceUsage(
|
||||
bytesUsage = event.bytesUsage
|
||||
)
|
||||
}
|
||||
message.fileLocalUsage != null -> {
|
||||
val event = message.fileLocalUsage
|
||||
checkNotNull(event)
|
||||
FileLimitsEvent.LocalUsage(
|
||||
bytesUsage = event.localBytesUsage
|
||||
)
|
||||
}
|
||||
message.fileLimitReached!= null -> {
|
||||
val event = message.fileLimitReached
|
||||
checkNotNull(event)
|
||||
FileLimitsEvent.FileLimitReached(
|
||||
fileId = event.fileId,
|
||||
spaceId = event.spaceId
|
||||
)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,6 +13,7 @@ import com.anytypeio.anytype.core_models.DVFilter
|
|||
import com.anytypeio.anytype.core_models.DVSort
|
||||
import com.anytypeio.anytype.core_models.DVViewer
|
||||
import com.anytypeio.anytype.core_models.DVViewerType
|
||||
import com.anytypeio.anytype.core_models.FileLimits
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Key
|
||||
import com.anytypeio.anytype.core_models.ObjectType
|
||||
|
@ -2120,6 +2121,15 @@ class Middleware(
|
|||
return response.event.toPayload()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun fileSpaceUsage(): FileLimits {
|
||||
val request = Rpc.File.SpaceUsage.Request()
|
||||
if (BuildConfig.DEBUG) logRequest(request)
|
||||
val response = service.spaceUsage(request)
|
||||
if (BuildConfig.DEBUG) logResponse(response)
|
||||
return response.toCoreModel()
|
||||
}
|
||||
|
||||
private fun logRequest(any: Any) {
|
||||
logger.logRequest(any)
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import com.anytypeio.anytype.core_models.DVViewerCardSize
|
|||
import com.anytypeio.anytype.core_models.DVViewerRelation
|
||||
import com.anytypeio.anytype.core_models.DVViewerType
|
||||
import com.anytypeio.anytype.core_models.Event
|
||||
import com.anytypeio.anytype.core_models.FileLimits
|
||||
import com.anytypeio.anytype.core_models.ObjectOrder
|
||||
import com.anytypeio.anytype.core_models.ObjectType
|
||||
import com.anytypeio.anytype.core_models.ObjectView
|
||||
|
@ -27,6 +28,7 @@ import com.anytypeio.anytype.core_models.Payload
|
|||
import com.anytypeio.anytype.core_models.Relation
|
||||
import com.anytypeio.anytype.core_models.RelationFormat
|
||||
import com.anytypeio.anytype.core_models.RelationLink
|
||||
import com.anytypeio.anytype.core_models.Response
|
||||
import com.anytypeio.anytype.core_models.restrictions.DataViewRestriction
|
||||
import com.anytypeio.anytype.core_models.restrictions.DataViewRestrictions
|
||||
import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction
|
||||
|
@ -716,4 +718,15 @@ fun MDVViewCardSize.toCodeModels(): DVViewerCardSize = when (this) {
|
|||
MDVViewCardSize.Small -> DVViewerCardSize.SMALL
|
||||
MDVViewCardSize.Medium -> DVViewerCardSize.MEDIUM
|
||||
MDVViewCardSize.Large -> DVViewerCardSize.LARGE
|
||||
}
|
||||
|
||||
fun Rpc.File.SpaceUsage.Response.toCoreModel(): FileLimits {
|
||||
return FileLimits(
|
||||
filesCount = usage?.filesCount,
|
||||
cidsCount = usage?.cidsCount,
|
||||
bytesUsage = usage?.bytesUsage,
|
||||
bytesLeft = usage?.bytesLeft,
|
||||
bytesLimit = usage?.bytesLimit,
|
||||
localBytesUsage = usage?.localBytesUsage
|
||||
)
|
||||
}
|
|
@ -172,6 +172,9 @@ interface MiddlewareService {
|
|||
@Throws(Exception::class)
|
||||
fun fileDownload(request: Rpc.File.Download.Request): Rpc.File.Download.Response
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun spaceUsage(request: Rpc.File.SpaceUsage.Request): Rpc.File.SpaceUsage.Response
|
||||
|
||||
//endregion
|
||||
|
||||
//region UNSPLASH commands
|
||||
|
|
|
@ -1584,4 +1584,17 @@ class MiddlewareServiceImplementation @Inject constructor(
|
|||
return response
|
||||
}
|
||||
}
|
||||
|
||||
override fun spaceUsage(request: Rpc.File.SpaceUsage.Request): Rpc.File.SpaceUsage.Response {
|
||||
val encoded = Service.fileSpaceUsage(
|
||||
Rpc.File.SpaceUsage.Request.ADAPTER.encode(request)
|
||||
)
|
||||
val response = Rpc.File.SpaceUsage.Response.ADAPTER.decode(encoded)
|
||||
val error = response.error
|
||||
if (error != null && error.code != Rpc.File.SpaceUsage.Response.Error.Code.NULL) {
|
||||
throw Exception(error.description)
|
||||
} else {
|
||||
return response
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package com.anytypeio.anytype.device
|
||||
|
||||
import android.os.Build
|
||||
|
||||
interface BuildProvider {
|
||||
fun getManufacturer(): String
|
||||
fun getModel(): String
|
||||
}
|
||||
|
||||
class DefaultBuildProvider() : BuildProvider {
|
||||
override fun getManufacturer(): String {
|
||||
return Build.MANUFACTURER
|
||||
}
|
||||
|
||||
override fun getModel(): String {
|
||||
return Build.MODEL
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@ import com.anytypeio.anytype.core_models.DVFilter
|
|||
import com.anytypeio.anytype.core_models.DVFilterCondition
|
||||
import com.anytypeio.anytype.core_models.Document
|
||||
import com.anytypeio.anytype.core_models.Event
|
||||
import com.anytypeio.anytype.core_models.FileLimitsEvent
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.InternalFlags
|
||||
import com.anytypeio.anytype.core_models.Key
|
||||
|
@ -83,6 +84,7 @@ 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.unsplash.DownloadUnsplashImage
|
||||
import com.anytypeio.anytype.domain.workspace.InterceptFileLimitEvents
|
||||
import com.anytypeio.anytype.domain.workspace.WorkspaceManager
|
||||
import com.anytypeio.anytype.presentation.BuildConfig
|
||||
import com.anytypeio.anytype.presentation.common.Action
|
||||
|
@ -274,7 +276,8 @@ class EditorViewModel(
|
|||
private val featureToggles: FeatureToggles,
|
||||
private val tableDelegate: EditorTableDelegate,
|
||||
private val workspaceManager: WorkspaceManager,
|
||||
private val getObjectTypes: GetObjectTypes
|
||||
private val getObjectTypes: GetObjectTypes,
|
||||
private val interceptFileLimitEvents: InterceptFileLimitEvents
|
||||
) : ViewStateViewModel<ViewState>(),
|
||||
PickerListener,
|
||||
SupportNavigation<EventWrapper<AppNavigation.Command>>,
|
||||
|
@ -966,6 +969,9 @@ class EditorViewModel(
|
|||
.filter { it.context == context }
|
||||
.collect { orchestrator.proxies.payloads.send(it) }
|
||||
}
|
||||
|
||||
observeFileLimitsEvents()
|
||||
|
||||
val startTime = System.currentTimeMillis()
|
||||
viewModelScope.launch {
|
||||
openPage.execute(id).fold(
|
||||
|
@ -6830,7 +6836,20 @@ class EditorViewModel(
|
|||
}
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region FILE LIMITS
|
||||
private fun observeFileLimitsEvents() {
|
||||
jobs += viewModelScope.launch {
|
||||
interceptFileLimitEvents
|
||||
.run(Unit)
|
||||
.collect { event ->
|
||||
if (event.any { it is FileLimitsEvent.FileLimitReached }) {
|
||||
_toasts.emit("You exceeded file limit upload")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ 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.unsplash.DownloadUnsplashImage
|
||||
import com.anytypeio.anytype.domain.workspace.InterceptFileLimitEvents
|
||||
import com.anytypeio.anytype.domain.workspace.WorkspaceManager
|
||||
import com.anytypeio.anytype.presentation.common.Action
|
||||
import com.anytypeio.anytype.presentation.common.Delegator
|
||||
|
@ -75,7 +76,8 @@ open class EditorViewModelFactory(
|
|||
private val tableDelegate: EditorTableDelegate,
|
||||
private val workspaceManager: WorkspaceManager,
|
||||
private val getObjectTypes: GetObjectTypes,
|
||||
private val objectToCollection: ConvertObjectToCollection
|
||||
private val objectToCollection: ConvertObjectToCollection,
|
||||
private val interceptFileLimitEvents: InterceptFileLimitEvents
|
||||
) : ViewModelProvider.Factory {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
|
@ -83,15 +85,15 @@ open class EditorViewModelFactory(
|
|||
return EditorViewModel(
|
||||
openPage = openPage,
|
||||
closePage = closeObject,
|
||||
createBlockLinkWithObject = createBlockLinkWithObject,
|
||||
createObjectAsMentionOrLink = createObjectAsMentionOrLink,
|
||||
interceptEvents = interceptEvents,
|
||||
interceptThreadStatus = interceptThreadStatus,
|
||||
updateLinkMarks = updateLinkMarks,
|
||||
removeLinkMark = removeLinkMark,
|
||||
createBlockLinkWithObject = createBlockLinkWithObject,
|
||||
reducer = documentEventReducer,
|
||||
urlBuilder = urlBuilder,
|
||||
renderer = renderer,
|
||||
createObjectAsMentionOrLink = createObjectAsMentionOrLink,
|
||||
orchestrator = orchestrator,
|
||||
analytics = analytics,
|
||||
dispatcher = dispatcher,
|
||||
|
@ -108,13 +110,14 @@ open class EditorViewModelFactory(
|
|||
templateDelegate = editorTemplateDelegate,
|
||||
createObject = createObject,
|
||||
objectToSet = objectToSet,
|
||||
objectToCollection = objectToCollection,
|
||||
storeOfRelations = storeOfRelations,
|
||||
storeOfObjectTypes = storeOfObjectTypes,
|
||||
featureToggles = featureToggles,
|
||||
tableDelegate = tableDelegate,
|
||||
workspaceManager = workspaceManager,
|
||||
getObjectTypes = getObjectTypes,
|
||||
objectToCollection = objectToCollection
|
||||
interceptFileLimitEvents = interceptFileLimitEvents
|
||||
) as T
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ import com.anytypeio.anytype.core_models.DVFilter
|
|||
import com.anytypeio.anytype.core_models.DVFilterCondition
|
||||
import com.anytypeio.anytype.core_models.DVSort
|
||||
import com.anytypeio.anytype.core_models.DVSortType
|
||||
import com.anytypeio.anytype.core_models.FileSyncStatus
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Marketplace.MARKETPLACE_ID
|
||||
import com.anytypeio.anytype.core_models.MarketplaceObjectTypeIds
|
||||
|
@ -785,6 +786,11 @@ object ObjectSearchConstants {
|
|||
relation = Relations.WORKSPACE_ID,
|
||||
condition = DVFilterCondition.EQUAL,
|
||||
value = workspaceId
|
||||
),
|
||||
DVFilter(
|
||||
relation = Relations.FILE_SYNC_STATUS,
|
||||
condition = DVFilterCondition.EQUAL,
|
||||
value = FileSyncStatus.SYNCED.value.toDouble()
|
||||
)
|
||||
)
|
||||
}
|
|
@ -1,16 +1,18 @@
|
|||
package com.anytypeio.anytype.presentation.settings
|
||||
|
||||
import android.os.Build
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.anytypeio.anytype.analytics.base.Analytics
|
||||
import com.anytypeio.anytype.analytics.base.EventsDictionary
|
||||
import com.anytypeio.anytype.analytics.base.sendEvent
|
||||
import com.anytypeio.anytype.core_models.FileLimitsEvent
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Relations
|
||||
import com.anytypeio.anytype.core_utils.ext.bytesToHumanReadableSize
|
||||
import com.anytypeio.anytype.core_utils.ext.cancel
|
||||
import com.anytypeio.anytype.core_utils.ext.throttleFirst
|
||||
import com.anytypeio.anytype.device.BuildProvider
|
||||
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
|
||||
import com.anytypeio.anytype.domain.base.BaseUseCase
|
||||
import com.anytypeio.anytype.domain.base.Interactor
|
||||
|
@ -23,17 +25,21 @@ import com.anytypeio.anytype.presentation.extension.sendSettingsOffloadEvent
|
|||
import com.anytypeio.anytype.presentation.extension.sendSettingsStorageEvent
|
||||
import com.anytypeio.anytype.presentation.extension.sendSettingsStorageManageEvent
|
||||
import com.anytypeio.anytype.presentation.extension.sendSettingsStorageOffloadEvent
|
||||
import com.anytypeio.anytype.domain.workspace.FileSpaceUsage
|
||||
import com.anytypeio.anytype.domain.workspace.InterceptFileLimitEvents
|
||||
import com.anytypeio.anytype.presentation.spaces.SpaceGradientProvider
|
||||
import com.anytypeio.anytype.presentation.spaces.SpaceIconView
|
||||
import com.anytypeio.anytype.presentation.spaces.spaceIcon
|
||||
import com.anytypeio.anytype.presentation.widgets.collection.Subscription
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
@ -45,32 +51,80 @@ class FilesStorageViewModel(
|
|||
private val clearFileCache: ClearFileCache,
|
||||
private val urlBuilder: UrlBuilder,
|
||||
private val spaceGradientProvider: SpaceGradientProvider,
|
||||
private val appCoroutineDispatchers: AppCoroutineDispatchers
|
||||
private val appCoroutineDispatchers: AppCoroutineDispatchers,
|
||||
private val fileSpaceUsage: FileSpaceUsage,
|
||||
private val interceptFileLimitEvents: InterceptFileLimitEvents,
|
||||
private val buildProvider: BuildProvider
|
||||
) : ViewModel() {
|
||||
|
||||
val events = MutableSharedFlow<Event>(replay = 0)
|
||||
val commands = MutableSharedFlow<Command>(replay = 0)
|
||||
private val isClearFileCacheInProgress = MutableStateFlow(false)
|
||||
|
||||
private val _state = MutableStateFlow<FilesStorageScreenState>(FilesStorageScreenState.Idle)
|
||||
val state: StateFlow<FilesStorageScreenState> = _state
|
||||
private val _state = MutableStateFlow(ScreenState.empty())
|
||||
val state: StateFlow<ScreenState> = _state
|
||||
val toasts = MutableSharedFlow<String>(replay = 0)
|
||||
|
||||
private val profileId = configStorage.get().profile
|
||||
private val workspaceId = configStorage.get().workspace
|
||||
private val jobs = mutableListOf<Job>()
|
||||
|
||||
init {
|
||||
subscribeToViewEvents()
|
||||
}
|
||||
|
||||
fun onStart() {
|
||||
subscribeToSpace()
|
||||
subscribeToFileLimitEvents()
|
||||
}
|
||||
|
||||
fun onStop() {
|
||||
viewModelScope.launch {
|
||||
storelessSubscriptionContainer.unsubscribe(listOf(SPACE_STORAGE_SUBSCRIPTION_ID))
|
||||
}
|
||||
jobs.cancel()
|
||||
}
|
||||
|
||||
private fun subscribeToViewEvents() {
|
||||
events
|
||||
.throttleFirst()
|
||||
.onEach { event -> dispatchAnalyticsEvent(event) }
|
||||
.onEach { event -> dispatchCommand(event) }
|
||||
.onEach { event ->
|
||||
dispatchAnalyticsEvent(event)
|
||||
dispatchCommand(event)
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
subscribeToSpace()
|
||||
viewModelScope.launch { analytics.sendSettingsStorageEvent() }
|
||||
}
|
||||
|
||||
private fun subscribeToFileLimitEvents() {
|
||||
jobs += viewModelScope.launch {
|
||||
interceptFileLimitEvents.run(Unit)
|
||||
.onEach { events ->
|
||||
val currentState = _state.value
|
||||
val newState = currentState.updateState(events)
|
||||
_state.value = newState
|
||||
}
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
private fun ScreenState.updateState(events: List<FileLimitsEvent>): ScreenState {
|
||||
var newState = this
|
||||
events.forEach { event ->
|
||||
newState = when (event) {
|
||||
is FileLimitsEvent.LocalUsage -> newState.copy(
|
||||
localUsage = bytesToHumanReadableSize(event.bytesUsage)
|
||||
)
|
||||
is FileLimitsEvent.SpaceUsage -> newState.copy(
|
||||
spaceUsage = bytesToHumanReadableSize(event.bytesUsage)
|
||||
)
|
||||
else -> newState
|
||||
}
|
||||
}
|
||||
return newState
|
||||
}
|
||||
|
||||
fun onClearFileCacheAccepted() {
|
||||
Timber.d("onClearFileCacheAccepted")
|
||||
viewModelScope.launch {
|
||||
jobs += viewModelScope.launch {
|
||||
analytics.sendSettingsStorageOffloadEvent()
|
||||
}
|
||||
viewModelScope.launch {
|
||||
|
@ -82,7 +136,7 @@ class FilesStorageViewModel(
|
|||
is Interactor.Status.Error -> {
|
||||
isClearFileCacheInProgress.value = false
|
||||
Timber.e(status.throwable, "Error while clearing file cache")
|
||||
// TODO send toast
|
||||
toasts.emit("Error while clearing the file cache")
|
||||
}
|
||||
Interactor.Status.Success -> {
|
||||
viewModelScope.sendEvent(
|
||||
|
@ -104,7 +158,7 @@ class FilesStorageViewModel(
|
|||
private suspend fun dispatchCommand(event: Event) {
|
||||
when (event) {
|
||||
Event.OnManageFilesClicked -> {
|
||||
commands.emit(Command.OpenRemoteStorageScreen(subscription = Subscription.Files.id))
|
||||
commands.emit(Command.OpenRemoteStorageScreen(Subscription.Files.id))
|
||||
analytics.sendSettingsStorageManageEvent()
|
||||
}
|
||||
Event.OnOffloadFilesClicked -> {
|
||||
|
@ -132,40 +186,52 @@ class FilesStorageViewModel(
|
|||
}
|
||||
|
||||
private fun subscribeToSpace() {
|
||||
viewModelScope.launch {
|
||||
storelessSubscriptionContainer.subscribe(
|
||||
StoreSearchByIdsParams(
|
||||
subscription = SPACE_SUBSCRIPTION_ID,
|
||||
targets = listOf(workspaceId, profileId),
|
||||
keys = listOf(
|
||||
Relations.ID,
|
||||
Relations.NAME,
|
||||
Relations.ICON_EMOJI,
|
||||
Relations.ICON_IMAGE,
|
||||
Relations.ICON_OPTION
|
||||
)
|
||||
val workspaceId = configStorage.get().workspace
|
||||
val profileId = configStorage.get().profile
|
||||
jobs += viewModelScope.launch {
|
||||
val subscribeParams = StoreSearchByIdsParams(
|
||||
subscription = SPACE_STORAGE_SUBSCRIPTION_ID,
|
||||
targets = listOf(workspaceId, profileId),
|
||||
keys = listOf(
|
||||
Relations.ID,
|
||||
Relations.NAME,
|
||||
Relations.ICON_EMOJI,
|
||||
Relations.ICON_IMAGE,
|
||||
Relations.ICON_OPTION
|
||||
)
|
||||
).map { result ->
|
||||
)
|
||||
combine(
|
||||
fileSpaceUsage.asFlow(Unit),
|
||||
storelessSubscriptionContainer.subscribe(subscribeParams)
|
||||
) { spaceUsage, result ->
|
||||
val workspace = result.find { it.id == workspaceId }
|
||||
val spaceUsage = (500 * 1024 * 1024).toDouble()
|
||||
val spaceLimit = (1024 * 1024 * 1024).toDouble()
|
||||
val localUsage = (104 * 1024 * 1024).toDouble()
|
||||
FilesStorageScreenState.SpaceData(
|
||||
spaceName = workspace?.name,
|
||||
val bytesUsage = spaceUsage.bytesUsage
|
||||
val bytesLimit = spaceUsage.bytesLimit
|
||||
val percentUsage =
|
||||
if (bytesUsage != null && bytesLimit != null && bytesLimit != 0L) {
|
||||
(bytesUsage.toFloat() / bytesLimit.toFloat())
|
||||
} else {
|
||||
null
|
||||
}
|
||||
ScreenState(
|
||||
spaceName = workspace?.name.orEmpty(),
|
||||
spaceIcon = workspace?.spaceIcon(urlBuilder, spaceGradientProvider),
|
||||
spaceUsage = bytesToHumanReadableSize(spaceUsage),
|
||||
percentUsage = (spaceUsage / spaceLimit).toFloat(),
|
||||
spaceUsage = bytesUsage?.let { bytesToHumanReadableSize(it) }.orEmpty(),
|
||||
percentUsage = percentUsage,
|
||||
device = getDeviceName(),
|
||||
localUsage = bytesToHumanReadableSize(localUsage),
|
||||
spaceLimit = bytesToHumanReadableSize(spaceLimit)
|
||||
localUsage = spaceUsage.localBytesUsage?.let { bytesToHumanReadableSize(it) }
|
||||
.orEmpty(),
|
||||
spaceLimit = bytesLimit?.let { bytesToHumanReadableSize(it) }.orEmpty(),
|
||||
)
|
||||
}.flowOn(appCoroutineDispatchers.io).collect { _state.value = it }
|
||||
}
|
||||
.flowOn(appCoroutineDispatchers.io)
|
||||
.collect { _state.value = it }
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDeviceName(): String {
|
||||
val manufacturer = Build.MANUFACTURER.capitalize()
|
||||
val model = Build.MODEL.capitalize()
|
||||
val manufacturer = buildProvider.getManufacturer().capitalize()
|
||||
val model = buildProvider.getModel().capitalize()
|
||||
return if (model.startsWith(manufacturer, ignoreCase = true)) {
|
||||
model
|
||||
} else {
|
||||
|
@ -190,7 +256,10 @@ class FilesStorageViewModel(
|
|||
private val clearFileCache: ClearFileCache,
|
||||
private val urlBuilder: UrlBuilder,
|
||||
private val spaceGradientProvider: SpaceGradientProvider,
|
||||
private val appCoroutineDispatchers: AppCoroutineDispatchers
|
||||
private val appCoroutineDispatchers: AppCoroutineDispatchers,
|
||||
private val fileSpaceUsage: FileSpaceUsage,
|
||||
private val interceptFileLimitEvents: InterceptFileLimitEvents,
|
||||
private val buildProvider: BuildProvider
|
||||
) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(
|
||||
|
@ -198,26 +267,36 @@ class FilesStorageViewModel(
|
|||
): T = FilesStorageViewModel(
|
||||
analytics = analytics,
|
||||
storelessSubscriptionContainer = storelessSubscriptionContainer,
|
||||
clearFileCache = clearFileCache,
|
||||
configStorage = configStorage,
|
||||
clearFileCache = clearFileCache,
|
||||
urlBuilder = urlBuilder,
|
||||
spaceGradientProvider = spaceGradientProvider,
|
||||
appCoroutineDispatchers = appCoroutineDispatchers
|
||||
appCoroutineDispatchers = appCoroutineDispatchers,
|
||||
fileSpaceUsage = fileSpaceUsage,
|
||||
interceptFileLimitEvents = interceptFileLimitEvents,
|
||||
buildProvider = buildProvider
|
||||
) as T
|
||||
}
|
||||
}
|
||||
|
||||
sealed class FilesStorageScreenState {
|
||||
|
||||
object Idle : FilesStorageScreenState()
|
||||
|
||||
data class SpaceData(
|
||||
data class ScreenState(
|
||||
val spaceIcon: SpaceIconView?,
|
||||
val spaceName: String?,
|
||||
val spaceName: String,
|
||||
val spaceLimit: String,
|
||||
val spaceUsage: String,
|
||||
val percentUsage: Float,
|
||||
val percentUsage: Float?,
|
||||
val device: String?,
|
||||
val localUsage: String
|
||||
): FilesStorageScreenState()
|
||||
) {
|
||||
companion object {
|
||||
fun empty() = ScreenState(
|
||||
spaceIcon = null,
|
||||
spaceName = "",
|
||||
spaceLimit = "",
|
||||
spaceUsage = "",
|
||||
percentUsage = null,
|
||||
device = null,
|
||||
localUsage = ""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -48,7 +48,7 @@ class MainSettingsViewModel(
|
|||
|
||||
val workspaceAndAccount = storelessSubscriptionContainer.subscribe(
|
||||
StoreSearchByIdsParams(
|
||||
subscription = SPACE_SUBSCRIPTION_ID,
|
||||
subscription = SPACE_STORAGE_SUBSCRIPTION_ID,
|
||||
targets = listOf(workspaceId, profileId),
|
||||
keys = listOf(
|
||||
Relations.ID,
|
||||
|
@ -151,7 +151,7 @@ class MainSettingsViewModel(
|
|||
super.onCleared()
|
||||
viewModelScope.launch {
|
||||
storelessSubscriptionContainer.unsubscribe(
|
||||
listOf(SPACE_SUBSCRIPTION_ID)
|
||||
listOf(SPACE_STORAGE_SUBSCRIPTION_ID)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -240,5 +240,5 @@ class MainSettingsViewModel(
|
|||
|
||||
}
|
||||
|
||||
const val SPACE_SUBSCRIPTION_ID = "settings_space_subscription"
|
||||
const val SPACE_STORAGE_SUBSCRIPTION_ID = "settings_space_storage_subscription"
|
||||
const val STOP_SUBSCRIPTION_TIMEOUT = 1_000L
|
|
@ -59,7 +59,7 @@ sealed class Subscription(
|
|||
|
||||
object Files : Subscription(
|
||||
id = Subscriptions.SUBSCRIPTION_FILES,
|
||||
keys = SUBSCRIPTION_DEFAULT_KEYS + Relations.SIZE_IN_BYTES + Relations.FILE_MIME_TYPE + Relations.FILE_EXT,
|
||||
keys = SUBSCRIPTION_DEFAULT_KEYS + Relations.SIZE_IN_BYTES + Relations.FILE_MIME_TYPE + Relations.FILE_EXT + Relations.FILE_SYNC_STATUS,
|
||||
sorts = listOf(
|
||||
DVSort(
|
||||
relationKey = Relations.SIZE_IN_BYTES,
|
||||
|
|
|
@ -86,6 +86,8 @@ import com.anytypeio.anytype.domain.templates.ApplyTemplate
|
|||
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.FileLimitsEventChannel
|
||||
import com.anytypeio.anytype.domain.workspace.InterceptFileLimitEvents
|
||||
import com.anytypeio.anytype.domain.workspace.WorkspaceManager
|
||||
import com.anytypeio.anytype.presentation.BuildConfig
|
||||
import com.anytypeio.anytype.presentation.MockBlockFactory
|
||||
|
@ -337,6 +339,10 @@ open class EditorViewModelTest {
|
|||
|
||||
private lateinit var updateDetail: UpdateDetail
|
||||
|
||||
@Mock
|
||||
lateinit var fileLimitsEventChannel: FileLimitsEventChannel
|
||||
lateinit var interceptFileLimitEvents: InterceptFileLimitEvents
|
||||
|
||||
lateinit var vm: EditorViewModel
|
||||
|
||||
private lateinit var builder: UrlBuilder
|
||||
|
@ -3890,6 +3896,7 @@ open class EditorViewModelTest {
|
|||
downloadUnsplashImage = DownloadUnsplashImage(unsplashRepo)
|
||||
clearBlockContent = ClearBlockContent(repo)
|
||||
clearBlockStyle = ClearBlockStyle(repo)
|
||||
interceptFileLimitEvents = InterceptFileLimitEvents(fileLimitsEventChannel, dispatchers)
|
||||
|
||||
workspaceManager = WorkspaceManager.DefaultWorkspaceManager()
|
||||
runBlocking {
|
||||
|
@ -3900,7 +3907,7 @@ open class EditorViewModelTest {
|
|||
vm = EditorViewModel(
|
||||
openPage = openPage,
|
||||
closePage = closePage,
|
||||
createObject = createObject,
|
||||
createBlockLinkWithObject = createBlockLinkWithObject,
|
||||
createObjectAsMentionOrLink = createObjectAsMentionOrLink,
|
||||
interceptEvents = interceptEvents,
|
||||
interceptThreadStatus = interceptThreadStatus,
|
||||
|
@ -3972,15 +3979,16 @@ open class EditorViewModelTest {
|
|||
setDocCoverImage = setDocCoverImage,
|
||||
setDocImageIcon = setDocImageIcon,
|
||||
templateDelegate = editorTemplateDelegate,
|
||||
createBlockLinkWithObject = createBlockLinkWithObject,
|
||||
featureToggles = mock(),
|
||||
createObject = createObject,
|
||||
objectToSet = objectToSet,
|
||||
objectToCollection = convertObjectToCollection,
|
||||
storeOfRelations = storeOfRelations,
|
||||
storeOfObjectTypes = storeOfObjectTypes,
|
||||
featureToggles = mock(),
|
||||
tableDelegate = tableDelegate,
|
||||
workspaceManager = workspaceManager,
|
||||
getObjectTypes = getObjectTypes,
|
||||
objectToCollection = convertObjectToCollection
|
||||
interceptFileLimitEvents = interceptFileLimitEvents
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -84,6 +84,8 @@ import com.anytypeio.anytype.domain.templates.ApplyTemplate
|
|||
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.FileLimitsEventChannel
|
||||
import com.anytypeio.anytype.domain.workspace.InterceptFileLimitEvents
|
||||
import com.anytypeio.anytype.domain.workspace.WorkspaceManager
|
||||
import com.anytypeio.anytype.presentation.common.Action
|
||||
import com.anytypeio.anytype.presentation.common.Delegator
|
||||
|
@ -336,6 +338,10 @@ open class EditorPresentationTestSetup {
|
|||
@Mock
|
||||
lateinit var getObjectTypes: GetObjectTypes
|
||||
|
||||
@Mock
|
||||
lateinit var fileLimitsEventChannel: FileLimitsEventChannel
|
||||
lateinit var interceptFileLimitEvents: InterceptFileLimitEvents
|
||||
|
||||
open fun buildViewModel(urlBuilder: UrlBuilder = builder): EditorViewModel {
|
||||
|
||||
val storage = Editor.Storage()
|
||||
|
@ -414,6 +420,7 @@ open class EditorPresentationTestSetup {
|
|||
workspaceManager.setCurrentWorkspace(workspaceId)
|
||||
}
|
||||
|
||||
interceptFileLimitEvents = InterceptFileLimitEvents(fileLimitsEventChannel, dispatchers)
|
||||
return EditorViewModel(
|
||||
openPage = openPage,
|
||||
closePage = closePage,
|
||||
|
@ -447,14 +454,15 @@ open class EditorPresentationTestSetup {
|
|||
setDocImageIcon = setDocImageIcon,
|
||||
templateDelegate = editorTemplateDelegate,
|
||||
createObject = createObject,
|
||||
featureToggles = mock(),
|
||||
objectToSet = objectToSet,
|
||||
objectToCollection = convertObjectToCollection,
|
||||
storeOfRelations = storeOfRelations,
|
||||
storeOfObjectTypes = storeOfObjectTypes,
|
||||
featureToggles = mock(),
|
||||
tableDelegate = tableDelegate,
|
||||
workspaceManager = workspaceManager,
|
||||
getObjectTypes = getObjectTypes,
|
||||
objectToCollection = convertObjectToCollection
|
||||
interceptFileLimitEvents = interceptFileLimitEvents
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ import com.anytypeio.anytype.core_ui.views.ButtonSize
|
|||
import com.anytypeio.anytype.core_ui.views.PreviewTitle2Medium
|
||||
import com.anytypeio.anytype.core_ui.views.Relations3
|
||||
import com.anytypeio.anytype.core_ui.views.Title1
|
||||
import com.anytypeio.anytype.presentation.settings.FilesStorageScreenState
|
||||
import com.anytypeio.anytype.presentation.settings.FilesStorageViewModel.ScreenState
|
||||
import com.anytypeio.anytype.presentation.spaces.SpaceIconView
|
||||
import com.anytypeio.anytype.ui_settings.R
|
||||
import com.anytypeio.anytype.ui_settings.fstorage.MockFileStorage.mockData
|
||||
|
@ -44,170 +44,165 @@ import com.anytypeio.anytype.ui_settings.main.SpaceImageBlock
|
|||
|
||||
@Composable
|
||||
fun FilesStorageScreen(
|
||||
data: FilesStorageScreenState,
|
||||
data: ScreenState,
|
||||
onOffloadFilesClicked: () -> Unit,
|
||||
onManageFilesClicked: () -> Unit
|
||||
) {
|
||||
when (data) {
|
||||
FilesStorageScreenState.Idle -> {
|
||||
|
||||
}
|
||||
is FilesStorageScreenState.SpaceData -> {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
backgroundColor = colorResource(id = R.color.background_primary)
|
||||
Card(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
backgroundColor = colorResource(id = R.color.background_primary)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(start = 20.dp, end = 20.dp),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(vertical = 6.dp)
|
||||
.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Dragger()
|
||||
}
|
||||
Header()
|
||||
Spacer(
|
||||
modifier = Modifier.height(36.dp)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.remote_storage),
|
||||
style = Title1,
|
||||
color = colorResource(id = R.color.text_primary),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.you_can_store, data.spaceLimit),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 4.dp),
|
||||
color = colorResource(R.color.text_primary),
|
||||
style = BodyCalloutRegular
|
||||
)
|
||||
Spacer(modifier = Modifier.height(28.dp))
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.height(48.dp)
|
||||
) {
|
||||
SpaceImageBlock(
|
||||
icon = data.spaceIcon,
|
||||
onSpaceIconClick = {},
|
||||
mainSize = 48.dp,
|
||||
emojiSize = 24.dp,
|
||||
gradientSize = 36.dp
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(start = 20.dp, end = 20.dp),
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight()
|
||||
.padding(start = 12.dp),
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(vertical = 6.dp)
|
||||
.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Dragger()
|
||||
}
|
||||
Header()
|
||||
Spacer(
|
||||
modifier = Modifier.height(36.dp)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.remote_storage),
|
||||
style = Title1,
|
||||
text = data.spaceName.orEmpty(),
|
||||
style = PreviewTitle2Medium,
|
||||
color = colorResource(id = R.color.text_primary),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.you_can_store, data.spaceLimit),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 4.dp),
|
||||
color = colorResource(R.color.text_primary),
|
||||
style = BodyCalloutRegular
|
||||
)
|
||||
Spacer(modifier = Modifier.height(28.dp))
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.height(48.dp)
|
||||
) {
|
||||
SpaceImageBlock(
|
||||
icon = data.spaceIcon,
|
||||
onSpaceIconClick = {},
|
||||
mainSize = 48.dp,
|
||||
emojiSize = 24.dp,
|
||||
gradientSize = 36.dp
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight()
|
||||
.padding(start = 12.dp),
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = data.spaceName.orEmpty(),
|
||||
style = PreviewTitle2Medium,
|
||||
color = colorResource(id = R.color.text_primary),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
Text(
|
||||
text = stringResource(
|
||||
id = R.string.space_usage,
|
||||
data.spaceUsage,
|
||||
data.spaceLimit
|
||||
),
|
||||
style = Relations3,
|
||||
color = if (data.percentUsage >= 0.9F) {
|
||||
colorResource(id = R.color.palette_system_red)
|
||||
} else {
|
||||
colorResource(id = R.color.text_secondary)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
DefaultLinearProgressIndicator(progress = data.percentUsage)
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
ButtonSecondary(
|
||||
text = stringResource(id = R.string.manage_files),
|
||||
onClick = onManageFilesClicked,
|
||||
size = ButtonSize.XSmall.apply {
|
||||
contentPadding = PaddingValues(12.dp, 7.dp, 12.dp, 7.dp)
|
||||
}
|
||||
)
|
||||
Spacer(modifier = Modifier.height(44.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.local_storage),
|
||||
style = Title1,
|
||||
color = colorResource(id = R.color.text_primary),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.in_order_to_save),
|
||||
style = BodyCalloutRegular,
|
||||
color = colorResource(id = R.color.text_primary),
|
||||
modifier = Modifier
|
||||
.padding(top = 4.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(28.dp))
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.wrapContentHeight(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.background(
|
||||
color = colorResource(id = R.color.shape_tertiary),
|
||||
shape = RoundedCornerShape(4.dp)
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "\uD83D\uDCF1",
|
||||
style = TextStyle(fontSize = 28.sp)
|
||||
)
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 12.dp),
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = data.device.orEmpty(),
|
||||
style = PreviewTitle2Medium,
|
||||
color = colorResource(id = R.color.text_primary),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
Text(
|
||||
text = stringResource(
|
||||
id = R.string.local_storage_used,
|
||||
data.localUsage
|
||||
),
|
||||
style = Relations3,
|
||||
color = colorResource(id = R.color.text_secondary),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(18.dp))
|
||||
ButtonSecondary(
|
||||
text = stringResource(id = R.string.offload_files),
|
||||
onClick = onOffloadFilesClicked,
|
||||
size = ButtonSize.XSmall.apply {
|
||||
contentPadding = PaddingValues(12.dp, 7.dp, 12.dp, 7.dp)
|
||||
}
|
||||
text = stringResource(
|
||||
id = R.string.space_usage,
|
||||
data.spaceUsage,
|
||||
data.spaceLimit
|
||||
),
|
||||
style = Relations3,
|
||||
color = if (data.percentUsage != null && data.percentUsage!! >= 0.9F) {
|
||||
colorResource(id = R.color.palette_system_red)
|
||||
} else {
|
||||
colorResource(id = R.color.text_secondary)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
data.percentUsage?.let {
|
||||
DefaultLinearProgressIndicator(progress = it)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
ButtonSecondary(
|
||||
text = stringResource(id = R.string.manage_files),
|
||||
onClick = onManageFilesClicked,
|
||||
size = ButtonSize.XSmall.apply {
|
||||
contentPadding = PaddingValues(12.dp, 7.dp, 12.dp, 7.dp)
|
||||
}
|
||||
)
|
||||
Spacer(modifier = Modifier.height(44.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.local_storage),
|
||||
style = Title1,
|
||||
color = colorResource(id = R.color.text_primary),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.in_order_to_save),
|
||||
style = BodyCalloutRegular,
|
||||
color = colorResource(id = R.color.text_primary),
|
||||
modifier = Modifier
|
||||
.padding(top = 4.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(28.dp))
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.wrapContentHeight(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.background(
|
||||
color = colorResource(id = R.color.shape_tertiary),
|
||||
shape = RoundedCornerShape(4.dp)
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "\uD83D\uDCF1",
|
||||
style = TextStyle(fontSize = 28.sp)
|
||||
)
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 12.dp),
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = data.device.orEmpty(),
|
||||
style = PreviewTitle2Medium,
|
||||
color = colorResource(id = R.color.text_primary),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
Text(
|
||||
text = stringResource(
|
||||
id = R.string.local_storage_used,
|
||||
data.localUsage
|
||||
),
|
||||
style = Relations3,
|
||||
color = colorResource(id = R.color.text_secondary),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(18.dp))
|
||||
ButtonSecondary(
|
||||
text = stringResource(id = R.string.offload_files),
|
||||
onClick = onOffloadFilesClicked,
|
||||
size = ButtonSize.XSmall.apply {
|
||||
contentPadding = PaddingValues(12.dp, 7.dp, 12.dp, 7.dp)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -250,11 +245,11 @@ object MockFileStorage {
|
|||
val mockSpaceIcon = SpaceIconView.Gradient(from = "#EEDEAE", to = "#B93252")
|
||||
val mockSpaceName = "Anton’s space"
|
||||
val mockSpaceInfraUsage = "212 MB of 1 GB used"
|
||||
val mockSpaceInfraPercent = 89F
|
||||
val mockSpaceInfraPercent = 0.9F
|
||||
val mockDevice = "iPhone 13 Pro"
|
||||
val mockSpaceLocalUsage = "518 MB used"
|
||||
val mockInfraMax = "14 GB"
|
||||
val mockData = FilesStorageScreenState.SpaceData(
|
||||
val mockData = ScreenState(
|
||||
spaceIcon = mockSpaceIcon,
|
||||
spaceName = mockSpaceName,
|
||||
spaceUsage = mockSpaceInfraUsage,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue