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

DROID-2885 All content | Navigation widget (#1628)

This commit is contained in:
Konstantin Ivanov 2024-10-05 12:07:18 +02:00 committed by GitHub
parent dc47b4ded7
commit 823032a7ea
Signed by: github
GPG key ID: B5690EEEBB952194
8 changed files with 269 additions and 29 deletions

View file

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

View file

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

View file

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

View file

@ -162,10 +162,7 @@
/>
<action
android:id="@+id/action_open_all_content"
app:destination="@id/allContentScreen"
app:enterAnim="@anim/enter_from_right"
app:exitAnim="@anim/exit_to_left"
/>
app:destination="@id/allContentScreen" />
</fragment>
<fragment
@ -408,6 +405,14 @@
<action
android:id="@+id/openTypeEditingScreen"
app:destination="@id/typeEditingFragment" />
<action
android:id="@+id/actionOpenVault"
app:destination="@id/vaultScreen"
app:popUpTo="@id/vaultScreen"
app:popUpToInclusive="true" />
<action
android:id="@+id/actionOpenGlobalSearch"
app:destination="@id/globalSearchScreen" />
</fragment>
<fragment

View file

@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
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.shape.CircleShape
@ -50,7 +51,10 @@ fun BottomNavigationMenu(
modifier = modifier
.height(Height)
.width(Width)
.background(color = colorResource(id = R.color.background_primary))
.background(
shape = RoundedCornerShape(16.dp),
color = colorResource(id = R.color.home_screen_button)
)
/**
* Workaround for clicks through the bottom navigation menu.
*/

View file

@ -3,6 +3,7 @@ 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.analytics.base.EventsDictionary
import com.anytypeio.anytype.core_models.DVSortType
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectWrapper
@ -15,6 +16,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.models.AllContentMenuMode
import com.anytypeio.anytype.feature_allcontent.models.AllContentSort
@ -31,8 +33,10 @@ import com.anytypeio.anytype.feature_allcontent.models.mapRelationKeyToSort
import com.anytypeio.anytype.feature_allcontent.models.toUiContentItems
import com.anytypeio.anytype.feature_allcontent.models.toUiContentTypes
import com.anytypeio.anytype.presentation.analytics.AnalyticSpaceHelperDelegate
import com.anytypeio.anytype.presentation.extension.sendAnalyticsObjectCreateEvent
import com.anytypeio.anytype.presentation.home.OpenObjectNavigation
import com.anytypeio.anytype.presentation.home.navigation
import com.anytypeio.anytype.presentation.objects.getCreateObjectParams
import java.time.Instant
import java.time.LocalDate
import java.time.ZoneId
@ -74,8 +78,9 @@ class AllContentViewModel(
private val updateAllContentState: UpdateAllContentState,
private val restoreAllContentState: RestoreAllContentState,
private val searchObjects: SearchObjects,
private val localeProvider: LocaleProvider
) : ViewModel() {
private val localeProvider: LocaleProvider,
private val createObject: CreateObject
) : ViewModel(), AnalyticSpaceHelperDelegate by analyticSpaceHelperDelegate {
private val searchResultIds = MutableStateFlow<List<Id>>(emptyList())
private val sortState = MutableStateFlow<AllContentSort>(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 {

View file

@ -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 <T : ViewModel> create(modelClass: Class<T>): T =
@ -38,6 +40,7 @@ class AllContentViewModelFactory @Inject constructor(
restoreAllContentState = restoreAllContentState,
updateAllContentState = updateAllContentState,
searchObjects = searchObjects,
localeProvider = localeProvider
localeProvider = localeProvider,
createObject = createObject
) as T
}

View file

@ -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<UiContentItem>,
@ -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 = {}
)
}