From 823032a7ea71d8cce2deac7f5fe9a9ff00c83a7f Mon Sep 17 00:00:00 2001
From: Konstantin Ivanov <54908981+konstantiniiv@users.noreply.github.com>
Date: Sat, 5 Oct 2024 12:07:18 +0200
Subject: [PATCH] DROID-2885 All content | Navigation widget (#1628)
---
.../anytype/di/feature/AllContentDI.kt | 38 +++++++
.../ui/allcontent/AllContentFragment.kt | 55 ++++++++++-
.../ui/widgets/collection/CollectionScreen.kt | 2 +-
app/src/main/res/navigation/graph.xml | 13 ++-
.../components/BottomNavigationMenu.kt | 6 +-
.../presentation/AllContentViewModel.kt | 79 ++++++++++++++-
.../AllContentViewModelFactory.kt | 7 +-
.../feature_allcontent/ui/AllContentScreen.kt | 98 ++++++++++++++++---
8 files changed, 269 insertions(+), 29 deletions(-)
diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/AllContentDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/AllContentDI.kt
index 09a3bfc718..a9d46bdc31 100644
--- a/app/src/main/java/com/anytypeio/anytype/di/feature/AllContentDI.kt
+++ b/app/src/main/java/com/anytypeio/anytype/di/feature/AllContentDI.kt
@@ -8,14 +8,18 @@ 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.ConfigStorage
import com.anytypeio.anytype.domain.config.UserSettingsRepository
import com.anytypeio.anytype.domain.debugging.Logger
+import com.anytypeio.anytype.domain.launch.GetDefaultObjectType
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.page.CreateObject
import com.anytypeio.anytype.domain.search.SearchObjects
import com.anytypeio.anytype.domain.search.SubscriptionEventChannel
+import com.anytypeio.anytype.domain.workspace.SpaceManager
import com.anytypeio.anytype.feature_allcontent.presentation.AllContentViewModel
import com.anytypeio.anytype.feature_allcontent.presentation.AllContentViewModelFactory
import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate
@@ -93,6 +97,38 @@ object AllContentModule {
repo: BlockRepository
): SearchObjects = SearchObjects(repo = repo)
+ @JvmStatic
+ @Provides
+ @PerScreen
+ fun createObject(
+ repo: BlockRepository,
+ getDefaultObjectType: GetDefaultObjectType,
+ dispatchers: AppCoroutineDispatchers,
+ spaceManager: SpaceManager,
+ ): CreateObject = CreateObject(
+ repo = repo,
+ getDefaultObjectType = getDefaultObjectType,
+ dispatchers = dispatchers,
+ spaceManager = spaceManager
+ )
+
+ @JvmStatic
+ @Provides
+ @PerScreen
+ fun provideGetDefaultPageType(
+ userSettingsRepository: UserSettingsRepository,
+ blockRepository: BlockRepository,
+ dispatchers: AppCoroutineDispatchers,
+ spaceManager: SpaceManager,
+ configStorage: ConfigStorage
+ ): GetDefaultObjectType = GetDefaultObjectType(
+ userSettingsRepository = userSettingsRepository,
+ blockRepository = blockRepository,
+ dispatchers = dispatchers,
+ spaceManager = spaceManager,
+ configStorage = configStorage
+ )
+
@Module
interface Declarations {
@PerScreen
@@ -115,4 +151,6 @@ interface AllContentDependencies : ComponentDependencies {
fun subEventChannel(): SubscriptionEventChannel
fun logger(): Logger
fun localeProvider(): LocaleProvider
+ fun spaceManager(): SpaceManager
+ fun config(): ConfigStorage
}
\ No newline at end of file
diff --git a/app/src/main/java/com/anytypeio/anytype/ui/allcontent/AllContentFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/allcontent/AllContentFragment.kt
index 43e8591825..2300b89bdd 100644
--- a/app/src/main/java/com/anytypeio/anytype/ui/allcontent/AllContentFragment.kt
+++ b/app/src/main/java/com/anytypeio/anytype/ui/allcontent/AllContentFragment.kt
@@ -14,7 +14,9 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
+import androidx.navigation.fragment.findNavController
import com.anytypeio.anytype.BuildConfig
+import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.core_utils.ext.argString
@@ -30,6 +32,8 @@ import com.anytypeio.anytype.feature_allcontent.ui.AllContentWrapperScreen
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.presentation.widgets.collection.Subscription
import com.anytypeio.anytype.ui.base.navigation
+import com.anytypeio.anytype.ui.objects.creation.SelectObjectTypeFragment
+import com.anytypeio.anytype.ui.search.GlobalSearchFragment
import com.anytypeio.anytype.ui.settings.typography
import javax.inject.Inject
import timber.log.Timber
@@ -59,6 +63,32 @@ class AllContentFragment : BaseComposeFragment() {
super.onViewCreated(view, savedInstanceState)
subscribe(vm.commands) { command ->
when (command) {
+ is AllContentViewModel.Command.ExitToVault -> {
+ runCatching {
+ findNavController().navigate(R.id.actionOpenVault)
+ }.onFailure { e ->
+ Timber.e(e, "Error while exiting to vault from all content")
+ }
+ }
+ is AllContentViewModel.Command.Back -> {
+ runCatching {
+ findNavController().popBackStack()
+ }.onFailure { e ->
+ Timber.e(e, "Error while exiting back from all content")
+ }
+ }
+ is AllContentViewModel.Command.OpenGlobalSearch -> {
+ runCatching {
+ findNavController().navigate(
+ resId = R.id.globalSearchScreen,
+ args = GlobalSearchFragment.args(
+ space = space
+ )
+ )
+ }.onFailure { e ->
+ Timber.e(e, "Error while opening global search screen from all content")
+ }
+ }
is AllContentViewModel.Command.NavigateToEditor -> {
runCatching {
navigation().openDocument(
@@ -67,7 +97,7 @@ class AllContentFragment : BaseComposeFragment() {
)
}.onFailure {
toast("Failed to open document")
- Timber.e(it, "Failed to open document")
+ Timber.e(it, "Failed to open document from all content")
}
}
is AllContentViewModel.Command.NavigateToSetOrCollection -> {
@@ -78,7 +108,7 @@ class AllContentFragment : BaseComposeFragment() {
)
}.onFailure {
toast("Failed to open object set")
- Timber.e(it, "Failed to open object set")
+ Timber.e(it, "Failed to open object set from all content")
}
}
is AllContentViewModel.Command.SendToast -> {
@@ -92,7 +122,7 @@ class AllContentFragment : BaseComposeFragment() {
)
}.onFailure {
toast("Failed to open bin")
- Timber.e(it, "Failed to open bin")
+ Timber.e(it, "Failed to open bin from all content")
}
}
is AllContentViewModel.Command.OpenTypeEditing -> {
@@ -105,7 +135,7 @@ class AllContentFragment : BaseComposeFragment() {
)
}.onFailure {
toast("Failed to open type editing screen")
- Timber.e(it, "Failed to open type editing screen")
+ Timber.e(it, "Failed to open type editing screen from all content")
}
}
}
@@ -133,7 +163,22 @@ class AllContentFragment : BaseComposeFragment() {
canPaginate = vm.canPaginate.collectAsStateWithLifecycle().value,
onUpdateLimitSearch = vm::updateLimit,
uiContentState = vm.uiContentState.collectAsStateWithLifecycle().value,
- onTypeClicked = vm::onTypeClicked
+ onTypeClicked = vm::onTypeClicked,
+ onHomeClicked = vm::onHomeClicked,
+ onGlobalSearchClicked = vm::onGlobalSearchClicked,
+ onAddDocClicked = vm::onAddDockClicked,
+ onCreateObjectLongClicked = {
+ val dialog = SelectObjectTypeFragment.new(
+ flow = SelectObjectTypeFragment.FLOW_CREATE_OBJECT,
+ space = space
+ ).apply {
+ onTypeSelected = {
+ vm.onCreateObjectOfTypeClicked(it)
+ }
+ }
+ dialog.show(childFragmentManager,null)
+ },
+ onBackClicked = vm::onBackClicked
)
}
}
diff --git a/app/src/main/java/com/anytypeio/anytype/ui/widgets/collection/CollectionScreen.kt b/app/src/main/java/com/anytypeio/anytype/ui/widgets/collection/CollectionScreen.kt
index 5557cb126b..2c9507df49 100644
--- a/app/src/main/java/com/anytypeio/anytype/ui/widgets/collection/CollectionScreen.kt
+++ b/app/src/main/java/com/anytypeio/anytype/ui/widgets/collection/CollectionScreen.kt
@@ -141,7 +141,7 @@ fun ScreenContent(
SearchBar(vm, uiState)
ListView(vm, uiState, stringResource(id = R.string.search_no_results_try))
}
- Box(Modifier.align(BottomCenter)) {
+ Box(Modifier.align(BottomCenter).padding(bottom = 20.dp)) {
BottomNavigationMenu(
backClick = { vm.onPrevClicked() },
homeClick = { vm.onHomeClicked() },
diff --git a/app/src/main/res/navigation/graph.xml b/app/src/main/res/navigation/graph.xml
index 217e28113b..5cd61d376f 100644
--- a/app/src/main/res/navigation/graph.xml
+++ b/app/src/main/res/navigation/graph.xml
@@ -162,10 +162,7 @@
/>
+ app:destination="@id/allContentScreen" />
+
+
>(emptyList())
private val sortState = MutableStateFlow(DEFAULT_INITIAL_SORT)
@@ -473,11 +478,17 @@ class AllContentViewModel(
fun onItemClicked(item: UiContentItem.Item) {
Timber.d("onItemClicked: ${item.id}")
val layout = item.layout ?: return
- viewModelScope.launch {
- when (val navigation = layout.navigation(
+ proceedWithNavigation(
+ navigation = layout.navigation(
target = item.id,
space = vmParams.spaceId.id
- )) {
+ )
+ )
+ }
+
+ private fun proceedWithNavigation(navigation: OpenObjectNavigation) {
+ viewModelScope.launch {
+ when (navigation) {
is OpenObjectNavigation.OpenDataView -> {
commands.emit(
Command.NavigateToSetOrCollection(
@@ -503,6 +514,61 @@ class AllContentViewModel(
}
}
+ fun onHomeClicked() {
+ Timber.d("onHomeClicked")
+ viewModelScope.launch {
+ commands.emit(Command.ExitToVault)
+ }
+ }
+
+ fun onGlobalSearchClicked() {
+ Timber.d("onGlobalSearchClicked")
+ viewModelScope.launch {
+ commands.emit(Command.OpenGlobalSearch)
+ }
+ }
+
+ fun onAddDockClicked() {
+ Timber.d("onAddDockClicked")
+ proceedWithCreateDoc()
+ }
+
+ fun onBackClicked() {
+ Timber.d("onBackClicked")
+ viewModelScope.launch {
+ commands.emit(Command.Back)
+ }
+ }
+
+ fun onCreateObjectOfTypeClicked(objType: ObjectWrapper.Type) {
+ proceedWithCreateDoc(objType)
+ }
+
+ private fun proceedWithCreateDoc(
+ objType: ObjectWrapper.Type? = null
+ ) {
+ val startTime = System.currentTimeMillis()
+ val params = objType?.uniqueKey.getCreateObjectParams(objType?.defaultTemplateId)
+ viewModelScope.launch {
+ createObject.async(params).fold(
+ onSuccess = { result ->
+ proceedWithNavigation(
+ navigation = result.obj.navigation()
+ )
+ sendAnalyticsObjectCreateEvent(
+ analytics = analytics,
+ route = EventsDictionary.Routes.objCreateLibrary,
+ startTime = startTime,
+ objType = objType ?: storeOfObjectTypes.getByKey(result.typeKey.key),
+ view = EventsDictionary.View.viewHome,
+ spaceParams = provideParams(space = vmParams.spaceId.id)
+ )
+ },
+ onFailure = { e -> Timber.e(e, "Error while creating a new object") }
+ )
+ }
+ }
+
fun onTypeClicked(item: UiContentItem.Type) {
Timber.d("onTypeClicked: ${item.id}")
viewModelScope.launch {
@@ -557,6 +623,9 @@ class AllContentViewModel(
data class NavigateToBin(val space: Id) : Command()
data class SendToast(val message: String) : Command()
data class OpenTypeEditing(val item: UiContentItem.Type) : Command()
+ data object OpenGlobalSearch : Command()
+ data object ExitToVault : Command()
+ data object Back : Command()
}
companion object {
diff --git a/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/presentation/AllContentViewModelFactory.kt b/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/presentation/AllContentViewModelFactory.kt
index cefa74902b..f308ae6302 100644
--- a/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/presentation/AllContentViewModelFactory.kt
+++ b/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/presentation/AllContentViewModelFactory.kt
@@ -9,6 +9,7 @@ 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.page.CreateObject
import com.anytypeio.anytype.domain.search.SearchObjects
import com.anytypeio.anytype.feature_allcontent.presentation.AllContentViewModel.VmParams
import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate
@@ -24,7 +25,8 @@ class AllContentViewModelFactory @Inject constructor(
private val updateAllContentState: UpdateAllContentState,
private val restoreAllContentState: RestoreAllContentState,
private val searchObjects: SearchObjects,
- private val localeProvider: LocaleProvider
+ private val localeProvider: LocaleProvider,
+ private val createObject: CreateObject
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun create(modelClass: Class): T =
@@ -38,6 +40,7 @@ class AllContentViewModelFactory @Inject constructor(
restoreAllContentState = restoreAllContentState,
updateAllContentState = updateAllContentState,
searchObjects = searchObjects,
- localeProvider = localeProvider
+ localeProvider = localeProvider,
+ createObject = createObject
) as T
}
\ No newline at end of file
diff --git a/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/ui/AllContentScreen.kt b/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/ui/AllContentScreen.kt
index 4f14c1fc19..ccec6bc4bd 100644
--- a/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/ui/AllContentScreen.kt
+++ b/feature-all-content/src/main/java/com/anytypeio/anytype/feature_allcontent/ui/AllContentScreen.kt
@@ -12,11 +12,14 @@ 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.ime
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.systemBars
import androidx.compose.foundation.layout.windowInsetsPadding
+import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
@@ -52,6 +55,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.foundation.Divider
+import com.anytypeio.anytype.core_ui.foundation.components.BottomNavigationMenu
import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
import com.anytypeio.anytype.core_ui.views.ButtonSize
import com.anytypeio.anytype.core_ui.views.Caption1Regular
@@ -99,7 +103,12 @@ fun AllContentWrapperScreen(
onBinClick: () -> Unit,
canPaginate: Boolean,
onUpdateLimitSearch: () -> Unit,
- uiContentState: UiContentState
+ uiContentState: UiContentState,
+ onHomeClicked: () -> Unit,
+ onGlobalSearchClicked: () -> Unit,
+ onAddDocClicked: () -> Unit,
+ onCreateObjectLongClicked: () -> Unit,
+ onBackClicked: () -> Unit
) {
val lazyListState = rememberLazyListState()
@@ -134,7 +143,12 @@ fun AllContentWrapperScreen(
uiItemsState = uiItemsState,
lazyListState = lazyListState,
uiContentState = uiContentState,
- onTypeClicked = onTypeClicked
+ onTypeClicked = onTypeClicked,
+ onHomeClicked = onHomeClicked,
+ onGlobalSearchClicked = onGlobalSearchClicked,
+ onAddDocClicked = onAddDocClicked,
+ onCreateObjectLongClicked = onCreateObjectLongClicked,
+ onBackClicked = onBackClicked
)
}
@@ -153,17 +167,41 @@ fun AllContentMainScreen(
onTypeClicked: (UiContentItem.Type) -> Unit,
onBinClick: () -> Unit,
lazyListState: LazyListState,
- uiContentState: UiContentState
+ uiContentState: UiContentState,
+ onHomeClicked: () -> Unit,
+ onGlobalSearchClicked: () -> Unit,
+ onAddDocClicked: () -> Unit,
+ onCreateObjectLongClicked: () -> Unit,
+ onBackClicked: () -> Unit
) {
- val modifier = Modifier
- .background(color = colorResource(id = R.color.background_primary))
-
var isSearchEmpty by remember { mutableStateOf(true) }
Scaffold(
- modifier = modifier
+ modifier = Modifier
.fillMaxSize(),
containerColor = colorResource(id = R.color.background_primary),
+ bottomBar = {
+ Box(
+ modifier = if (BuildConfig.USE_EDGE_TO_EDGE && Build.VERSION.SDK_INT >= EDGE_TO_EDGE_MIN_SDK)
+ Modifier
+ .windowInsetsPadding(WindowInsets.navigationBars)
+ .padding(bottom = 20.dp)
+ .fillMaxWidth()
+ else
+ Modifier
+ .padding(bottom = 20.dp)
+ .fillMaxWidth()
+ ) {
+ BottomMenu(
+ modifier = Modifier.align(Alignment.BottomCenter),
+ onHomeClicked = onHomeClicked,
+ onGlobalSearchClicked = onGlobalSearchClicked,
+ onAddDocClicked = onAddDocClicked,
+ onCreateObjectLongClicked = onCreateObjectLongClicked,
+ onBackClicked = onBackClicked
+ )
+ }
+ },
topBar = {
Column(
modifier = if (BuildConfig.USE_EDGE_TO_EDGE && Build.VERSION.SDK_INT >= EDGE_TO_EDGE_MIN_SDK)
@@ -198,11 +236,13 @@ fun AllContentMainScreen(
Modifier
.windowInsetsPadding(WindowInsets.navigationBars)
.fillMaxSize()
- .padding(paddingValues)
+ .padding(top = paddingValues.calculateTopPadding())
+ .background(color = colorResource(id = R.color.background_primary))
else
Modifier
.fillMaxSize()
.padding(paddingValues)
+ .background(color = colorResource(id = R.color.background_primary))
Box(
modifier = contentModifier,
@@ -214,18 +254,22 @@ fun AllContentMainScreen(
is UiContentState.Error -> {
ErrorState(uiContentState.message)
}
+
is UiContentState.Idle -> {
// Do nothing.
}
+
UiContentState.InitLoading -> {
LoadingState()
}
+
UiContentState.Paging -> {}
UiContentState.Empty -> {
EmptyState(isSearchEmpty = isSearchEmpty)
}
}
}
+
else -> {
ContentItems(
uiItemsState = uiItemsState,
@@ -241,6 +285,27 @@ fun AllContentMainScreen(
)
}
+@Composable
+fun BottomMenu(
+ modifier: Modifier = Modifier,
+ onHomeClicked: () -> Unit,
+ onGlobalSearchClicked: () -> Unit,
+ onAddDocClicked: () -> Unit,
+ onCreateObjectLongClicked: () -> Unit,
+ onBackClicked: () -> Unit
+) {
+ val isImeVisible = WindowInsets.ime.getBottom(LocalDensity.current) > 0
+ if (isImeVisible) return
+ BottomNavigationMenu(
+ modifier = modifier,
+ backClick = onBackClicked,
+ onProfileClicked = onHomeClicked,
+ searchClick = onGlobalSearchClicked,
+ addDocClick = onAddDocClicked,
+ onCreateObjectLongClicked = onCreateObjectLongClicked
+ )
+}
+
@Composable
private fun ContentItems(
uiItemsState: List,
@@ -329,7 +394,7 @@ private fun ContentItems(
LaunchedEffect(key1 = uiContentState) {
if (uiContentState is UiContentState.Idle) {
- if (uiContentState.scrollToTop) {
+ if (uiContentState.scrollToTop) {
scope.launch {
lazyListState.scrollToItem(0)
}
@@ -366,7 +431,13 @@ fun PreviewMainScreen() {
AllContentMainScreen(
uiItemsState = emptyList(),
uiTitleState = UiTitleState.AllContent,
- uiTabsState = UiTabsState(tabs = listOf(AllContentTab.PAGES, AllContentTab.TYPES, AllContentTab.LISTS), selectedTab = AllContentTab.LISTS),
+ uiTabsState = UiTabsState(
+ tabs = listOf(
+ AllContentTab.PAGES,
+ AllContentTab.TYPES,
+ AllContentTab.LISTS
+ ), selectedTab = AllContentTab.LISTS
+ ),
uiMenuState = UiMenuState.Hidden,
onTabClick = {},
onQueryChanged = {},
@@ -376,7 +447,12 @@ fun PreviewMainScreen() {
onBinClick = {},
lazyListState = rememberLazyListState(),
uiContentState = UiContentState.Error("Error message"),
- onTypeClicked = {}
+ onTypeClicked = {},
+ onHomeClicked = {},
+ onGlobalSearchClicked = {},
+ onAddDocClicked = {},
+ onCreateObjectLongClicked = {},
+ onBackClicked = {}
)
}