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

DROID-977 | Implemented bottom menu in library (#2944)

DROID-977 Library | Enhancement | Implemented bottom menu in library
This commit is contained in:
Allan Quatermain 2023-02-21 18:11:28 +04:00 committed by GitHub
parent f96a74a638
commit a5cf9ebfb8
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 257 additions and 52 deletions

View file

@ -6,10 +6,14 @@ import com.anytypeio.anytype.core_utils.di.scope.PerScreen
import com.anytypeio.anytype.di.common.ComponentDependencies
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.launch.GetDefaultEditorType
import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.`object`.SetObjectDetails
import com.anytypeio.anytype.domain.page.CreateObject
import com.anytypeio.anytype.domain.search.SubscriptionEventChannel
import com.anytypeio.anytype.domain.templates.GetTemplates
import com.anytypeio.anytype.domain.workspace.AddObjectToWorkspace
import com.anytypeio.anytype.domain.workspace.RemoveObjectsFromWorkspace
import com.anytypeio.anytype.domain.workspace.WorkspaceManager
@ -114,6 +118,41 @@ object LibraryModule {
dispatchers: AppCoroutineDispatchers
): RemoveObjectsFromWorkspace = RemoveObjectsFromWorkspace(repo, dispatchers)
@JvmStatic
@Provides
@PerScreen
fun getCreateObject(
repo: BlockRepository,
getTemplates: GetTemplates,
getDefaultEditorType: GetDefaultEditorType,
dispatchers: AppCoroutineDispatchers
): CreateObject = CreateObject(
repo = repo,
getTemplates = getTemplates,
getDefaultEditorType = getDefaultEditorType,
dispatchers = dispatchers
)
@JvmStatic
@PerScreen
@Provides
fun provideGetDefaultPageType(
repo: UserSettingsRepository,
dispatchers: AppCoroutineDispatchers
): GetDefaultEditorType =
GetDefaultEditorType(repo, dispatchers)
@JvmStatic
@Provides
@PerScreen
fun provideGetTemplates(
repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
): GetTemplates = GetTemplates(
repo = repo,
dispatchers = dispatchers
)
@Provides
@PerScreen
@JvmStatic
@ -148,4 +187,6 @@ interface LibraryDependencies : ComponentDependencies {
fun channel(): SubscriptionEventChannel
fun dispatchers(): AppCoroutineDispatchers
fun userSettingsRepository(): UserSettingsRepository
}

View file

@ -39,7 +39,7 @@ import com.anytypeio.anytype.presentation.widgets.TreePath
import com.anytypeio.anytype.presentation.widgets.ViewId
import com.anytypeio.anytype.presentation.widgets.WidgetId
import com.anytypeio.anytype.presentation.widgets.WidgetView
import com.anytypeio.anytype.ui.library.views.list.items.noRippleClickable
import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
import com.anytypeio.anytype.ui.widgets.menu.DropDownMenuAction
import com.anytypeio.anytype.ui.widgets.menu.WidgetActionButton
import com.anytypeio.anytype.ui.widgets.types.LinkWidgetCard

View file

@ -9,6 +9,7 @@ import androidx.compose.material.MaterialTheme
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.core.os.bundleOf
import androidx.fragment.app.setFragmentResultListener
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
@ -21,6 +22,7 @@ import com.anytypeio.anytype.core_utils.ui.BaseComposeFragment
import com.anytypeio.anytype.di.common.componentManager
import com.anytypeio.anytype.presentation.library.LibraryViewModel
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.ui.editor.EditorFragment
import com.anytypeio.anytype.ui.relations.REQUEST_KEY_MODIFY_RELATION
import com.anytypeio.anytype.ui.relations.REQUEST_KEY_UNINSTALL_RELATION
import com.anytypeio.anytype.ui.relations.REQUEST_UNINSTALL_RELATION_ARG_ID
@ -108,6 +110,24 @@ class LibraryFragment : BaseComposeFragment() {
)
)
}
is LibraryViewModel.Navigation.Back -> {
findNavController().popBackStack()
}
is LibraryViewModel.Navigation.Search -> {
findNavController().safeNavigate(
R.id.libraryFragment,
R.id.pageSearchFragment
)
}
is LibraryViewModel.Navigation.CreateDoc -> {
findNavController().safeNavigate(
R.id.libraryFragment,
R.id.objectNavigation,
bundleOf(
EditorFragment.ID_KEY to it.id
)
)
}
}
}
}

View file

@ -16,17 +16,23 @@ import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.ime
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.unit.IntSize
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_ui.foundation.components.BottomNavigationMenu
import com.anytypeio.anytype.presentation.library.LibraryEvent
import com.anytypeio.anytype.presentation.library.LibraryViewModel
import com.anytypeio.anytype.ui.library.views.LibraryTabs
import com.anytypeio.anytype.ui.library.views.LibraryTabsContent
@ -50,26 +56,45 @@ fun LibraryScreen(configuration: LibraryConfiguration, viewModel: LibraryViewMod
val screenState = remember { mutableStateOf(ScreenState.CONTENT) }
Column(modifier = modifier) {
LibraryTabs(
modifier = modifier,
pagerState = pagerState,
configuration = configuration,
screenState = screenState,
)
LibraryTabsContent(
modifier = modifier,
pagerState = pagerState,
configuration = listOf(configuration.types, configuration.relations),
state = uiState,
vmEventStream = viewModel::eventStream,
screenState = screenState,
effects = effects
)
Scaffold(bottomBar = { Menu(viewModel, modifier = modifier) }) {
println(it)
Column(modifier = modifier) {
LibraryTabs(
modifier = modifier,
pagerState = pagerState,
configuration = configuration,
screenState = screenState,
)
LibraryTabsContent(
modifier = modifier,
pagerState = pagerState,
configuration = listOf(configuration.types, configuration.relations),
state = uiState,
vmEventStream = viewModel::eventStream,
screenState = screenState,
effects = effects
)
}
}
}
@Composable
fun Menu(
viewModel: LibraryViewModel,
modifier: Modifier = Modifier
) {
val isImeVisible = WindowInsets.ime.getBottom(LocalDensity.current) > 0
if (isImeVisible) return
BottomNavigationMenu(
modifier = modifier,
backClick = { viewModel.eventStream(LibraryEvent.BottomMenu.Back()) },
homeClick = { viewModel.eventStream(LibraryEvent.BottomMenu.Back()) },
searchClick = { viewModel.eventStream(LibraryEvent.BottomMenu.Search()) },
addDocClick = { viewModel.eventStream(LibraryEvent.BottomMenu.AddDoc()) },
)
}
@Composable
fun WrapWithLibraryAnimation(
visible: Boolean,

View file

@ -31,6 +31,7 @@ import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
import com.anytypeio.anytype.presentation.library.DependentData
import com.anytypeio.anytype.presentation.library.LibraryEvent
import com.anytypeio.anytype.presentation.library.LibraryScreenState
@ -51,7 +52,6 @@ import com.anytypeio.anytype.ui.library.views.list.items.LibTypeItem
import com.anytypeio.anytype.ui.library.views.list.items.LibraryObjectEmptyItem
import com.anytypeio.anytype.ui.library.views.list.items.MyRelationItem
import com.anytypeio.anytype.ui.library.views.list.items.MyTypeItem
import com.anytypeio.anytype.ui.library.views.list.items.noRippleClickable
import com.anytypeio.anytype.ui.settings.fonts
import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.HorizontalPager
@ -157,11 +157,12 @@ private fun SearchCancel(modifier: Modifier = Modifier, visible: Boolean = false
}
@OptIn(ExperimentalComposeUiApi::class)
@Composable
private fun LibraryList(
data: LibraryScreenState.Tabs.TabData,
vmEventStream: (LibraryEvent) -> Unit,
screenState: MutableState<ScreenState>
screenState: MutableState<ScreenState>,
) {
val itemModifier = Modifier
@ -258,6 +259,9 @@ private fun LibraryList(
if (ix < data.items.lastIndex) {
LibraryDivider()
}
if (ix == data.items.lastIndex && screenState.value.visible()) {
Spacer(modifier = Modifier.height(48.dp))
}
}
)
}

View file

@ -1,9 +1,6 @@
package com.anytypeio.anytype.ui.library.views.list.items
import androidx.compose.foundation.Image
import androidx.compose.foundation.Indication
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@ -13,13 +10,11 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
@ -29,6 +24,7 @@ import androidx.compose.ui.viewinterop.AndroidView
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.RelationFormat
import com.anytypeio.anytype.core_ui.extensions.simpleIcon
import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
import com.anytypeio.anytype.core_ui.widgets.ObjectIconWidget
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.ui.library.views.list.LibraryListDefaults
@ -293,20 +289,3 @@ object ItemDefaults {
val ITEM_HEIGHT = 52.dp
val TEXT_PADDING_START = 10.dp
}
@Composable
fun Modifier.noRippleClickable(
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
indication: Indication? = null,
enabled: Boolean = true,
onClickLabel: String? = null,
role: Role? = null,
onClick: () -> Unit,
) = clickable(
interactionSource = interactionSource,
indication = indication,
enabled = enabled,
onClickLabel = onClickLabel,
role = role,
onClick = onClick,
)

View file

@ -47,7 +47,7 @@ import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_ui.foundation.Dragger
import com.anytypeio.anytype.presentation.relations.RelationEditState
import com.anytypeio.anytype.presentation.relations.RelationEditViewModel
import com.anytypeio.anytype.ui.library.views.list.items.noRippleClickable
import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
import com.anytypeio.anytype.ui.relations.TypeEditWidgetDefaults.OffsetX
import com.anytypeio.anytype.ui.relations.TypeEditWidgetDefaults.PaddingStart
import com.anytypeio.anytype.ui.relations.RelationScreenDefaults.PaddingBottom

View file

@ -25,7 +25,7 @@ import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_ui.foundation.Dragger
import com.anytypeio.anytype.presentation.types.TypeCreationViewModel
import com.anytypeio.anytype.ui.library.views.list.items.noRippleClickable
import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
import com.anytypeio.anytype.ui.settings.fonts
import com.anytypeio.anytype.ui.settings.typography
import com.anytypeio.anytype.ui.types.views.HeaderDefaults.ColorTextActive

View file

@ -1,6 +1,5 @@
package com.anytypeio.anytype.ui.types.views
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
@ -24,7 +23,7 @@ import androidx.compose.ui.unit.sp
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_ui.foundation.Dragger
import com.anytypeio.anytype.presentation.types.TypeEditViewModel
import com.anytypeio.anytype.ui.library.views.list.items.noRippleClickable
import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
import com.anytypeio.anytype.ui.settings.fonts
import com.anytypeio.anytype.ui.settings.typography

View file

@ -21,7 +21,7 @@ import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.anytypeio.anytype.R
import com.anytypeio.anytype.ui.library.views.list.items.noRippleClickable
import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
@Composable
fun WidgetMenu(

View file

@ -29,8 +29,8 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
import com.anytypeio.anytype.presentation.widgets.WidgetView
import com.anytypeio.anytype.ui.library.views.list.items.noRippleClickable
import com.anytypeio.anytype.ui.widgets.menu.DropDownMenuAction
import com.anytypeio.anytype.ui.widgets.menu.WidgetMenu

View file

@ -33,7 +33,7 @@ import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.presentation.widgets.ViewId
import com.anytypeio.anytype.presentation.widgets.WidgetId
import com.anytypeio.anytype.presentation.widgets.WidgetView
import com.anytypeio.anytype.ui.library.views.list.items.noRippleClickable
import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
import com.anytypeio.anytype.ui.widgets.menu.DropDownMenuAction
import com.anytypeio.anytype.ui.widgets.menu.WidgetMenu

View file

@ -45,7 +45,7 @@ import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.presentation.widgets.TreePath
import com.anytypeio.anytype.presentation.widgets.WidgetId
import com.anytypeio.anytype.presentation.widgets.WidgetView
import com.anytypeio.anytype.ui.library.views.list.items.noRippleClickable
import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
import com.anytypeio.anytype.ui.widgets.menu.DropDownMenuAction
import com.anytypeio.anytype.ui.widgets.menu.WidgetMenu

View file

@ -0,0 +1,26 @@
package com.anytypeio.anytype.core_ui.foundation
import androidx.compose.foundation.Indication
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.Role
@Composable
fun Modifier.noRippleClickable(
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
indication: Indication? = null,
enabled: Boolean = true,
onClickLabel: String? = null,
role: Role? = null,
onClick: () -> Unit,
) = clickable(
interactionSource = interactionSource,
indication = indication,
enabled = enabled,
onClickLabel = onClickLabel,
role = role,
onClick = onClick,
)

View file

@ -0,0 +1,74 @@
package com.anytypeio.anytype.core_ui.foundation.components
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.foundation.components.BottomNavigationDefaults.Height
import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
@Composable
fun BottomNavigationMenu(
modifier: Modifier = Modifier,
backClick: () -> Unit = {},
homeClick: () -> Unit = {},
searchClick: () -> Unit = {},
addDocClick: () -> Unit = {},
) {
Row(
modifier = modifier
.height(Height)
.fillMaxWidth()
.background(color = colorResource(id = R.color.background_primary))
/**
* Workaround for clicks through the bottom navigation menu.
*/
.noRippleClickable { },
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
) {
MenuItem(BottomNavigationItem.BACK.res, onClick = backClick)
MenuItem(BottomNavigationItem.HOME.res, onClick = homeClick)
MenuItem(BottomNavigationItem.SEARCH.res, onClick = searchClick)
MenuItem(BottomNavigationItem.ADD_DOC.res, onClick = addDocClick)
}
}
@Composable
private fun MenuItem(
@DrawableRes res: Int,
onClick: () -> Unit = {}
) {
Image(
painter = painterResource(id = res),
contentDescription = "",
modifier = Modifier.noRippleClickable {
onClick.invoke()
}
)
}
private enum class BottomNavigationItem(@DrawableRes val res: Int) {
BACK(R.drawable.ic_main_toolbar_back),
HOME(R.drawable.ic_main_toolbar_home),
SEARCH(R.drawable.ic_main_toolbar_search),
ADD_DOC(R.drawable.ic_page_toolbar_add_doc)
}
@Immutable
private object BottomNavigationDefaults {
val Height = 48.dp
}

View file

@ -2,6 +2,12 @@ package com.anytypeio.anytype.presentation.library
sealed class LibraryEvent {
sealed class BottomMenu : LibraryEvent() {
class Back : BottomMenu()
class Search: BottomMenu()
class AddDoc: BottomMenu()
}
sealed class Query(open val query: String) : LibraryEvent() {
class MyTypes(override val query: String) : Query(query)
class LibraryTypes(override val query: String) : Query(query)

View file

@ -8,6 +8,7 @@ import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_utils.ext.orNull
import com.anytypeio.anytype.domain.base.fold
import com.anytypeio.anytype.domain.`object`.SetObjectDetails
import com.anytypeio.anytype.domain.page.CreateObject
import com.anytypeio.anytype.domain.workspace.AddObjectToWorkspace
import com.anytypeio.anytype.domain.workspace.RemoveObjectsFromWorkspace
import com.anytypeio.anytype.presentation.library.delegates.LibraryRelationsDelegate
@ -32,7 +33,8 @@ class LibraryViewModel(
private val addObjectToWorkspace: AddObjectToWorkspace,
private val removeObjectsFromWorkspace: RemoveObjectsFromWorkspace,
private val resourceManager: LibraryResourceManager,
private val setObjectDetails: SetObjectDetails
private val setObjectDetails: SetObjectDetails,
private val createObject: CreateObject
) : NavigationViewModel<LibraryViewModel.Navigation>() {
private val uiEvents = MutableStateFlow<LibraryEvent>(LibraryEvent.Query.MyTypes(""))
@ -83,11 +85,32 @@ class LibraryViewModel(
is LibraryEvent.ToggleInstall -> proceedWithToggleInstall(it.item)
is LibraryEvent.Type -> proceedWithTypeActions(it)
is LibraryEvent.Relation -> proceedWithRelationActions(it)
is LibraryEvent.BottomMenu -> proceedWithBottomMenuActions(it)
}
}
}
}
private fun proceedWithBottomMenuActions(it: LibraryEvent.BottomMenu) {
when (it) {
is LibraryEvent.BottomMenu.Back -> navigate(Navigation.Back())
is LibraryEvent.BottomMenu.Search -> navigate(Navigation.Search())
is LibraryEvent.BottomMenu.AddDoc -> proceedWithCreateDoc()
}
}
private fun proceedWithCreateDoc() {
viewModelScope.launch {
createObject.execute(CreateObject.Param(type = null))
.fold(
onSuccess = { result ->
navigate(Navigation.CreateDoc(result.objectId))
},
onFailure = { e -> Timber.e(e, "Error while creating a new page") }
)
}
}
private fun proceedQueryEvent(event: LibraryEvent.Query) {
when (event) {
is LibraryEvent.Query.MyTypes -> {
@ -284,7 +307,8 @@ class LibraryViewModel(
private val addObjectToWorkspace: AddObjectToWorkspace,
private val removeObjectsFromWorkspace: RemoveObjectsFromWorkspace,
private val resourceManager: LibraryResourceManager,
private val setObjectDetails: SetObjectDetails
private val setObjectDetails: SetObjectDetails,
private val createObject: CreateObject
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@ -296,7 +320,8 @@ class LibraryViewModel(
addObjectToWorkspace,
removeObjectsFromWorkspace,
resourceManager,
setObjectDetails
setObjectDetails,
createObject
) as T
}
}
@ -317,6 +342,12 @@ class LibraryViewModel(
class OpenRelationEditing(
val view: LibraryView.MyRelationView
) : Navigation()
class Back : Navigation()
class Search: Navigation()
class CreateDoc(val id: Id): Navigation()
}
sealed class Effect {