mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-07 21:37:02 +09:00
DROID-2836 All content | epic (#1608)
This commit is contained in:
parent
a1dcdda9d6
commit
7c03809e18
29 changed files with 2669 additions and 11 deletions
|
@ -162,6 +162,7 @@ dependencies {
|
|||
implementation project(':payments')
|
||||
implementation project(':feature-discussions')
|
||||
implementation project(':gallery-experience')
|
||||
implementation project(':feature-all-content')
|
||||
|
||||
//Compile time dependencies
|
||||
ksp libs.daggerCompiler
|
||||
|
@ -197,6 +198,7 @@ dependencies {
|
|||
implementation libs.lifecycleCompose
|
||||
|
||||
implementation libs.compose
|
||||
implementation libs.fragmentCompose
|
||||
implementation libs.composeFoundation
|
||||
implementation libs.composeMaterial
|
||||
implementation libs.composeMaterial3
|
||||
|
|
|
@ -6,6 +6,7 @@ import com.anytypeio.anytype.core_models.Id
|
|||
import com.anytypeio.anytype.core_models.primitives.SpaceId
|
||||
import com.anytypeio.anytype.di.feature.CreateBookmarkModule
|
||||
import com.anytypeio.anytype.di.feature.CreateObjectModule
|
||||
import com.anytypeio.anytype.di.feature.DaggerAllContentComponent
|
||||
import com.anytypeio.anytype.di.feature.DaggerAppPreferencesComponent
|
||||
import com.anytypeio.anytype.di.feature.DaggerBacklinkOrAddToObjectComponent
|
||||
import com.anytypeio.anytype.di.feature.DaggerSplashComponent
|
||||
|
@ -99,6 +100,7 @@ import com.anytypeio.anytype.di.feature.wallpaper.WallpaperSelectModule
|
|||
import com.anytypeio.anytype.di.feature.widgets.SelectWidgetSourceModule
|
||||
import com.anytypeio.anytype.di.feature.widgets.SelectWidgetTypeModule
|
||||
import com.anytypeio.anytype.di.main.MainComponent
|
||||
import com.anytypeio.anytype.feature_allcontent.presentation.AllContentViewModel
|
||||
import com.anytypeio.anytype.gallery_experience.viewmodel.GalleryInstallationViewModel
|
||||
import com.anytypeio.anytype.presentation.editor.EditorViewModel
|
||||
import com.anytypeio.anytype.presentation.history.VersionHistoryViewModel
|
||||
|
@ -329,6 +331,12 @@ class ComponentManager(
|
|||
.create(params, findComponentDependencies())
|
||||
}
|
||||
|
||||
val allContentComponent = ComponentWithParams { params: AllContentViewModel.VmParams ->
|
||||
DaggerAllContentComponent
|
||||
.factory()
|
||||
.create(params, findComponentDependencies())
|
||||
}
|
||||
|
||||
val objectSetComponent = ComponentMapWithParam { param: DefaultComponentParam ->
|
||||
main.objectSetComponentBuilder()
|
||||
.module(ObjectSetModule)
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
package com.anytypeio.anytype.di.feature
|
||||
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.anytypeio.anytype.analytics.base.Analytics
|
||||
import com.anytypeio.anytype.core_utils.di.scope.PerScreen
|
||||
import com.anytypeio.anytype.di.common.ComponentDependencies
|
||||
import com.anytypeio.anytype.domain.all_content.RestoreAllContentState
|
||||
import com.anytypeio.anytype.domain.all_content.UpdateAllContentState
|
||||
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
import com.anytypeio.anytype.domain.config.UserSettingsRepository
|
||||
import com.anytypeio.anytype.domain.debugging.Logger
|
||||
import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer
|
||||
import com.anytypeio.anytype.domain.misc.LocaleProvider
|
||||
import com.anytypeio.anytype.domain.misc.UrlBuilder
|
||||
import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
|
||||
import com.anytypeio.anytype.domain.objects.StoreOfRelations
|
||||
import com.anytypeio.anytype.domain.search.SearchObjects
|
||||
import com.anytypeio.anytype.domain.search.SubscriptionEventChannel
|
||||
import com.anytypeio.anytype.feature_allcontent.presentation.AllContentViewModel
|
||||
import com.anytypeio.anytype.feature_allcontent.presentation.AllContentViewModelFactory
|
||||
import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate
|
||||
import com.anytypeio.anytype.ui.allcontent.AllContentFragment
|
||||
import dagger.Binds
|
||||
import dagger.BindsInstance
|
||||
import dagger.Component
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import javax.inject.Named
|
||||
|
||||
@Component(
|
||||
dependencies = [AllContentDependencies::class],
|
||||
modules = [
|
||||
AllContentModule::class,
|
||||
AllContentModule.Declarations::class
|
||||
]
|
||||
)
|
||||
@PerScreen
|
||||
interface AllContentComponent {
|
||||
@Component.Factory
|
||||
interface Factory {
|
||||
fun create(
|
||||
@BindsInstance vmParams: AllContentViewModel.VmParams,
|
||||
dependencies: AllContentDependencies
|
||||
): AllContentComponent
|
||||
}
|
||||
|
||||
fun inject(fragment: AllContentFragment)
|
||||
}
|
||||
|
||||
@Module
|
||||
object AllContentModule {
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@PerScreen
|
||||
fun provideStoreLessSubscriptionContainer(
|
||||
repo: BlockRepository,
|
||||
channel: SubscriptionEventChannel,
|
||||
dispatchers: AppCoroutineDispatchers,
|
||||
logger: Logger
|
||||
): StorelessSubscriptionContainer = StorelessSubscriptionContainer.Impl(
|
||||
repo = repo,
|
||||
channel = channel,
|
||||
dispatchers = dispatchers,
|
||||
logger = logger
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@PerScreen
|
||||
fun provideUpdateAllContentState(
|
||||
dispatchers: AppCoroutineDispatchers,
|
||||
userSettingsRepository: UserSettingsRepository
|
||||
): UpdateAllContentState = UpdateAllContentState(
|
||||
dispatchers = dispatchers,
|
||||
settings = userSettingsRepository
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@PerScreen
|
||||
fun provideRestoreAllContentState(
|
||||
dispatchers: AppCoroutineDispatchers,
|
||||
userSettingsRepository: UserSettingsRepository
|
||||
): RestoreAllContentState = RestoreAllContentState(
|
||||
dispatchers = dispatchers,
|
||||
settings = userSettingsRepository
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@PerScreen
|
||||
fun searchObjects(
|
||||
repo: BlockRepository
|
||||
): SearchObjects = SearchObjects(repo = repo)
|
||||
|
||||
@Module
|
||||
interface Declarations {
|
||||
@PerScreen
|
||||
@Binds
|
||||
fun bindViewModelFactory(
|
||||
factory: AllContentViewModelFactory
|
||||
): ViewModelProvider.Factory
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
interface AllContentDependencies : ComponentDependencies {
|
||||
fun blockRepository(): BlockRepository
|
||||
fun analytics(): Analytics
|
||||
fun urlBuilder(): UrlBuilder
|
||||
fun dispatchers(): AppCoroutineDispatchers
|
||||
fun storeOfObjectTypes(): StoreOfObjectTypes
|
||||
fun storeOfRelations(): StoreOfRelations
|
||||
fun analyticsHelper(): AnalyticSpaceHelperDelegate
|
||||
fun userSettingsRepository(): UserSettingsRepository
|
||||
fun subEventChannel(): SubscriptionEventChannel
|
||||
fun logger(): Logger
|
||||
fun localeProvider(): LocaleProvider
|
||||
}
|
|
@ -3,6 +3,7 @@ package com.anytypeio.anytype.di.main
|
|||
import com.anytypeio.anytype.app.AndroidApplication
|
||||
import com.anytypeio.anytype.di.common.ComponentDependencies
|
||||
import com.anytypeio.anytype.di.common.ComponentDependenciesKey
|
||||
import com.anytypeio.anytype.di.feature.AllContentDependencies
|
||||
import com.anytypeio.anytype.di.feature.AppPreferencesDependencies
|
||||
import com.anytypeio.anytype.di.feature.BacklinkOrAddToObjectDependencies
|
||||
import com.anytypeio.anytype.di.feature.CreateBookmarkSubComponent
|
||||
|
@ -126,7 +127,8 @@ interface MainComponent :
|
|||
NotificationDependencies,
|
||||
GlobalSearchDependencies,
|
||||
MembershipUpdateComponentDependencies,
|
||||
VaultComponentDependencies
|
||||
VaultComponentDependencies,
|
||||
AllContentDependencies
|
||||
{
|
||||
|
||||
fun inject(app: AndroidApplication)
|
||||
|
@ -352,4 +354,9 @@ abstract class ComponentDependenciesModule {
|
|||
@IntoMap
|
||||
@ComponentDependenciesKey(VaultComponentDependencies::class)
|
||||
abstract fun provideVaultComponentDependencies(component: MainComponent): ComponentDependencies
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ComponentDependenciesKey(AllContentDependencies::class)
|
||||
abstract fun provideAllContentDependencies(component: MainComponent): ComponentDependencies
|
||||
}
|
|
@ -8,6 +8,7 @@ import com.anytypeio.anytype.core_models.Id
|
|||
import com.anytypeio.anytype.core_models.Key
|
||||
import com.anytypeio.anytype.presentation.navigation.AppNavigation
|
||||
import com.anytypeio.anytype.presentation.widgets.collection.Subscription
|
||||
import com.anytypeio.anytype.ui.allcontent.AllContentFragment
|
||||
import com.anytypeio.anytype.ui.auth.account.DeletedAccountFragment
|
||||
import com.anytypeio.anytype.ui.editor.EditorFragment
|
||||
import com.anytypeio.anytype.ui.editor.EditorModalFragment
|
||||
|
@ -239,4 +240,11 @@ class Navigator : AppNavigation {
|
|||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun openAllContent(space: Id) {
|
||||
navController?.navigate(
|
||||
resId = R.id.action_open_all_content,
|
||||
args = AllContentFragment.args(space)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
package com.anytypeio.anytype.ui.allcontent
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.fragment.compose.content
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.anytypeio.anytype.BuildConfig
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.primitives.SpaceId
|
||||
import com.anytypeio.anytype.core_utils.ext.argString
|
||||
import com.anytypeio.anytype.core_utils.ext.subscribe
|
||||
import com.anytypeio.anytype.core_utils.ext.toast
|
||||
import com.anytypeio.anytype.core_utils.insets.EDGE_TO_EDGE_MIN_SDK
|
||||
import com.anytypeio.anytype.core_utils.ui.BaseComposeFragment
|
||||
import com.anytypeio.anytype.di.common.componentManager
|
||||
import com.anytypeio.anytype.feature_allcontent.presentation.AllContentViewModel
|
||||
import com.anytypeio.anytype.feature_allcontent.presentation.AllContentViewModelFactory
|
||||
import com.anytypeio.anytype.feature_allcontent.ui.AllContentWrapperScreen
|
||||
import com.anytypeio.anytype.feature_allcontent.ui.AllContentNavigation.ALL_CONTENT_MAIN
|
||||
import com.anytypeio.anytype.ui.base.navigation
|
||||
import com.anytypeio.anytype.ui.settings.typography
|
||||
import javax.inject.Inject
|
||||
|
||||
class AllContentFragment : BaseComposeFragment() {
|
||||
|
||||
@Inject
|
||||
lateinit var factory: AllContentViewModelFactory
|
||||
|
||||
private val vm by viewModels<AllContentViewModel> { factory }
|
||||
|
||||
private val space get() = argString(ARG_SPACE)
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
) = content {
|
||||
MaterialTheme(
|
||||
typography = typography
|
||||
) {
|
||||
AllContentScreenWrapper()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
subscribe(vm.commands) { command ->
|
||||
when (command) {
|
||||
is AllContentViewModel.Command.NavigateToEditor -> {
|
||||
runCatching {
|
||||
navigation().openDocument(
|
||||
target = command.id,
|
||||
space = command.space
|
||||
)
|
||||
}
|
||||
}
|
||||
is AllContentViewModel.Command.NavigateToSetOrCollection -> {
|
||||
runCatching {
|
||||
navigation().openObjectSet(
|
||||
target = command.id,
|
||||
space = command.space,
|
||||
)
|
||||
}
|
||||
}
|
||||
is AllContentViewModel.Command.SendToast -> {
|
||||
toast(command.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AllContentScreenWrapper() {
|
||||
NavHost(
|
||||
navController = rememberNavController(),
|
||||
startDestination = ALL_CONTENT_MAIN
|
||||
) {
|
||||
composable(route = ALL_CONTENT_MAIN) {
|
||||
AllContentWrapperScreen(
|
||||
uiState = vm.uiState.collectAsStateWithLifecycle().value,
|
||||
onTabClick = vm::onTabClicked,
|
||||
onQueryChanged = vm::onFilterChanged,
|
||||
uiTabsState = vm.uiTabsState.collectAsStateWithLifecycle().value,
|
||||
uiTitleState = vm.uiTitleState.collectAsStateWithLifecycle().value,
|
||||
uiMenuButtonViewState = vm.uiMenuButtonState.collectAsStateWithLifecycle().value,
|
||||
uiMenuState = vm.uiMenu.collectAsStateWithLifecycle().value,
|
||||
onSortClick = vm::onSortClicked,
|
||||
onModeClick = vm::onAllContentModeClicked,
|
||||
onItemClicked = vm::onItemClicked
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
vm.onStop()
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
override fun injectDependencies() {
|
||||
val vmParams = AllContentViewModel.VmParams(spaceId = SpaceId(space))
|
||||
componentManager().allContentComponent.get(vmParams).inject(this)
|
||||
}
|
||||
|
||||
override fun releaseDependencies() {
|
||||
componentManager().allContentComponent.release()
|
||||
}
|
||||
|
||||
override fun onApplyWindowRootInsets(view: View) {
|
||||
if (BuildConfig.USE_EDGE_TO_EDGE && Build.VERSION.SDK_INT >= EDGE_TO_EDGE_MIN_SDK) {
|
||||
// Do nothing.
|
||||
} else {
|
||||
super.onApplyWindowRootInsets(view)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KEYBOARD_HIDE_DELAY = 300L
|
||||
|
||||
const val ARG_SPACE = "arg.all.content.space"
|
||||
fun args(space: Id): Bundle = bundleOf(ARG_SPACE to space)
|
||||
}
|
||||
}
|
|
@ -353,14 +353,17 @@ class HomeScreenFragment : BaseComposeFragment() {
|
|||
)
|
||||
}
|
||||
is Navigation.OpenLibrary -> runCatching {
|
||||
runCatching {
|
||||
findNavController().navigate(
|
||||
R.id.libraryFragment,
|
||||
args = LibraryFragment.args(destination.space)
|
||||
)
|
||||
}.onFailure { e ->
|
||||
Timber.e(e, "Error while opening space library from widgets")
|
||||
}
|
||||
findNavController().navigate(
|
||||
R.id.libraryFragment,
|
||||
args = LibraryFragment.args(destination.space)
|
||||
)
|
||||
}.onFailure { e ->
|
||||
Timber.e(e, "Error while opening space library from widgets")
|
||||
}
|
||||
is Navigation.OpenAllContent -> runCatching {
|
||||
navigation().openAllContent(space = destination.space)
|
||||
}.onFailure { e ->
|
||||
Timber.e(e, "Error while opening all content from widgets")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
10
app/src/main/res/anim/enter_from_right.xml
Normal file
10
app/src/main/res/anim/enter_from_right.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<translate
|
||||
android:fromXDelta="100%"
|
||||
android:toXDelta="0%"
|
||||
android:duration="200" />
|
||||
<alpha
|
||||
android:fromAlpha="0.0"
|
||||
android:toAlpha="1.0"
|
||||
android:duration="200" />
|
||||
</set>
|
10
app/src/main/res/anim/exit_to_left.xml
Normal file
10
app/src/main/res/anim/exit_to_left.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<translate
|
||||
android:fromXDelta="0%"
|
||||
android:toXDelta="-70%"
|
||||
android:duration="200" />
|
||||
<alpha
|
||||
android:fromAlpha="1.0"
|
||||
android:toAlpha="0.0"
|
||||
android:duration="200" />
|
||||
</set>
|
|
@ -160,6 +160,12 @@
|
|||
app:popUpTo="@id/vaultScreen"
|
||||
app:popUpToInclusive="true"
|
||||
/>
|
||||
<action
|
||||
android:id="@+id/action_open_all_content"
|
||||
app:destination="@id/allContentScreen"
|
||||
app:enterAnim="@anim/enter_from_right"
|
||||
app:exitAnim="@anim/exit_to_left"
|
||||
/>
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
|
@ -386,6 +392,12 @@
|
|||
android:id="@+id/globalSearchScreen"
|
||||
android:name="com.anytypeio.anytype.ui.search.GlobalSearchFragment"
|
||||
android:label="GlobalSearchScreen" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/allContentScreen"
|
||||
android:name="com.anytypeio.anytype.ui.allcontent.AllContentFragment"
|
||||
android:label="AllContentScreen" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/libraryFragment"
|
||||
android:name="com.anytypeio.anytype.ui.library.LibraryFragment"
|
||||
|
|
|
@ -54,6 +54,7 @@ object Relations {
|
|||
const val CREATOR = "creator"
|
||||
const val SYNC_DATE = "syncDate"
|
||||
const val SYNC_STATUS = "syncStatus"
|
||||
const val IS_HIDDEN_DISCOVERY = "isHiddenDiscovery"
|
||||
|
||||
const val PAGE_COVER = "pageCover"
|
||||
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package com.anytypeio.anytype.core_ui.common
|
||||
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
||||
@Preview(
|
||||
backgroundColor = 0xFFFFFFFF,
|
||||
showBackground = true,
|
||||
uiMode = UI_MODE_NIGHT_NO,
|
||||
name = "Light Mode"
|
||||
)
|
||||
@Preview(
|
||||
backgroundColor = 0x000000,
|
||||
showBackground = true,
|
||||
uiMode = UI_MODE_NIGHT_YES,
|
||||
name = "Dark Mode"
|
||||
)
|
||||
annotation class DefaultPreviews
|
|
@ -1,7 +1,18 @@
|
|||
package com.anytypeio.anytype.core_ui.extensions
|
||||
|
||||
import androidx.compose.animation.core.animateFloat
|
||||
import androidx.compose.animation.core.updateTransition
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsPressedAsState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import com.anytypeio.anytype.core_models.ThemeColor
|
||||
import com.anytypeio.anytype.core_ui.R
|
||||
|
@ -41,3 +52,41 @@ fun light(
|
|||
ThemeColor.GREY -> colorResource(id = R.color.palette_light_grey)
|
||||
ThemeColor.DEFAULT -> colorResource(id = R.color.palette_light_default)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
fun Modifier.bouncingClickable(
|
||||
enabled: Boolean = true,
|
||||
pressScaleFactor: Float = 0.97f,
|
||||
pressAlphaFactor: Float = 0.7f,
|
||||
onLongClick: (() -> Unit)? = null,
|
||||
onDoubleClick: (() -> Unit)? = null,
|
||||
onClick: () -> Unit,
|
||||
) = composed {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val isPressed by interactionSource.collectIsPressedAsState()
|
||||
|
||||
val animationTransition = updateTransition(isPressed, label = "BouncingClickableTransition")
|
||||
val scaleFactor by animationTransition.animateFloat(
|
||||
targetValueByState = { pressed -> if (pressed) pressScaleFactor else 1f },
|
||||
label = "BouncingClickableScaleFactorTransition",
|
||||
)
|
||||
val opacity by animationTransition.animateFloat(
|
||||
targetValueByState = { pressed -> if (pressed) pressAlphaFactor else 1f },
|
||||
label = "BouncingClickableAlphaTransition",
|
||||
)
|
||||
|
||||
this
|
||||
.graphicsLayer {
|
||||
scaleX = scaleFactor
|
||||
scaleY = scaleFactor
|
||||
alpha = opacity
|
||||
}
|
||||
.combinedClickable(
|
||||
interactionSource = interactionSource,
|
||||
indication = null,
|
||||
enabled = enabled,
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick,
|
||||
onDoubleClick = onDoubleClick,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package com.anytypeio.anytype.domain.all_content
|
||||
|
||||
import com.anytypeio.anytype.core_models.DVSort
|
||||
import com.anytypeio.anytype.core_models.primitives.SpaceId
|
||||
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
|
||||
import com.anytypeio.anytype.domain.base.ResultInteractor
|
||||
import com.anytypeio.anytype.domain.config.UserSettingsRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class RestoreAllContentState @Inject constructor(
|
||||
private val settings: UserSettingsRepository,
|
||||
dispatchers: AppCoroutineDispatchers
|
||||
) : ResultInteractor<RestoreAllContentState.Params, RestoreAllContentState.Response>(
|
||||
dispatchers.io
|
||||
) {
|
||||
|
||||
override suspend fun doWork(params: Params): Response {
|
||||
//todo: implement
|
||||
return Response(activeSort = null)
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val spaceId: SpaceId
|
||||
)
|
||||
|
||||
data class Response(
|
||||
val activeSort: String?
|
||||
)
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package com.anytypeio.anytype.domain.all_content
|
||||
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.primitives.SpaceId
|
||||
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
|
||||
import com.anytypeio.anytype.domain.base.ResultInteractor
|
||||
import com.anytypeio.anytype.domain.config.UserSettingsRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class UpdateAllContentState @Inject constructor(
|
||||
private val settings: UserSettingsRepository,
|
||||
dispatchers: AppCoroutineDispatchers
|
||||
) : ResultInteractor<UpdateAllContentState.Params, Unit>(dispatchers.io) {
|
||||
|
||||
override suspend fun doWork(params: Params) {
|
||||
//todo: implement
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val spaceId: SpaceId,
|
||||
val query: String,
|
||||
val relatedObjectId: Id?
|
||||
)
|
||||
}
|
60
feature-all-content/build.gradle
Normal file
60
feature-all-content/build.gradle
Normal file
|
@ -0,0 +1,60 @@
|
|||
plugins {
|
||||
id "com.android.library"
|
||||
id "kotlin-android"
|
||||
alias(libs.plugins.compose.compiler)
|
||||
}
|
||||
|
||||
android {
|
||||
|
||||
defaultConfig {
|
||||
buildConfigField "boolean", "USE_NEW_WINDOW_INSET_API", "true"
|
||||
buildConfigField "boolean", "USE_EDGE_TO_EDGE", "true"
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose true
|
||||
}
|
||||
|
||||
namespace 'com.anytypeio.anytype.feature_allcontent'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation project(':domain')
|
||||
implementation project(':core-ui')
|
||||
implementation project(':analytics')
|
||||
implementation project(':core-models')
|
||||
implementation project(':core-utils')
|
||||
implementation project(':localization')
|
||||
implementation project(':presentation')
|
||||
implementation project(':library-emojifier')
|
||||
|
||||
compileOnly libs.javaxInject
|
||||
|
||||
implementation libs.lifecycleViewModel
|
||||
implementation libs.lifecycleRuntime
|
||||
|
||||
implementation libs.appcompat
|
||||
implementation libs.compose
|
||||
implementation libs.composeFoundation
|
||||
implementation libs.composeToolingPreview
|
||||
implementation libs.composeMaterial3
|
||||
implementation libs.composeMaterial
|
||||
implementation libs.navigationCompose
|
||||
|
||||
debugImplementation libs.composeTooling
|
||||
|
||||
implementation libs.timber
|
||||
|
||||
testImplementation project(':test:android-utils')
|
||||
testImplementation project(':test:utils')
|
||||
testImplementation project(":test:core-models-stub")
|
||||
testImplementation libs.junit
|
||||
testImplementation libs.kotlinTest
|
||||
testImplementation libs.robolectric
|
||||
testImplementation libs.androidXTestCore
|
||||
testImplementation libs.mockitoKotlin
|
||||
testImplementation libs.coroutineTesting
|
||||
testImplementation libs.timberJUnit
|
||||
testImplementation libs.turbine
|
||||
}
|
2
feature-all-content/src/main/AndroidManifest.xml
Normal file
2
feature-all-content/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest/>
|
|
@ -0,0 +1,256 @@
|
|||
package com.anytypeio.anytype.feature_allcontent.models
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.anytypeio.anytype.core_models.DVSortType
|
||||
import com.anytypeio.anytype.core_models.Key
|
||||
import com.anytypeio.anytype.core_models.MarketplaceObjectTypeIds
|
||||
import com.anytypeio.anytype.core_models.ObjectType
|
||||
import com.anytypeio.anytype.core_models.ObjectTypeUniqueKeys
|
||||
import com.anytypeio.anytype.core_models.ObjectWrapper
|
||||
import com.anytypeio.anytype.core_models.Relations
|
||||
import com.anytypeio.anytype.core_models.ext.DateParser
|
||||
import com.anytypeio.anytype.core_models.primitives.RelationKey
|
||||
import com.anytypeio.anytype.core_models.primitives.SpaceId
|
||||
import com.anytypeio.anytype.domain.misc.UrlBuilder
|
||||
import com.anytypeio.anytype.feature_allcontent.presentation.AllContentViewModel.Companion.DEFAULT_INITIAL_SORT
|
||||
import com.anytypeio.anytype.presentation.objects.ObjectIcon
|
||||
import com.anytypeio.anytype.presentation.objects.getProperName
|
||||
import com.anytypeio.anytype.presentation.objects.getProperType
|
||||
|
||||
//region STATE
|
||||
sealed class AllContentState {
|
||||
data object Init : AllContentState()
|
||||
data class Default(
|
||||
val activeTab: AllContentTab,
|
||||
val activeMode: AllContentMode,
|
||||
val activeSort: AllContentSort,
|
||||
val filter: String,
|
||||
val limit: Int
|
||||
) : AllContentState()
|
||||
}
|
||||
|
||||
@Immutable
|
||||
enum class AllContentTab {
|
||||
PAGES, LISTS, MEDIA, BOOKMARKS, FILES, TYPES, RELATIONS
|
||||
}
|
||||
|
||||
sealed class AllContentMode {
|
||||
data object AllContent : AllContentMode()
|
||||
data object Unlinked : AllContentMode()
|
||||
}
|
||||
|
||||
sealed class AllContentMenuMode {
|
||||
abstract val isSelected: Boolean
|
||||
|
||||
data class AllContent(
|
||||
override val isSelected: Boolean = false
|
||||
) : AllContentMenuMode()
|
||||
|
||||
data class Unlinked(
|
||||
override val isSelected: Boolean = false
|
||||
) : AllContentMenuMode()
|
||||
}
|
||||
|
||||
sealed class AllContentSort {
|
||||
abstract val relationKey: RelationKey
|
||||
abstract val sortType: DVSortType
|
||||
abstract val canGroupByDate: Boolean
|
||||
abstract val isSelected: Boolean
|
||||
|
||||
data class ByName(
|
||||
override val relationKey: RelationKey = RelationKey(Relations.NAME),
|
||||
override val sortType: DVSortType = DVSortType.ASC,
|
||||
override val canGroupByDate: Boolean = false,
|
||||
override val isSelected: Boolean = false
|
||||
) : AllContentSort()
|
||||
|
||||
data class ByDateUpdated(
|
||||
override val relationKey: RelationKey = RelationKey(Relations.LAST_MODIFIED_DATE),
|
||||
override val sortType: DVSortType = DVSortType.DESC,
|
||||
override val canGroupByDate: Boolean = true,
|
||||
override val isSelected: Boolean = false
|
||||
) : AllContentSort()
|
||||
|
||||
data class ByDateCreated(
|
||||
override val relationKey: RelationKey = RelationKey(Relations.CREATED_DATE),
|
||||
override val sortType: DVSortType = DVSortType.DESC,
|
||||
override val canGroupByDate: Boolean = true,
|
||||
override val isSelected: Boolean = false
|
||||
) : AllContentSort()
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region VIEW STATES
|
||||
|
||||
//TITLE
|
||||
sealed class UiTitleState {
|
||||
data object Hidden : UiTitleState()
|
||||
data object AllContent : UiTitleState()
|
||||
data object OnlyUnlinked : UiTitleState()
|
||||
}
|
||||
|
||||
//MENU BUTTON
|
||||
sealed class MenuButtonViewState {
|
||||
data object Hidden : MenuButtonViewState()
|
||||
data object Visible : MenuButtonViewState()
|
||||
}
|
||||
|
||||
// TABS
|
||||
@Immutable
|
||||
sealed class UiTabsState {
|
||||
data object Hidden : UiTabsState()
|
||||
|
||||
@Immutable
|
||||
data class Default(
|
||||
val tabs: List<AllContentTab>,
|
||||
val selectedTab: AllContentTab
|
||||
) : UiTabsState()
|
||||
}
|
||||
|
||||
// CONTENT
|
||||
sealed class UiContentState {
|
||||
|
||||
data object Hidden : UiContentState()
|
||||
|
||||
data object Loading : UiContentState()
|
||||
|
||||
data class Error(
|
||||
val message: String,
|
||||
) : UiContentState()
|
||||
|
||||
@Immutable
|
||||
data class Content(
|
||||
val items: List<UiContentItem>,
|
||||
) : UiContentState()
|
||||
}
|
||||
|
||||
// ITEMS
|
||||
sealed class UiContentItem {
|
||||
abstract val id: String
|
||||
|
||||
sealed class Group : UiContentItem() {
|
||||
data class Today(override val id: String = TODAY_ID) : Group()
|
||||
data class Yesterday(override val id: String = YESTERDAY_ID) : Group()
|
||||
data class Previous7Days(override val id: String = PREVIOUS_7_DAYS_ID) : Group()
|
||||
data class Previous14Days(override val id: String = PREVIOUS_14_DAYS_ID) : Group()
|
||||
data class Month(override val id: String, val title: String) : Group()
|
||||
data class MonthAndYear(override val id: String, val title: String) : Group()
|
||||
}
|
||||
|
||||
data class Item(
|
||||
override val id: String,
|
||||
val name: String,
|
||||
val space: SpaceId,
|
||||
val type: String? = null,
|
||||
val typeName: String? = null,
|
||||
val description: String? = null,
|
||||
val layout: ObjectType.Layout? = null,
|
||||
val icon: ObjectIcon = ObjectIcon.None,
|
||||
val lastModifiedDate: Long = 0L,
|
||||
val createdDate: Long = 0L,
|
||||
) : UiContentItem()
|
||||
|
||||
companion object {
|
||||
const val TODAY_ID = "TodayId"
|
||||
const val YESTERDAY_ID = "YesterdayId"
|
||||
const val PREVIOUS_7_DAYS_ID = "Previous7DaysId"
|
||||
const val PREVIOUS_14_DAYS_ID = "Previous14DaysId"
|
||||
}
|
||||
}
|
||||
|
||||
// MENU
|
||||
@Immutable
|
||||
data class UiMenuState(
|
||||
val mode: List<AllContentMenuMode>,
|
||||
val container: MenuSortsItem.Container,
|
||||
val sorts: List<MenuSortsItem.Sort>,
|
||||
val types: List<MenuSortsItem.SortType>
|
||||
) {
|
||||
companion object {
|
||||
fun empty(): UiMenuState {
|
||||
return UiMenuState(
|
||||
mode = emptyList(),
|
||||
container = MenuSortsItem.Container(AllContentSort.ByName()),
|
||||
sorts = emptyList(),
|
||||
types = emptyList()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class MenuSortsItem {
|
||||
data class Container(val sort: AllContentSort) : MenuSortsItem()
|
||||
data class Sort(val sort: AllContentSort) : MenuSortsItem()
|
||||
data object Spacer : MenuSortsItem()
|
||||
data class SortType(
|
||||
val sort: AllContentSort,
|
||||
val sortType: DVSortType,
|
||||
val isSelected: Boolean
|
||||
) : MenuSortsItem()
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region MAPPING
|
||||
fun AllContentState.Default.toMenuMode(): AllContentMenuMode {
|
||||
return when (activeMode) {
|
||||
AllContentMode.AllContent -> AllContentMenuMode.AllContent(isSelected = true)
|
||||
AllContentMode.Unlinked -> AllContentMenuMode.Unlinked(isSelected = true)
|
||||
}
|
||||
}
|
||||
|
||||
fun AllContentMode.view(): UiTitleState {
|
||||
return when (this) {
|
||||
AllContentMode.AllContent -> UiTitleState.AllContent
|
||||
AllContentMode.Unlinked -> UiTitleState.OnlyUnlinked
|
||||
}
|
||||
}
|
||||
|
||||
fun Key?.mapRelationKeyToSort(): AllContentSort {
|
||||
return when (this) {
|
||||
Relations.CREATED_DATE -> AllContentSort.ByDateCreated()
|
||||
Relations.LAST_OPENED_DATE -> AllContentSort.ByDateUpdated()
|
||||
else -> DEFAULT_INITIAL_SORT
|
||||
}
|
||||
}
|
||||
|
||||
fun List<ObjectWrapper.Basic>.toUiContentItems(
|
||||
space: SpaceId,
|
||||
urlBuilder: UrlBuilder,
|
||||
objectTypes: List<ObjectWrapper.Type>
|
||||
): List<UiContentItem.Item> {
|
||||
return map { it.toAllContentItem(space, urlBuilder, objectTypes) }
|
||||
}
|
||||
|
||||
fun ObjectWrapper.Basic.toAllContentItem(
|
||||
space: SpaceId,
|
||||
urlBuilder: UrlBuilder,
|
||||
objectTypes: List<ObjectWrapper.Type>
|
||||
): UiContentItem.Item {
|
||||
val obj = this
|
||||
val typeUrl = obj.getProperType()
|
||||
val isProfile = typeUrl == MarketplaceObjectTypeIds.PROFILE
|
||||
val layout = layout ?: ObjectType.Layout.BASIC
|
||||
return UiContentItem.Item(
|
||||
id = obj.id,
|
||||
space = space,
|
||||
name = obj.getProperName(),
|
||||
description = obj.description,
|
||||
type = typeUrl,
|
||||
typeName = objectTypes.firstOrNull { type ->
|
||||
if (isProfile) {
|
||||
type.uniqueKey == ObjectTypeUniqueKeys.PROFILE
|
||||
} else {
|
||||
type.id == typeUrl
|
||||
}
|
||||
}?.name,
|
||||
layout = layout,
|
||||
icon = ObjectIcon.from(
|
||||
obj = obj,
|
||||
layout = layout,
|
||||
builder = urlBuilder
|
||||
),
|
||||
lastModifiedDate = DateParser.parseInMillis(obj.lastModifiedDate) ?: 0L,
|
||||
createdDate = DateParser.parse(obj.getValue(Relations.CREATED_DATE)) ?: 0L
|
||||
)
|
||||
}
|
||||
//endregion
|
|
@ -0,0 +1,229 @@
|
|||
package com.anytypeio.anytype.feature_allcontent.models
|
||||
|
||||
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.Id
|
||||
import com.anytypeio.anytype.core_models.ObjectType
|
||||
import com.anytypeio.anytype.core_models.ObjectTypeUniqueKeys
|
||||
import com.anytypeio.anytype.core_models.RelationFormat
|
||||
import com.anytypeio.anytype.core_models.Relations
|
||||
import com.anytypeio.anytype.domain.library.StoreSearchParams
|
||||
|
||||
val allContentTabLayouts = mapOf(
|
||||
AllContentTab.PAGES to listOf(
|
||||
ObjectType.Layout.BASIC,
|
||||
ObjectType.Layout.PROFILE,
|
||||
ObjectType.Layout.TODO,
|
||||
ObjectType.Layout.NOTE
|
||||
),
|
||||
AllContentTab.LISTS to listOf(
|
||||
ObjectType.Layout.SET,
|
||||
ObjectType.Layout.COLLECTION
|
||||
),
|
||||
AllContentTab.FILES to listOf(
|
||||
ObjectType.Layout.FILE,
|
||||
ObjectType.Layout.PDF
|
||||
),
|
||||
AllContentTab.MEDIA to listOf(
|
||||
ObjectType.Layout.IMAGE,
|
||||
ObjectType.Layout.VIDEO,
|
||||
ObjectType.Layout.AUDIO
|
||||
),
|
||||
AllContentTab.BOOKMARKS to listOf(
|
||||
ObjectType.Layout.BOOKMARK
|
||||
)
|
||||
)
|
||||
|
||||
// Function to create subscription params
|
||||
fun createSubscriptionParams(
|
||||
spaceId: Id,
|
||||
activeMode: AllContentMode,
|
||||
activeTab: AllContentTab,
|
||||
activeSort: AllContentSort,
|
||||
limitedObjectIds: List<String>,
|
||||
limit: Int,
|
||||
subscriptionId: String
|
||||
): StoreSearchParams {
|
||||
val (filters, sorts) = activeTab.filtersForSubscribe(
|
||||
spaces = listOf(spaceId),
|
||||
activeSort = activeSort,
|
||||
limitedObjectIds = limitedObjectIds,
|
||||
activeMode = activeMode
|
||||
)
|
||||
return StoreSearchParams(
|
||||
filters = filters,
|
||||
sorts = sorts,
|
||||
keys = listOf(
|
||||
Relations.ID,
|
||||
Relations.SPACE_ID,
|
||||
Relations.TARGET_SPACE_ID,
|
||||
Relations.UNIQUE_KEY,
|
||||
Relations.NAME,
|
||||
Relations.ICON_IMAGE,
|
||||
Relations.ICON_EMOJI,
|
||||
Relations.ICON_OPTION,
|
||||
Relations.TYPE,
|
||||
Relations.LAYOUT,
|
||||
Relations.IS_ARCHIVED,
|
||||
Relations.IS_DELETED,
|
||||
Relations.IS_HIDDEN,
|
||||
Relations.SNIPPET,
|
||||
Relations.DONE,
|
||||
Relations.IDENTITY_PROFILE_LINK,
|
||||
Relations.RESTRICTIONS,
|
||||
Relations.SIZE_IN_BYTES,
|
||||
Relations.FILE_MIME_TYPE,
|
||||
Relations.FILE_EXT,
|
||||
Relations.LAST_OPENED_DATE,
|
||||
Relations.LAST_MODIFIED_DATE,
|
||||
Relations.CREATED_DATE,
|
||||
Relations.LINKS,
|
||||
Relations.BACKLINKS
|
||||
),
|
||||
limit = limit,
|
||||
subscription = subscriptionId
|
||||
)
|
||||
}
|
||||
|
||||
fun AllContentTab.filtersForSubscribe(
|
||||
spaces: List<Id>,
|
||||
activeSort: AllContentSort,
|
||||
limitedObjectIds: List<Id>,
|
||||
activeMode: AllContentMode
|
||||
): Pair<List<DVFilter>, List<DVSort>> {
|
||||
val tab = this
|
||||
when (this) {
|
||||
AllContentTab.PAGES,
|
||||
AllContentTab.LISTS,
|
||||
AllContentTab.FILES,
|
||||
AllContentTab.MEDIA,
|
||||
AllContentTab.BOOKMARKS -> {
|
||||
val filters = buildList {
|
||||
addAll(buildDeletedFilter())
|
||||
add(buildLayoutFilter(layouts = allContentTabLayouts.getValue(tab)))
|
||||
add(buildSpaceIdFilter(spaces))
|
||||
if (tab == AllContentTab.PAGES) {
|
||||
add(buildTemplateFilter())
|
||||
}
|
||||
if (limitedObjectIds.isNotEmpty()) {
|
||||
add(buildLimitedObjectIdsFilter(limitedObjectIds = limitedObjectIds))
|
||||
}
|
||||
if (activeMode == AllContentMode.Unlinked) {
|
||||
addAll(buildUnlinkedObjectFilter())
|
||||
}
|
||||
}
|
||||
val sorts = listOf(activeSort.toDVSort())
|
||||
return filters to sorts
|
||||
}
|
||||
|
||||
AllContentTab.TYPES -> TODO()
|
||||
AllContentTab.RELATIONS -> TODO()
|
||||
}
|
||||
}
|
||||
|
||||
fun AllContentTab.filtersForSearch(
|
||||
spaces: List<Id>
|
||||
): List<DVFilter> {
|
||||
val tab = this
|
||||
when (this) {
|
||||
AllContentTab.PAGES,
|
||||
AllContentTab.LISTS,
|
||||
AllContentTab.FILES,
|
||||
AllContentTab.MEDIA,
|
||||
AllContentTab.BOOKMARKS -> {
|
||||
val filters = buildList {
|
||||
addAll(buildDeletedFilter())
|
||||
add(buildLayoutFilter(layouts = allContentTabLayouts.getValue(tab)))
|
||||
add(buildSpaceIdFilter(spaces))
|
||||
if (tab == AllContentTab.PAGES) {
|
||||
add(buildTemplateFilter())
|
||||
}
|
||||
}
|
||||
return filters
|
||||
}
|
||||
|
||||
AllContentTab.TYPES -> TODO()
|
||||
AllContentTab.RELATIONS -> TODO()
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildLayoutFilter(layouts: List<ObjectType.Layout>): DVFilter = DVFilter(
|
||||
relation = Relations.LAYOUT,
|
||||
condition = DVFilterCondition.IN,
|
||||
value = layouts.map { it.code.toDouble() }
|
||||
)
|
||||
|
||||
private fun buildTemplateFilter(): DVFilter = DVFilter(
|
||||
relation = Relations.TYPE_UNIQUE_KEY,
|
||||
condition = DVFilterCondition.NOT_EQUAL,
|
||||
value = ObjectTypeUniqueKeys.TEMPLATE
|
||||
)
|
||||
|
||||
private fun buildSpaceIdFilter(spaces: List<Id>): DVFilter = DVFilter(
|
||||
relation = Relations.SPACE_ID,
|
||||
condition = DVFilterCondition.IN,
|
||||
value = spaces
|
||||
)
|
||||
|
||||
private fun buildUnlinkedObjectFilter(): List<DVFilter> = listOf(
|
||||
DVFilter(
|
||||
relation = Relations.LINKS,
|
||||
condition = DVFilterCondition.EMPTY
|
||||
),
|
||||
DVFilter(
|
||||
relation = Relations.BACKLINKS,
|
||||
condition = DVFilterCondition.EMPTY
|
||||
)
|
||||
)
|
||||
|
||||
private fun buildLimitedObjectIdsFilter(limitedObjectIds: List<Id>): DVFilter = DVFilter(
|
||||
relation = Relations.ID,
|
||||
condition = DVFilterCondition.IN,
|
||||
value = limitedObjectIds
|
||||
)
|
||||
|
||||
private fun buildDeletedFilter(): List<DVFilter> {
|
||||
return listOf(
|
||||
DVFilter(
|
||||
relation = Relations.IS_ARCHIVED,
|
||||
condition = DVFilterCondition.NOT_EQUAL,
|
||||
value = true
|
||||
),
|
||||
DVFilter(
|
||||
relation = Relations.IS_HIDDEN,
|
||||
condition = DVFilterCondition.NOT_EQUAL,
|
||||
value = true
|
||||
),
|
||||
DVFilter(
|
||||
relation = Relations.IS_DELETED,
|
||||
condition = DVFilterCondition.NOT_EQUAL,
|
||||
value = true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun AllContentSort.toDVSort(): DVSort {
|
||||
return when (this) {
|
||||
is AllContentSort.ByDateCreated -> DVSort(
|
||||
relationKey = relationKey.key,
|
||||
type = sortType,
|
||||
relationFormat = RelationFormat.DATE,
|
||||
includeTime = true,
|
||||
)
|
||||
|
||||
is AllContentSort.ByDateUpdated -> DVSort(
|
||||
relationKey = relationKey.key,
|
||||
type = sortType,
|
||||
relationFormat = RelationFormat.DATE,
|
||||
includeTime = true,
|
||||
)
|
||||
|
||||
is AllContentSort.ByName -> DVSort(
|
||||
relationKey = relationKey.key,
|
||||
type = sortType,
|
||||
relationFormat = RelationFormat.LONG_TEXT,
|
||||
includeTime = false
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,523 @@
|
|||
package com.anytypeio.anytype.feature_allcontent.presentation
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.anytypeio.anytype.analytics.base.Analytics
|
||||
import com.anytypeio.anytype.core_models.DVSortType
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.ObjectWrapper
|
||||
import com.anytypeio.anytype.core_models.Relations
|
||||
import com.anytypeio.anytype.core_models.primitives.SpaceId
|
||||
import com.anytypeio.anytype.domain.all_content.RestoreAllContentState
|
||||
import com.anytypeio.anytype.domain.all_content.UpdateAllContentState
|
||||
import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer
|
||||
import com.anytypeio.anytype.domain.misc.LocaleProvider
|
||||
import com.anytypeio.anytype.domain.misc.UrlBuilder
|
||||
import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
|
||||
import com.anytypeio.anytype.domain.objects.StoreOfRelations
|
||||
import com.anytypeio.anytype.domain.search.SearchObjects
|
||||
import com.anytypeio.anytype.feature_allcontent.models.UiContentItem
|
||||
import com.anytypeio.anytype.feature_allcontent.models.AllContentMenuMode
|
||||
import com.anytypeio.anytype.feature_allcontent.models.AllContentMode
|
||||
import com.anytypeio.anytype.feature_allcontent.models.AllContentSort
|
||||
import com.anytypeio.anytype.feature_allcontent.models.AllContentTab
|
||||
import com.anytypeio.anytype.feature_allcontent.models.UiTitleState
|
||||
import com.anytypeio.anytype.feature_allcontent.models.UiContentState
|
||||
import com.anytypeio.anytype.feature_allcontent.models.MenuButtonViewState
|
||||
import com.anytypeio.anytype.feature_allcontent.models.MenuSortsItem
|
||||
import com.anytypeio.anytype.feature_allcontent.models.UiMenuState
|
||||
import com.anytypeio.anytype.feature_allcontent.models.UiTabsState
|
||||
import com.anytypeio.anytype.feature_allcontent.models.createSubscriptionParams
|
||||
import com.anytypeio.anytype.feature_allcontent.models.filtersForSearch
|
||||
import com.anytypeio.anytype.feature_allcontent.models.mapRelationKeyToSort
|
||||
import com.anytypeio.anytype.feature_allcontent.models.toUiContentItems
|
||||
import com.anytypeio.anytype.feature_allcontent.models.view
|
||||
import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate
|
||||
import com.anytypeio.anytype.presentation.home.OpenObjectNavigation
|
||||
import com.anytypeio.anytype.presentation.home.navigation
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.ZoneId
|
||||
import java.time.format.TextStyle
|
||||
import java.time.temporal.ChronoUnit
|
||||
import javax.inject.Named
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onCompletion
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* ViewState: @see [UiContentState]
|
||||
* Factory: @see [AllContentViewModelFactory]
|
||||
* Screen: @see [com.anytypeio.anytype.feature_allcontent.ui.AllContentWrapperScreen]
|
||||
*/
|
||||
|
||||
class AllContentViewModel(
|
||||
private val vmParams: VmParams,
|
||||
private val storeOfObjectTypes: StoreOfObjectTypes,
|
||||
private val storeOfRelations: StoreOfRelations,
|
||||
private val urlBuilder: UrlBuilder,
|
||||
private val analytics: Analytics,
|
||||
private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate,
|
||||
private val storelessSubscriptionContainer: StorelessSubscriptionContainer,
|
||||
private val updateAllContentState: UpdateAllContentState,
|
||||
private val restoreAllContentState: RestoreAllContentState,
|
||||
private val searchObjects: SearchObjects,
|
||||
private val localeProvider: LocaleProvider
|
||||
) : ViewModel() {
|
||||
|
||||
private val _limitedObjectIds: MutableStateFlow<List<String>> = MutableStateFlow(emptyList())
|
||||
|
||||
private val _tabsState = MutableStateFlow<AllContentTab>(DEFAULT_INITIAL_TAB)
|
||||
private val _modeState = MutableStateFlow<AllContentMode>(DEFAULT_INITIAL_MODE)
|
||||
private val _sortState = MutableStateFlow<AllContentSort>(DEFAULT_INITIAL_SORT)
|
||||
private val _limitState = MutableStateFlow(DEFAULT_SEARCH_LIMIT)
|
||||
private val userInput = MutableStateFlow(DEFAULT_QUERY)
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
private val searchQuery = userInput
|
||||
.take(1)
|
||||
.onCompletion {
|
||||
emitAll(userInput.drop(1).debounce(DEFAULT_DEBOUNCE_DURATION).distinctUntilChanged())
|
||||
}
|
||||
|
||||
private val _uiTitleState = MutableStateFlow<UiTitleState>(UiTitleState.Hidden)
|
||||
val uiTitleState: StateFlow<UiTitleState> = _uiTitleState.asStateFlow()
|
||||
|
||||
private val _uiMenuButtonState =
|
||||
MutableStateFlow<MenuButtonViewState>(MenuButtonViewState.Hidden)
|
||||
val uiMenuButtonState: StateFlow<MenuButtonViewState> = _uiMenuButtonState.asStateFlow()
|
||||
|
||||
private val _uiTabsState = MutableStateFlow<UiTabsState>(UiTabsState.Hidden)
|
||||
val uiTabsState: StateFlow<UiTabsState> = _uiTabsState.asStateFlow()
|
||||
|
||||
private val _uiState = MutableStateFlow<UiContentState>(UiContentState.Hidden)
|
||||
val uiState: StateFlow<UiContentState> = _uiState.asStateFlow()
|
||||
|
||||
private val _uiMenu = MutableStateFlow(UiMenuState.empty())
|
||||
val uiMenu: StateFlow<UiMenuState> = _uiMenu.asStateFlow()
|
||||
|
||||
private val _commands = MutableSharedFlow<Command>()
|
||||
val commands: SharedFlow<Command> = _commands
|
||||
|
||||
init {
|
||||
Timber.d("AllContentViewModel init, spaceId:[${vmParams.spaceId.id}]")
|
||||
setupInitialStateParams()
|
||||
proceedWithUiTitleStateSetup()
|
||||
proceedWithUiTabsStateSetup()
|
||||
proceedWithUiStateSetup()
|
||||
proceedWithSearchStateSetup()
|
||||
proceedWithMenuSetup()
|
||||
}
|
||||
|
||||
private fun proceedWithUiTitleStateSetup() {
|
||||
viewModelScope.launch {
|
||||
_modeState.collectLatest { result ->
|
||||
Timber.d("New mode: [$result]")
|
||||
_uiTitleState.value = result.view()
|
||||
_uiMenuButtonState.value = MenuButtonViewState.Visible
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedWithUiTabsStateSetup() {
|
||||
viewModelScope.launch {
|
||||
_tabsState.collectLatest { result ->
|
||||
Timber.d("New tab: [$result]")
|
||||
_uiTabsState.value = UiTabsState.Default(
|
||||
tabs = AllContentTab.entries,
|
||||
selectedTab = result
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupInitialStateParams() {
|
||||
viewModelScope.launch {
|
||||
if (vmParams.useHistory) {
|
||||
runCatching {
|
||||
val initialParams = restoreAllContentState.run(
|
||||
RestoreAllContentState.Params(vmParams.spaceId)
|
||||
)
|
||||
if (initialParams.activeSort != null) {
|
||||
_sortState.value = initialParams.activeSort.mapRelationKeyToSort()
|
||||
}
|
||||
}.onFailure { e ->
|
||||
Timber.e(e, "Error restoring state")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedWithSearchStateSetup() {
|
||||
viewModelScope.launch {
|
||||
searchQuery.collectLatest { query ->
|
||||
Timber.d("New query: [$query]")
|
||||
if (query.isBlank()) {
|
||||
_limitedObjectIds.value = emptyList()
|
||||
} else {
|
||||
val searchParams = createSearchParams(
|
||||
activeTab = _tabsState.value,
|
||||
activeQuery = query
|
||||
)
|
||||
searchObjects(searchParams).process(
|
||||
success = { searchResults ->
|
||||
Timber.d("Search objects by query:[$query], size: : ${searchResults.size}")
|
||||
_limitedObjectIds.value = searchResults.map { it.id }
|
||||
},
|
||||
failure = {
|
||||
Timber.e(it, "Error searching objects by query")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun proceedWithUiStateSetup() {
|
||||
viewModelScope.launch {
|
||||
combine(
|
||||
_modeState,
|
||||
_tabsState,
|
||||
_sortState,
|
||||
_limitedObjectIds,
|
||||
_limitState
|
||||
) { mode, tab, sort, limitedObjectIds, limit ->
|
||||
Result(mode, tab, sort, limitedObjectIds, limit)
|
||||
}
|
||||
.flatMapLatest { currentState ->
|
||||
Timber.d("AllContentNewState:$currentState, restart subscription")
|
||||
loadData(currentState)
|
||||
}.collect {
|
||||
_uiState.value = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun subscriptionId() = "all_content_subscription_${vmParams.spaceId.id}"
|
||||
|
||||
private fun loadData(
|
||||
result: Result
|
||||
): Flow<UiContentState> = flow {
|
||||
val loadingStartTime = System.currentTimeMillis()
|
||||
|
||||
emit(UiContentState.Loading)
|
||||
|
||||
val searchParams = createSubscriptionParams(
|
||||
activeTab = result.tab,
|
||||
activeSort = result.sort,
|
||||
limitedObjectIds = result.limitedObjectIds,
|
||||
limit = result.limit,
|
||||
subscriptionId = subscriptionId(),
|
||||
spaceId = vmParams.spaceId.id,
|
||||
activeMode = result.mode
|
||||
)
|
||||
|
||||
val dataFlow = storelessSubscriptionContainer.subscribe(searchParams)
|
||||
.map { objWrappers ->
|
||||
val items = mapToUiContentItems(
|
||||
objectWrappers = objWrappers,
|
||||
activeSort = result.sort
|
||||
)
|
||||
UiContentState.Content(items = items)
|
||||
}
|
||||
.catch { e ->
|
||||
emit(
|
||||
UiContentState.Error(
|
||||
message = e.message ?: "Error loading objects by subscription"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
var isFirstEmission = true
|
||||
|
||||
emitAll(
|
||||
dataFlow.onEach {
|
||||
if (isFirstEmission) {
|
||||
val elapsedTime = System.currentTimeMillis() - loadingStartTime
|
||||
if (elapsedTime < DEFAULT_LOADING_DELAY) {
|
||||
delay(DEFAULT_LOADING_DELAY - elapsedTime)
|
||||
}
|
||||
isFirstEmission = false
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun mapToUiContentItems(
|
||||
objectWrappers: List<ObjectWrapper.Basic>,
|
||||
activeSort: AllContentSort
|
||||
): List<UiContentItem> {
|
||||
val items = objectWrappers.toUiContentItems(
|
||||
space = vmParams.spaceId,
|
||||
urlBuilder = urlBuilder,
|
||||
objectTypes = storeOfObjectTypes.getAll()
|
||||
)
|
||||
return if (activeSort.canGroupByDate) {
|
||||
groupItemsByDate(
|
||||
items = items,
|
||||
activeSort = activeSort
|
||||
)
|
||||
} else {
|
||||
items
|
||||
}
|
||||
}
|
||||
|
||||
private fun groupItemsByDate(
|
||||
items: List<UiContentItem.Item>,
|
||||
activeSort: AllContentSort
|
||||
): List<UiContentItem> {
|
||||
val groupedItems = mutableListOf<UiContentItem>()
|
||||
var currentGroupKey: String? = null
|
||||
|
||||
for (item in items) {
|
||||
val timestamp = when (activeSort) {
|
||||
is AllContentSort.ByDateCreated -> item.createdDate
|
||||
is AllContentSort.ByDateUpdated -> item.lastModifiedDate
|
||||
is AllContentSort.ByName -> 0L
|
||||
}
|
||||
val (groupKey, group) = getDateGroup(timestamp)
|
||||
|
||||
if (currentGroupKey != groupKey) {
|
||||
groupedItems.add(group)
|
||||
currentGroupKey = groupKey
|
||||
}
|
||||
|
||||
groupedItems.add(item)
|
||||
}
|
||||
|
||||
return groupedItems
|
||||
}
|
||||
|
||||
private fun getDateGroup(timestamp: Long): Pair<String, UiContentItem.Group> {
|
||||
val zoneId = ZoneId.systemDefault()
|
||||
val itemDate = Instant.ofEpochSecond(timestamp)
|
||||
.atZone(zoneId)
|
||||
.toLocalDate()
|
||||
val today = LocalDate.now(zoneId)
|
||||
val daysAgo = ChronoUnit.DAYS.between(itemDate, today)
|
||||
val todayGroup = UiContentItem.Group.Today()
|
||||
val yesterdayGroup = UiContentItem.Group.Yesterday()
|
||||
val previous7DaysGroup = UiContentItem.Group.Previous7Days()
|
||||
val previous14DaysGroup = UiContentItem.Group.Previous14Days()
|
||||
return when {
|
||||
daysAgo == 0L -> todayGroup.id to todayGroup
|
||||
daysAgo == 1L -> yesterdayGroup.id to yesterdayGroup
|
||||
daysAgo in 2..7 -> previous7DaysGroup.id to previous7DaysGroup
|
||||
daysAgo in 8..14 -> previous14DaysGroup.id to previous14DaysGroup
|
||||
itemDate.year == today.year -> {
|
||||
val monthName =
|
||||
itemDate.month.getDisplayName(TextStyle.FULL, localeProvider.locale())
|
||||
monthName to UiContentItem.Group.Month(id = monthName, title = monthName)
|
||||
}
|
||||
|
||||
else -> {
|
||||
val monthAndYear = "${
|
||||
itemDate.month.getDisplayName(
|
||||
TextStyle.FULL,
|
||||
localeProvider.locale()
|
||||
)
|
||||
} ${itemDate.year}"
|
||||
monthAndYear to UiContentItem.Group.MonthAndYear(
|
||||
id = monthAndYear,
|
||||
title = monthAndYear
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Function to create search parameters
|
||||
private fun createSearchParams(
|
||||
activeTab: AllContentTab,
|
||||
activeQuery: String,
|
||||
): SearchObjects.Params {
|
||||
val filters = activeTab.filtersForSearch(
|
||||
spaces = listOf(vmParams.spaceId.id)
|
||||
)
|
||||
return SearchObjects.Params(
|
||||
filters = filters,
|
||||
keys = listOf(Relations.ID),
|
||||
fulltext = activeQuery
|
||||
)
|
||||
}
|
||||
|
||||
// Function to get the menu mode based on the active mode
|
||||
private fun getMenuMode(mode: AllContentMode): AllContentMenuMode {
|
||||
return when (mode) {
|
||||
AllContentMode.AllContent -> AllContentMenuMode.AllContent(isSelected = true)
|
||||
AllContentMode.Unlinked -> AllContentMenuMode.Unlinked(isSelected = true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedWithMenuSetup() {
|
||||
viewModelScope.launch {
|
||||
combine(
|
||||
_modeState,
|
||||
_sortState
|
||||
) { mode, sort ->
|
||||
mode to sort
|
||||
}.collectLatest { (mode, sort) ->
|
||||
val uiMode = listOf(
|
||||
AllContentMenuMode.AllContent(isSelected = mode == AllContentMode.AllContent),
|
||||
AllContentMenuMode.Unlinked(isSelected = mode == AllContentMode.Unlinked)
|
||||
)
|
||||
val container = MenuSortsItem.Container(sort = sort)
|
||||
val uiSorts = listOf(
|
||||
MenuSortsItem.Sort(
|
||||
sort = AllContentSort.ByName(isSelected = sort is AllContentSort.ByName)
|
||||
),
|
||||
MenuSortsItem.Sort(
|
||||
sort = AllContentSort.ByDateUpdated(isSelected = sort is AllContentSort.ByDateUpdated)
|
||||
),
|
||||
MenuSortsItem.Sort(
|
||||
sort = AllContentSort.ByDateCreated(isSelected = sort is AllContentSort.ByDateCreated)
|
||||
)
|
||||
)
|
||||
val uiSortTypes = listOf(
|
||||
MenuSortsItem.SortType(
|
||||
sort = sort,
|
||||
sortType = DVSortType.ASC,
|
||||
isSelected = sort.sortType == DVSortType.ASC
|
||||
),
|
||||
MenuSortsItem.SortType(
|
||||
sort = sort,
|
||||
sortType = DVSortType.DESC,
|
||||
isSelected = sort.sortType == DVSortType.DESC
|
||||
)
|
||||
)
|
||||
_uiMenu.value = UiMenuState(
|
||||
mode = uiMode,
|
||||
container = container,
|
||||
sorts = uiSorts,
|
||||
types = uiSortTypes
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onTabClicked(tab: AllContentTab) {
|
||||
Timber.d("onTabClicked: $tab")
|
||||
if (tab == AllContentTab.TYPES || tab == AllContentTab.RELATIONS) {
|
||||
viewModelScope.launch {
|
||||
_commands.emit(Command.SendToast("Not implemented yet"))
|
||||
}
|
||||
return
|
||||
}
|
||||
_tabsState.value = tab
|
||||
}
|
||||
|
||||
fun onAllContentModeClicked(mode: AllContentMenuMode) {
|
||||
Timber.d("onAllContentModeClicked: $mode")
|
||||
_modeState.value = when (mode) {
|
||||
is AllContentMenuMode.AllContent -> AllContentMode.AllContent
|
||||
is AllContentMenuMode.Unlinked -> AllContentMode.Unlinked
|
||||
}
|
||||
}
|
||||
|
||||
fun onSortClicked(sort: AllContentSort) {
|
||||
Timber.d("onSortClicked: $sort")
|
||||
_sortState.value = sort
|
||||
}
|
||||
|
||||
fun onFilterChanged(filter: String) {
|
||||
Timber.d("onFilterChanged: $filter")
|
||||
userInput.value = filter
|
||||
}
|
||||
|
||||
fun onLimitUpdated(limit: Int) {
|
||||
Timber.d("onLimitUpdated: $limit")
|
||||
_limitState.value = limit
|
||||
}
|
||||
|
||||
fun onItemClicked(item: UiContentItem.Item) {
|
||||
Timber.d("onItemClicked: ${item.id}")
|
||||
val layout = item.layout ?: return
|
||||
viewModelScope.launch {
|
||||
when (val navigation = layout.navigation(
|
||||
target = item.id,
|
||||
space = vmParams.spaceId.id
|
||||
)) {
|
||||
is OpenObjectNavigation.OpenDataView -> {
|
||||
_commands.emit(
|
||||
Command.NavigateToSetOrCollection(
|
||||
id = navigation.target,
|
||||
space = navigation.space
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
is OpenObjectNavigation.OpenEditor -> {
|
||||
_commands.emit(
|
||||
Command.NavigateToEditor(
|
||||
id = navigation.target,
|
||||
space = navigation.space
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
is OpenObjectNavigation.UnexpectedLayoutError -> {
|
||||
_commands.emit(Command.SendToast("Unexpected layout: ${navigation.layout}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onStop() {
|
||||
Timber.d("onStop")
|
||||
viewModelScope.launch {
|
||||
storelessSubscriptionContainer.unsubscribe(listOf(subscriptionId()))
|
||||
}
|
||||
}
|
||||
|
||||
data class VmParams(
|
||||
val spaceId: SpaceId,
|
||||
val useHistory: Boolean = true
|
||||
)
|
||||
|
||||
internal data class Result(
|
||||
val mode: AllContentMode,
|
||||
val tab: AllContentTab,
|
||||
val sort: AllContentSort,
|
||||
val limitedObjectIds: List<String>,
|
||||
val limit: Int
|
||||
)
|
||||
|
||||
sealed class Command {
|
||||
data class NavigateToEditor(val id: Id, val space: Id) : Command()
|
||||
data class NavigateToSetOrCollection(val id: Id, val space: Id) : Command()
|
||||
data class SendToast(val message: String) : Command()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val DEFAULT_DEBOUNCE_DURATION = 300L
|
||||
const val DEFAULT_LOADING_DELAY = 250L
|
||||
|
||||
//INITIAL STATE
|
||||
const val DEFAULT_SEARCH_LIMIT = 50
|
||||
val DEFAULT_INITIAL_TAB = AllContentTab.PAGES
|
||||
val DEFAULT_INITIAL_SORT = AllContentSort.ByDateCreated()
|
||||
val DEFAULT_INITIAL_MODE = AllContentMode.AllContent
|
||||
val DEFAULT_QUERY = ""
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package com.anytypeio.anytype.feature_allcontent.presentation
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.anytypeio.anytype.analytics.base.Analytics
|
||||
import com.anytypeio.anytype.domain.all_content.RestoreAllContentState
|
||||
import com.anytypeio.anytype.domain.all_content.UpdateAllContentState
|
||||
import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer
|
||||
import com.anytypeio.anytype.domain.misc.LocaleProvider
|
||||
import com.anytypeio.anytype.domain.misc.UrlBuilder
|
||||
import com.anytypeio.anytype.domain.objects.StoreOfObjectTypes
|
||||
import com.anytypeio.anytype.domain.objects.StoreOfRelations
|
||||
import com.anytypeio.anytype.domain.search.SearchObjects
|
||||
import com.anytypeio.anytype.feature_allcontent.presentation.AllContentViewModel.VmParams
|
||||
import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
||||
class AllContentViewModelFactory @Inject constructor(
|
||||
private val vmParams: VmParams,
|
||||
private val storeOfObjectTypes: StoreOfObjectTypes,
|
||||
private val storeOfRelations: StoreOfRelations,
|
||||
private val urlBuilder: UrlBuilder,
|
||||
private val analytics: Analytics,
|
||||
private val analyticSpaceHelperDelegate: AnalyticSpaceHelperDelegate,
|
||||
private val storelessSubscriptionContainer: StorelessSubscriptionContainer,
|
||||
private val updateAllContentState: UpdateAllContentState,
|
||||
private val restoreAllContentState: RestoreAllContentState,
|
||||
private val searchObjects: SearchObjects,
|
||||
private val localeProvider: LocaleProvider
|
||||
) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T =
|
||||
AllContentViewModel(
|
||||
vmParams = vmParams,
|
||||
storeOfObjectTypes = storeOfObjectTypes,
|
||||
storeOfRelations = storeOfRelations,
|
||||
urlBuilder = urlBuilder,
|
||||
analytics = analytics,
|
||||
analyticSpaceHelperDelegate = analyticSpaceHelperDelegate,
|
||||
storelessSubscriptionContainer = storelessSubscriptionContainer,
|
||||
restoreAllContentState = restoreAllContentState,
|
||||
updateAllContentState = updateAllContentState,
|
||||
searchObjects = searchObjects,
|
||||
localeProvider = localeProvider
|
||||
) as T
|
||||
}
|
|
@ -0,0 +1,238 @@
|
|||
package com.anytypeio.anytype.feature_allcontent.ui
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.ColorFilter.Companion.tint
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.anytypeio.anytype.core_models.DVSortType
|
||||
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
|
||||
import com.anytypeio.anytype.core_ui.views.BodyCalloutRegular
|
||||
import com.anytypeio.anytype.core_ui.views.UXBody
|
||||
import com.anytypeio.anytype.feature_allcontent.R
|
||||
import com.anytypeio.anytype.feature_allcontent.models.AllContentMenuMode
|
||||
import com.anytypeio.anytype.feature_allcontent.models.AllContentSort
|
||||
import com.anytypeio.anytype.feature_allcontent.models.MenuSortsItem
|
||||
import com.anytypeio.anytype.feature_allcontent.models.UiMenuState
|
||||
|
||||
@Composable
|
||||
fun AllContentMenu(
|
||||
uiMenuState: UiMenuState,
|
||||
onModeClick: (AllContentMenuMode) -> Unit,
|
||||
onSortClick: (AllContentSort) -> Unit
|
||||
) {
|
||||
var sortingExpanded by remember { mutableStateOf(false) }
|
||||
|
||||
uiMenuState.mode.forEach { item ->
|
||||
MenuItem(
|
||||
title = getModeTitle(item),
|
||||
isSelected = item.isSelected,
|
||||
modifier = Modifier.clickable {
|
||||
onModeClick(item)
|
||||
}
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
SortingBox(
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
sortingExpanded = !sortingExpanded
|
||||
},
|
||||
subtitle = uiMenuState.container.sort.title()
|
||||
)
|
||||
if (sortingExpanded) {
|
||||
uiMenuState.sorts.forEach { item ->
|
||||
MenuItem(
|
||||
title = item.sort.title(),
|
||||
isSelected = item.sort.isSelected,
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
onSortClick(item.sort)
|
||||
}
|
||||
)
|
||||
}
|
||||
uiMenuState.types.forEach { item ->
|
||||
MenuItem(
|
||||
title = item.sortType.title(item.sort),
|
||||
isSelected = item.isSelected,
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
val updatedSort = when (item.sort) {
|
||||
is AllContentSort.ByName -> item.sort.copy(sortType = item.sortType)
|
||||
is AllContentSort.ByDateCreated -> item.sort.copy(sortType = item.sortType)
|
||||
is AllContentSort.ByDateUpdated -> item.sort.copy(sortType = item.sortType)
|
||||
}
|
||||
onSortClick(updatedSort)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SortingBox(modifier: Modifier, subtitle: String) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.background(colorResource(id = R.color.background_secondary)),
|
||||
verticalAlignment = CenterVertically
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier.size(32.dp),
|
||||
painter = painterResource(R.drawable.ic_menu_arrow_right),
|
||||
contentDescription = "",
|
||||
colorFilter = tint(colorResource(id = R.color.glyph_selected))
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.wrapContentHeight()
|
||||
.padding(top = 11.dp, bottom = 10.dp, start = 6.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.all_content_sort_by),
|
||||
modifier = Modifier.wrapContentSize(),
|
||||
style = UXBody,
|
||||
color = colorResource(id = R.color.text_primary)
|
||||
)
|
||||
Text(
|
||||
text = subtitle,
|
||||
modifier = Modifier.wrapContentSize(),
|
||||
style = BodyCalloutRegular,
|
||||
color = colorResource(id = R.color.text_secondary)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MenuItem(modifier: Modifier, title: String, isSelected: Boolean) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(44.dp)
|
||||
.background(colorResource(id = R.color.background_secondary)),
|
||||
verticalAlignment = CenterVertically,
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 12.dp),
|
||||
painter = painterResource(R.drawable.ic_check_16),
|
||||
contentDescription = "All Content mode selected",
|
||||
alpha = if (isSelected) 1f else 0f
|
||||
)
|
||||
Text(
|
||||
text = title,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp),
|
||||
style = UXBody,
|
||||
color = colorResource(id = R.color.text_primary)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//region RESOURCES
|
||||
@Composable
|
||||
private fun getModeTitle(mode: AllContentMenuMode): String = stringResource(
|
||||
when (mode) {
|
||||
is AllContentMenuMode.AllContent -> R.string.all_content_title_all_content
|
||||
is AllContentMenuMode.Unlinked -> R.string.all_content_title_only_unlinked
|
||||
}
|
||||
)
|
||||
|
||||
@Composable
|
||||
private fun AllContentSort.title(): String = stringResource(
|
||||
when (this) {
|
||||
is AllContentSort.ByDateCreated -> R.string.all_content_sort_date_created
|
||||
is AllContentSort.ByDateUpdated -> R.string.all_content_sort_date_updated
|
||||
is AllContentSort.ByName -> R.string.all_content_sort_name
|
||||
}
|
||||
)
|
||||
|
||||
@Composable
|
||||
private fun DVSortType.title(sort: AllContentSort): String = when (this) {
|
||||
DVSortType.ASC -> {
|
||||
when (sort) {
|
||||
is AllContentSort.ByDateCreated, is AllContentSort.ByDateUpdated -> stringResource(
|
||||
id = R.string.all_content_sort_date_asc
|
||||
)
|
||||
|
||||
is AllContentSort.ByName -> stringResource(id = R.string.all_content_sort_name_asc)
|
||||
}
|
||||
}
|
||||
|
||||
DVSortType.DESC -> {
|
||||
when (sort) {
|
||||
is AllContentSort.ByDateCreated, is AllContentSort.ByDateUpdated -> stringResource(
|
||||
id = R.string.all_content_sort_date_desc
|
||||
)
|
||||
|
||||
is AllContentSort.ByName -> stringResource(id = R.string.all_content_sort_name_desc)
|
||||
}
|
||||
}
|
||||
|
||||
DVSortType.CUSTOM -> ""
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region PREVIEW
|
||||
@DefaultPreviews
|
||||
@Composable
|
||||
fun AllContentMenuPreview() {
|
||||
AllContentMenu(
|
||||
uiMenuState = UiMenuState(
|
||||
mode = listOf(
|
||||
AllContentMenuMode.AllContent(isSelected = true),
|
||||
AllContentMenuMode.Unlinked(isSelected = false)
|
||||
),
|
||||
sorts = listOf(
|
||||
MenuSortsItem.Sort(
|
||||
sort = AllContentSort.ByName(isSelected = true)
|
||||
),
|
||||
MenuSortsItem.Sort(
|
||||
AllContentSort.ByDateUpdated(isSelected = false)
|
||||
),
|
||||
MenuSortsItem.Sort(
|
||||
AllContentSort.ByDateCreated(isSelected = false)
|
||||
)
|
||||
),
|
||||
types = listOf(
|
||||
MenuSortsItem.SortType(
|
||||
sortType = DVSortType.ASC,
|
||||
isSelected = true,
|
||||
sort = AllContentSort.ByName(isSelected = true)
|
||||
),
|
||||
MenuSortsItem.SortType(
|
||||
sortType = DVSortType.DESC,
|
||||
isSelected = false,
|
||||
sort = AllContentSort.ByName(isSelected = false)
|
||||
),
|
||||
),
|
||||
container = MenuSortsItem.Container(AllContentSort.ByName())
|
||||
),
|
||||
onModeClick = {},
|
||||
onSortClick = {}
|
||||
)
|
||||
}
|
||||
//endregion
|
|
@ -0,0 +1,417 @@
|
|||
package com.anytypeio.anytype.feature_allcontent.ui
|
||||
|
||||
import android.os.Build
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.statusBars
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material3.FabPosition
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.anytypeio.anytype.core_models.DVSortType
|
||||
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
|
||||
import com.anytypeio.anytype.core_ui.foundation.Divider
|
||||
import com.anytypeio.anytype.core_ui.views.ButtonSize
|
||||
import com.anytypeio.anytype.core_ui.views.Caption1Regular
|
||||
import com.anytypeio.anytype.core_ui.views.PreviewTitle2Medium
|
||||
import com.anytypeio.anytype.core_ui.views.Relations3
|
||||
import com.anytypeio.anytype.core_ui.views.UXBody
|
||||
import com.anytypeio.anytype.core_ui.views.animations.DotsLoadingIndicator
|
||||
import com.anytypeio.anytype.core_ui.views.animations.FadeAnimationSpecs
|
||||
import com.anytypeio.anytype.core_ui.widgets.DefaultBasicAvatarIcon
|
||||
import com.anytypeio.anytype.core_ui.widgets.DefaultEmojiObjectIcon
|
||||
import com.anytypeio.anytype.core_ui.widgets.DefaultFileObjectImageIcon
|
||||
import com.anytypeio.anytype.core_ui.widgets.DefaultObjectBookmarkIcon
|
||||
import com.anytypeio.anytype.core_ui.widgets.DefaultObjectImageIcon
|
||||
import com.anytypeio.anytype.core_ui.widgets.DefaultProfileAvatarIcon
|
||||
import com.anytypeio.anytype.core_ui.widgets.DefaultProfileIconImage
|
||||
import com.anytypeio.anytype.core_ui.widgets.DefaultTaskObjectIcon
|
||||
import com.anytypeio.anytype.core_utils.insets.EDGE_TO_EDGE_MIN_SDK
|
||||
import com.anytypeio.anytype.feature_allcontent.BuildConfig
|
||||
import com.anytypeio.anytype.feature_allcontent.R
|
||||
import com.anytypeio.anytype.feature_allcontent.models.AllContentMenuMode
|
||||
import com.anytypeio.anytype.feature_allcontent.models.AllContentSort
|
||||
import com.anytypeio.anytype.feature_allcontent.models.AllContentTab
|
||||
import com.anytypeio.anytype.feature_allcontent.models.UiContentState
|
||||
import com.anytypeio.anytype.feature_allcontent.models.MenuButtonViewState
|
||||
import com.anytypeio.anytype.feature_allcontent.models.UiContentItem
|
||||
import com.anytypeio.anytype.feature_allcontent.models.UiMenuState
|
||||
import com.anytypeio.anytype.feature_allcontent.models.UiTabsState
|
||||
import com.anytypeio.anytype.feature_allcontent.models.UiTitleState
|
||||
import com.anytypeio.anytype.presentation.objects.ObjectIcon
|
||||
|
||||
@Composable
|
||||
fun AllContentWrapperScreen(
|
||||
uiTitleState: UiTitleState,
|
||||
uiTabsState: UiTabsState,
|
||||
uiMenuButtonViewState: MenuButtonViewState,
|
||||
uiMenuState: UiMenuState,
|
||||
uiState: UiContentState,
|
||||
onTabClick: (AllContentTab) -> Unit,
|
||||
onQueryChanged: (String) -> Unit,
|
||||
onModeClick: (AllContentMenuMode) -> Unit,
|
||||
onSortClick: (AllContentSort) -> Unit,
|
||||
onItemClicked: (UiContentItem.Item) -> Unit
|
||||
) {
|
||||
val objects = remember { mutableStateOf<List<UiContentItem>>(emptyList()) }
|
||||
if (uiState is UiContentState.Content) {
|
||||
objects.value = uiState.items
|
||||
}
|
||||
AllContentMainScreen(
|
||||
uiTitleState = uiTitleState,
|
||||
uiTabsState = uiTabsState,
|
||||
uiMenuButtonViewState = uiMenuButtonViewState,
|
||||
onTabClick = onTabClick,
|
||||
objects = objects,
|
||||
isLoading = uiState is UiContentState.Loading,
|
||||
onQueryChanged = onQueryChanged,
|
||||
uiMenuState = uiMenuState,
|
||||
onModeClick = onModeClick,
|
||||
onSortClick = onSortClick,
|
||||
onItemClicked = onItemClicked
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AllContentMainScreen(
|
||||
uiTitleState: UiTitleState,
|
||||
uiTabsState: UiTabsState,
|
||||
uiMenuButtonViewState: MenuButtonViewState,
|
||||
uiMenuState: UiMenuState,
|
||||
objects: MutableState<List<UiContentItem>>,
|
||||
onTabClick: (AllContentTab) -> Unit,
|
||||
onQueryChanged: (String) -> Unit,
|
||||
onModeClick: (AllContentMenuMode) -> Unit,
|
||||
onSortClick: (AllContentSort) -> Unit,
|
||||
isLoading: Boolean,
|
||||
onItemClicked: (UiContentItem.Item) -> Unit
|
||||
) {
|
||||
val modifier = Modifier
|
||||
.background(color = colorResource(id = R.color.background_primary))
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier
|
||||
.fillMaxSize(),
|
||||
containerColor = colorResource(id = R.color.background_primary),
|
||||
topBar = {
|
||||
Column(
|
||||
modifier = if (BuildConfig.USE_EDGE_TO_EDGE && Build.VERSION.SDK_INT >= EDGE_TO_EDGE_MIN_SDK)
|
||||
Modifier
|
||||
.windowInsetsPadding(WindowInsets.statusBars)
|
||||
.fillMaxWidth()
|
||||
else
|
||||
Modifier.fillMaxWidth()
|
||||
) {
|
||||
if (uiTitleState !is UiTitleState.Hidden) {
|
||||
AllContentTopBarContainer(
|
||||
titleState = uiTitleState,
|
||||
menuButtonState = uiMenuButtonViewState,
|
||||
uiMenuState = uiMenuState,
|
||||
onSortClick = onSortClick,
|
||||
onModeClick = onModeClick,
|
||||
)
|
||||
}
|
||||
|
||||
if (uiTabsState is UiTabsState.Default) {
|
||||
AllContentTabs(tabsViewState = uiTabsState) { tab ->
|
||||
onTabClick(tab)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.size(10.dp))
|
||||
AllContentSearchBar(onQueryChanged)
|
||||
Spacer(modifier = Modifier.size(10.dp))
|
||||
Divider(paddingStart = 0.dp, paddingEnd = 0.dp)
|
||||
}
|
||||
},
|
||||
content = { paddingValues ->
|
||||
val contentModifier =
|
||||
if (BuildConfig.USE_EDGE_TO_EDGE && Build.VERSION.SDK_INT >= EDGE_TO_EDGE_MIN_SDK)
|
||||
Modifier
|
||||
.windowInsetsPadding(WindowInsets.navigationBars)
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
else
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
if (isLoading) {
|
||||
Box(modifier = contentModifier) {
|
||||
LoadingState()
|
||||
}
|
||||
} else {
|
||||
ContentItems(
|
||||
modifier = contentModifier,
|
||||
items = objects.value,
|
||||
onItemClicked = onItemClicked
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ContentItems(
|
||||
modifier: Modifier,
|
||||
items: List<UiContentItem>,
|
||||
onItemClicked: (UiContentItem.Item) -> Unit
|
||||
) {
|
||||
LazyColumn(modifier = modifier) {
|
||||
items(
|
||||
count = items.size,
|
||||
key = { index -> items[index].id },
|
||||
contentType = { index ->
|
||||
when (items[index]) {
|
||||
is UiContentItem.Group -> "group"
|
||||
is UiContentItem.Item -> "item"
|
||||
}
|
||||
}
|
||||
) { index ->
|
||||
when (val item = items[index]) {
|
||||
is UiContentItem.Group -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(52.dp),
|
||||
contentAlignment = Alignment.BottomStart
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 20.dp, bottom = 8.dp),
|
||||
text = item.title(),
|
||||
style = Caption1Regular,
|
||||
color = colorResource(id = R.color.text_secondary),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is UiContentItem.Item -> {
|
||||
Item(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.bottomBorder()
|
||||
.animateItem()
|
||||
.clickable {
|
||||
onItemClicked(item)
|
||||
},
|
||||
item = item
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BoxScope.LoadingState() {
|
||||
val loadingAlpha by animateFloatAsState(targetValue = 1f, label = "")
|
||||
DotsLoadingIndicator(
|
||||
animating = true,
|
||||
modifier = Modifier
|
||||
.graphicsLayer { alpha = loadingAlpha }
|
||||
.align(Alignment.Center),
|
||||
animationSpecs = FadeAnimationSpecs(itemCount = 3),
|
||||
color = colorResource(id = R.color.glyph_active),
|
||||
size = ButtonSize.Small
|
||||
)
|
||||
}
|
||||
|
||||
@DefaultPreviews
|
||||
@Composable
|
||||
fun PreviewLoadingState() {
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
LoadingState()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Item(
|
||||
modifier: Modifier,
|
||||
item: UiContentItem.Item
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth()
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(0.dp, 12.dp, 12.dp, 12.dp)
|
||||
.size(48.dp)
|
||||
.align(CenterVertically)
|
||||
) {
|
||||
AllContentItemIcon(icon = item.icon, modifier = Modifier)
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.align(CenterVertically)
|
||||
.padding(0.dp, 0.dp, 60.dp, 0.dp)
|
||||
) {
|
||||
|
||||
val name = item.name.trim().ifBlank { stringResource(R.string.untitled) }
|
||||
|
||||
Text(
|
||||
text = name,
|
||||
style = PreviewTitle2Medium,
|
||||
color = colorResource(id = R.color.text_primary),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
|
||||
val description = item.description
|
||||
if (!description.isNullOrBlank()) {
|
||||
Text(
|
||||
text = description,
|
||||
style = Relations3,
|
||||
color = colorResource(id = R.color.text_primary),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
|
||||
val typeName = item.typeName
|
||||
if (!typeName.isNullOrBlank()) {
|
||||
Text(
|
||||
text = typeName,
|
||||
style = Relations3,
|
||||
color = colorResource(id = R.color.text_secondary),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AllContentItemIcon(
|
||||
icon: ObjectIcon,
|
||||
modifier: Modifier,
|
||||
iconSize: Dp = 48.dp,
|
||||
onTaskIconClicked: (Boolean) -> Unit = {},
|
||||
avatarBackgroundColor: Int = R.color.shape_secondary,
|
||||
avatarFontSize: TextUnit = 28.sp,
|
||||
avatarTextStyle: TextStyle = TextStyle(
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = colorResource(id = R.color.text_white)
|
||||
)
|
||||
) {
|
||||
when (icon) {
|
||||
is ObjectIcon.Profile.Avatar -> DefaultProfileAvatarIcon(
|
||||
modifier = modifier,
|
||||
iconSize = iconSize,
|
||||
icon = icon,
|
||||
avatarTextStyle = avatarTextStyle,
|
||||
avatarFontSize = avatarFontSize,
|
||||
avatarBackgroundColor = avatarBackgroundColor
|
||||
)
|
||||
|
||||
is ObjectIcon.Profile.Image -> DefaultProfileIconImage(icon, modifier, iconSize)
|
||||
is ObjectIcon.Basic.Emoji -> DefaultEmojiObjectIcon(modifier, iconSize, icon)
|
||||
is ObjectIcon.Basic.Image -> DefaultObjectImageIcon(icon.hash, modifier, iconSize)
|
||||
is ObjectIcon.Basic.Avatar -> DefaultBasicAvatarIcon(modifier, iconSize, icon)
|
||||
is ObjectIcon.Bookmark -> DefaultObjectBookmarkIcon(icon.image, modifier, iconSize)
|
||||
is ObjectIcon.Task -> DefaultTaskObjectIcon(modifier, iconSize, icon, onTaskIconClicked)
|
||||
is ObjectIcon.File -> {
|
||||
DefaultFileObjectImageIcon(
|
||||
fileName = icon.fileName.orEmpty(),
|
||||
mime = icon.mime.orEmpty(),
|
||||
modifier = modifier,
|
||||
iconSize = iconSize,
|
||||
extension = icon.extensions
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
// Draw nothing.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BoxScope.ErrorState(message: String) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.align(Alignment.Center),
|
||||
text = "Error : message",
|
||||
color = colorResource(id = R.color.palette_system_red),
|
||||
style = UXBody
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UiContentItem.Group.title(): String {
|
||||
return when (this) {
|
||||
is UiContentItem.Group.Today -> stringResource(R.string.allContent_group_today)
|
||||
is UiContentItem.Group.Yesterday -> stringResource(R.string.allContent_group_yesterday)
|
||||
is UiContentItem.Group.Previous7Days -> stringResource(R.string.allContent_group_prev_7)
|
||||
is UiContentItem.Group.Previous14Days -> stringResource(R.string.allContent_group_prev_14)
|
||||
is UiContentItem.Group.Month -> title
|
||||
is UiContentItem.Group.MonthAndYear -> title
|
||||
}
|
||||
}
|
||||
|
||||
object AllContentNavigation {
|
||||
const val ALL_CONTENT_MAIN = "all_content_main"
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Modifier.bottomBorder(
|
||||
strokeWidth: Dp = 0.5.dp,
|
||||
color: Color = colorResource(R.color.shape_primary)
|
||||
) = composed(
|
||||
factory = {
|
||||
val density = LocalDensity.current
|
||||
val strokeWidthPx = density.run { strokeWidth.toPx() }
|
||||
|
||||
Modifier.drawBehind {
|
||||
val width = size.width
|
||||
val height = size.height - strokeWidthPx / 2
|
||||
|
||||
drawLine(
|
||||
color = color,
|
||||
start = Offset(x = 0f, y = height),
|
||||
end = Offset(x = width, y = height),
|
||||
strokeWidth = strokeWidthPx
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
|
@ -0,0 +1,411 @@
|
|||
package com.anytypeio.anytype.feature_allcontent.ui
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
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.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.selection.LocalTextSelectionColors
|
||||
import androidx.compose.foundation.text.selection.TextSelectionColors
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.TextFieldDefaults
|
||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.anytypeio.anytype.core_models.DVSortType
|
||||
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
|
||||
import com.anytypeio.anytype.core_ui.extensions.bouncingClickable
|
||||
import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
|
||||
import com.anytypeio.anytype.core_ui.views.BodyRegular
|
||||
import com.anytypeio.anytype.core_ui.views.Title1
|
||||
import com.anytypeio.anytype.core_ui.views.Title2
|
||||
import com.anytypeio.anytype.feature_allcontent.R
|
||||
import com.anytypeio.anytype.feature_allcontent.models.AllContentMenuMode
|
||||
import com.anytypeio.anytype.feature_allcontent.models.AllContentSort
|
||||
import com.anytypeio.anytype.feature_allcontent.models.AllContentTab
|
||||
import com.anytypeio.anytype.feature_allcontent.models.MenuButtonViewState
|
||||
import com.anytypeio.anytype.feature_allcontent.models.MenuSortsItem
|
||||
import com.anytypeio.anytype.feature_allcontent.models.UiMenuState
|
||||
import com.anytypeio.anytype.feature_allcontent.models.UiTabsState
|
||||
import com.anytypeio.anytype.feature_allcontent.models.UiTitleState
|
||||
|
||||
//region AllContentTopBarContainer
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AllContentTopBarContainer(
|
||||
titleState: UiTitleState,
|
||||
menuButtonState: MenuButtonViewState,
|
||||
uiMenuState: UiMenuState,
|
||||
onModeClick: (AllContentMenuMode) -> Unit,
|
||||
onSortClick: (AllContentSort) -> Unit
|
||||
) {
|
||||
var isMenuExpanded by remember { mutableStateOf(false) }
|
||||
|
||||
CenterAlignedTopAppBar(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
expandedHeight = 48.dp,
|
||||
title = { AllContentTitle(state = titleState) },
|
||||
actions = {
|
||||
AllContentMenuButton(
|
||||
state = menuButtonState,
|
||||
onClick = { isMenuExpanded = true }
|
||||
)
|
||||
DropdownMenu(
|
||||
modifier = Modifier.width(252.dp),
|
||||
expanded = isMenuExpanded,
|
||||
onDismissRequest = { isMenuExpanded = false },
|
||||
shape = RoundedCornerShape(size = 16.dp),
|
||||
containerColor = colorResource(id = R.color.background_primary),
|
||||
shadowElevation = 20.dp,
|
||||
) {
|
||||
AllContentMenu(
|
||||
uiMenuState = uiMenuState,
|
||||
onModeClick = {
|
||||
onModeClick(it)
|
||||
isMenuExpanded = false
|
||||
},
|
||||
onSortClick = {
|
||||
onSortClick(it)
|
||||
isMenuExpanded = false
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
|
||||
containerColor = colorResource(id = R.color.background_primary)
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@DefaultPreviews
|
||||
@Composable
|
||||
private fun AllContentTopBarContainerPreview() {
|
||||
AllContentTopBarContainer(
|
||||
titleState = UiTitleState.OnlyUnlinked,
|
||||
menuButtonState = MenuButtonViewState.Visible,
|
||||
uiMenuState = UiMenuState(
|
||||
mode = listOf(
|
||||
AllContentMenuMode.AllContent(isSelected = true),
|
||||
AllContentMenuMode.Unlinked()
|
||||
),
|
||||
container = MenuSortsItem.Container(
|
||||
sort = AllContentSort.ByName(isSelected = true)
|
||||
),
|
||||
sorts = listOf(
|
||||
MenuSortsItem.Sort(
|
||||
sort = AllContentSort.ByName(isSelected = true)
|
||||
),
|
||||
),
|
||||
types = listOf(
|
||||
MenuSortsItem.SortType(
|
||||
sort = AllContentSort.ByName(isSelected = true),
|
||||
sortType = DVSortType.DESC,
|
||||
isSelected = true
|
||||
),
|
||||
MenuSortsItem.SortType(
|
||||
sort = AllContentSort.ByDateCreated(isSelected = false),
|
||||
sortType = DVSortType.ASC,
|
||||
isSelected = false
|
||||
),
|
||||
)
|
||||
),
|
||||
onModeClick = {},
|
||||
onSortClick = {}
|
||||
)
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region AllContentTitle
|
||||
@Composable
|
||||
fun AllContentTitle(state: UiTitleState) {
|
||||
when (state) {
|
||||
UiTitleState.Hidden -> return
|
||||
UiTitleState.AllContent -> {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.wrapContentSize(),
|
||||
text = stringResource(id = R.string.all_content_title_all_content),
|
||||
style = Title1,
|
||||
color = colorResource(id = R.color.text_primary)
|
||||
)
|
||||
}
|
||||
|
||||
UiTitleState.OnlyUnlinked -> {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.wrapContentSize(),
|
||||
text = stringResource(id = R.string.all_content_title_only_unlinked),
|
||||
style = Title1,
|
||||
color = colorResource(id = R.color.text_primary)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region AllContentMenuButton
|
||||
@Composable
|
||||
fun AllContentMenuButton(state: MenuButtonViewState, onClick: () -> Unit) {
|
||||
when (state) {
|
||||
MenuButtonViewState.Hidden -> return
|
||||
MenuButtonViewState.Visible -> {
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.padding(end = 12.dp)
|
||||
.size(32.dp)
|
||||
.bouncingClickable { onClick() },
|
||||
painter = painterResource(id = R.drawable.ic_space_list_dots),
|
||||
contentDescription = "Menu icon",
|
||||
contentScale = ContentScale.Inside
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region AllContentTabs
|
||||
@Composable
|
||||
fun AllContentTabs(
|
||||
tabsViewState: UiTabsState.Default,
|
||||
onClick: (AllContentTab) -> Unit
|
||||
) {
|
||||
val scrollState = rememberLazyListState()
|
||||
var selectedTab by remember { mutableStateOf(tabsViewState.selectedTab) }
|
||||
|
||||
val snapFlingBehavior = rememberSnapFlingBehavior(scrollState)
|
||||
|
||||
LazyRow(
|
||||
state = scrollState,
|
||||
flingBehavior = snapFlingBehavior,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(40.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(20.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
contentPadding = PaddingValues(start = 20.dp, end = 20.dp)
|
||||
) {
|
||||
items(
|
||||
count = tabsViewState.tabs.size,
|
||||
key = { index -> tabsViewState.tabs[index].ordinal },
|
||||
) { index ->
|
||||
val tab = tabsViewState.tabs[index]
|
||||
AllContentTabText(
|
||||
tab = tab,
|
||||
isSelected = tab == selectedTab,
|
||||
onClick = {
|
||||
selectedTab = tab
|
||||
onClick(tab)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AllContentTabText(
|
||||
tab: AllContentTab,
|
||||
isSelected: Boolean,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.noRippleClickable { onClick() },
|
||||
text = getTabText(tab),
|
||||
style = Title2,
|
||||
color = if (isSelected) colorResource(id = R.color.glyph_button) else colorResource(id = R.color.glyph_active),
|
||||
maxLines = 1
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getTabText(tab: AllContentTab): String {
|
||||
return when (tab) {
|
||||
AllContentTab.PAGES -> stringResource(id = R.string.all_content_title_tab_pages)
|
||||
AllContentTab.FILES -> stringResource(id = R.string.all_content_title_tab_files)
|
||||
AllContentTab.MEDIA -> stringResource(id = R.string.all_content_title_tab_media)
|
||||
AllContentTab.BOOKMARKS -> stringResource(id = R.string.all_content_title_tab_bookmarks)
|
||||
AllContentTab.TYPES -> stringResource(id = R.string.all_content_title_tab_objetc_types)
|
||||
AllContentTab.RELATIONS -> stringResource(id = R.string.all_content_title_tab_relations)
|
||||
AllContentTab.LISTS -> stringResource(id = R.string.all_content_title_tab_lists)
|
||||
}
|
||||
}
|
||||
|
||||
@DefaultPreviews
|
||||
@Composable
|
||||
private fun AllContentTabsPreview() {
|
||||
AllContentTabs(
|
||||
tabsViewState = UiTabsState.Default(
|
||||
tabs = listOf(
|
||||
AllContentTab.PAGES,
|
||||
AllContentTab.FILES,
|
||||
AllContentTab.MEDIA,
|
||||
AllContentTab.BOOKMARKS,
|
||||
AllContentTab.TYPES,
|
||||
AllContentTab.RELATIONS
|
||||
),
|
||||
selectedTab = AllContentTab.MEDIA
|
||||
),
|
||||
onClick = {}
|
||||
)
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
//region SearchBar
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun AllContentSearchBar(onQueryChanged: (String) -> Unit) {
|
||||
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val focus = LocalFocusManager.current
|
||||
val focusRequester = FocusRequester()
|
||||
|
||||
val selectionColors = TextSelectionColors(
|
||||
backgroundColor = colorResource(id = R.color.cursor_color).copy(
|
||||
alpha = 0.2f
|
||||
),
|
||||
handleColor = colorResource(id = R.color.cursor_color),
|
||||
)
|
||||
|
||||
var query by remember { mutableStateOf(TextFieldValue()) }
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
.background(
|
||||
color = colorResource(id = R.color.shape_transparent),
|
||||
shape = RoundedCornerShape(10.dp)
|
||||
)
|
||||
.height(40.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.ic_search_18),
|
||||
contentDescription = "Search icon",
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterVertically)
|
||||
.padding(
|
||||
start = 11.dp
|
||||
)
|
||||
)
|
||||
CompositionLocalProvider(value = LocalTextSelectionColors provides selectionColors) {
|
||||
|
||||
BasicTextField(
|
||||
value = query,
|
||||
modifier = Modifier
|
||||
.weight(1.0f)
|
||||
.padding(start = 6.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
.focusRequester(focusRequester),
|
||||
textStyle = BodyRegular.copy(
|
||||
color = colorResource(id = R.color.text_primary)
|
||||
),
|
||||
onValueChange = { input ->
|
||||
query = input.also {
|
||||
onQueryChanged(input.text)
|
||||
}
|
||||
},
|
||||
singleLine = true,
|
||||
maxLines = 1,
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = {
|
||||
focus.clearFocus(true)
|
||||
}
|
||||
),
|
||||
decorationBox = @Composable { innerTextField ->
|
||||
TextFieldDefaults.OutlinedTextFieldDecorationBox(
|
||||
value = query.text,
|
||||
innerTextField = innerTextField,
|
||||
enabled = true,
|
||||
singleLine = true,
|
||||
visualTransformation = VisualTransformation.None,
|
||||
interactionSource = interactionSource,
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.search),
|
||||
style = BodyRegular.copy(
|
||||
color = colorResource(id = R.color.text_tertiary)
|
||||
)
|
||||
)
|
||||
},
|
||||
colors = TextFieldDefaults.textFieldColors(
|
||||
backgroundColor = Color.Transparent,
|
||||
cursorColor = colorResource(id = R.color.cursor_color),
|
||||
),
|
||||
border = {},
|
||||
contentPadding = PaddingValues()
|
||||
)
|
||||
},
|
||||
cursorBrush = SolidColor(colorResource(id = R.color.palette_system_blue)),
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.width(9.dp))
|
||||
AnimatedVisibility(
|
||||
visible = query.text.isNotEmpty(),
|
||||
enter = fadeIn(tween(100)),
|
||||
exit = fadeOut(tween(100))
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.ic_clear_18),
|
||||
contentDescription = "Clear icon",
|
||||
modifier = Modifier
|
||||
.padding(end = 9.dp)
|
||||
.noRippleClickable {
|
||||
query = TextFieldValue().also {
|
||||
onQueryChanged("")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DefaultPreviews
|
||||
@Composable
|
||||
private fun AllContentSearchBarPreview() {
|
||||
AllContentSearchBar() {}
|
||||
}
|
||||
//endregion
|
|
@ -64,6 +64,7 @@ coilComposeVersion = '2.6.0'
|
|||
sentryVersion = '7.6.0'
|
||||
|
||||
composeQrCodeVersion = '1.0.1'
|
||||
fragmentComposeVersion = "1.8.3"
|
||||
|
||||
[libraries]
|
||||
middleware = { module = "io.anyproto:anytype-heart-android", version.ref = "middlewareVersion" }
|
||||
|
@ -151,6 +152,7 @@ navigationCompose = { module = "androidx.navigation:navigation-compose", version
|
|||
appUpdater = { module = "com.github.PLPsiSoft:AndroidAppUpdater", version = "9913ce80da7871c84af24b9adc2bf2414ca294f0" }
|
||||
composeQrCode = { module = "com.lightspark:compose-qr-code", version.ref = "composeQrCodeVersion" }
|
||||
playBilling = { module = "com.android.billingclient:billing", version = "7.0.0" }
|
||||
fragmentCompose = { group = "androidx.fragment", name = "fragment-compose", version.ref = "fragmentComposeVersion" }
|
||||
|
||||
[bundles]
|
||||
|
||||
|
|
|
@ -1748,6 +1748,35 @@ Please provide specific details of your needs here.</string>
|
|||
<string name="new_object">New object</string>
|
||||
<string name="vault_my_spaces">My spaces</string>
|
||||
|
||||
<string name="all_content">All content</string>
|
||||
<string name="all_content_title_all_content">All objects</string>
|
||||
<string name="all_content_title_only_unlinked">Only unlinked</string>
|
||||
<string name="all_content_title_only_unlinked_description">Unlinked objects that do not have a direct link or backlink with other objects in the graph.</string>
|
||||
|
||||
<string name="all_content_view_bin">View Bin</string>
|
||||
|
||||
<string name="all_content_title_tab_pages">Pages</string>
|
||||
<string name="all_content_title_tab_lists">Lists</string>
|
||||
<string name="all_content_title_tab_media">Media</string>
|
||||
<string name="all_content_title_tab_bookmarks">Bookmarks</string>
|
||||
<string name="all_content_title_tab_files">Files</string>
|
||||
<string name="all_content_title_tab_objetc_types">Object Types</string>
|
||||
<string name="all_content_title_tab_relations">Relations</string>
|
||||
<string name="all_content_sort_by">Sort by</string>
|
||||
|
||||
<string name="all_content_sort_name_desc">Z → A</string>
|
||||
<string name="all_content_sort_name_asc">A → Z</string>
|
||||
<string name="all_content_sort_date_desc">Newest first</string>
|
||||
<string name="all_content_sort_date_asc">Oldest first</string>
|
||||
<string name="all_content_sort_date_updated">Date updated</string>
|
||||
<string name="all_content_sort_date_created">Date created</string>
|
||||
<string name="all_content_sort_name">Name</string>
|
||||
|
||||
<string name="all_content">All objects</string>
|
||||
|
||||
<string name="allContent_group_today">Today</string>
|
||||
<string name="allContent_group_yesterday">Yesterday</string>
|
||||
<string name="allContent_group_prev_7">Previous 7 days</string>
|
||||
<string name="allContent_group_prev_14">Previous 14 days</string>
|
||||
|
||||
|
||||
</resources>
|
|
@ -1035,7 +1035,11 @@ class HomeScreenViewModel(
|
|||
)
|
||||
}
|
||||
WidgetView.AllContent.ALL_CONTENT_WIDGET_ID -> {
|
||||
// TODO Proceed with navigation
|
||||
navigation(
|
||||
Navigation.OpenAllContent(
|
||||
space = space
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2036,6 +2040,7 @@ class HomeScreenViewModel(
|
|||
data class OpenSet(val ctx: Id, val space: Id, val view: Id?) : Navigation()
|
||||
data class ExpandWidget(val subscription: Subscription, val space: Id) : Navigation()
|
||||
data class OpenLibrary(val space: Id) : Navigation()
|
||||
data class OpenAllContent(val space: Id) : Navigation()
|
||||
}
|
||||
|
||||
class Factory @Inject constructor(
|
||||
|
|
|
@ -51,6 +51,8 @@ interface AppNavigation {
|
|||
|
||||
fun openTemplatesModal(typeId: Id)
|
||||
|
||||
fun openAllContent(space: Id)
|
||||
|
||||
sealed class Command {
|
||||
|
||||
data object Exit : Command()
|
||||
|
|
|
@ -65,3 +65,4 @@ include ':localization'
|
|||
include ':payments'
|
||||
include ':gallery-experience'
|
||||
include ':feature-discussions'
|
||||
include ':feature-all-content'
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue