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

DROID-2545 Global search | Enhancement | Added objects icons and navigation for new object search (#1253)

This commit is contained in:
Evgenii Kozlov 2024-06-03 16:35:48 +02:00 committed by GitHub
parent 7dd731d95e
commit 560404bd6d
Signed by: github
GPG key ID: B5690EEEBB952194
5 changed files with 224 additions and 45 deletions

View file

@ -144,9 +144,9 @@ class Navigator : AppNavigation {
override fun openPageSearch() {
// Old search
navController?.navigate(R.id.pageSearchFragment)
// navController?.navigate(R.id.pageSearchFragment)
// Uncomment to use new search
// navController?.navigate(R.id.globalSearchScreen)
navController?.navigate(R.id.globalSearchScreen)
}
override fun openObjectSet(

View file

@ -18,6 +18,7 @@ import com.anytypeio.anytype.di.common.componentManager
import com.anytypeio.anytype.presentation.home.OpenObjectNavigation
import com.anytypeio.anytype.presentation.search.GlobalSearchViewModel
import com.anytypeio.anytype.ui.editor.EditorFragment
import com.anytypeio.anytype.ui.sets.ObjectSetFragment
import com.anytypeio.anytype.ui.settings.typography
import javax.inject.Inject
@ -61,7 +62,7 @@ class GlobalSearchFragment : BaseBottomSheetComposeFragment() {
dismiss()
findNavController().navigate(
R.id.dataViewNavigation,
EditorFragment.args(
ObjectSetFragment.args(
ctx = nav.target,
space = nav.space
)
@ -78,7 +79,7 @@ class GlobalSearchFragment : BaseBottomSheetComposeFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupBottomSheetBehavior(0)
setupBottomSheetBehavior(DEFAULT_PADDING_TOP)
}
override fun injectDependencies() {

View file

@ -2,19 +2,25 @@ package com.anytypeio.anytype.ui.search
import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
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.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Text
import androidx.compose.material.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@ -22,28 +28,44 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.ThemeColor
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.core_ui.common.keyboardAsState
import com.anytypeio.anytype.core_ui.extensions.dark
import com.anytypeio.anytype.core_ui.extensions.light
import com.anytypeio.anytype.core_ui.foundation.Divider
import com.anytypeio.anytype.core_ui.foundation.Dragger
import com.anytypeio.anytype.core_ui.views.BodyRegular
import com.anytypeio.anytype.core_ui.views.PreviewTitle2Medium
import com.anytypeio.anytype.core_ui.views.Relations2
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.DefaultTaskObjectIcon
import com.anytypeio.anytype.core_ui.widgets.defaultProfileIconImage
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.presentation.search.GlobalSearchItemView
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun GlobalSearchScreen(
items: List<GlobalSearchItemView>,
@ -51,36 +73,108 @@ fun GlobalSearchScreen(
onObjectClicked: (GlobalSearchItemView) -> Unit
) {
var query by remember { mutableStateOf("") }
val isKeyboardOpen by keyboardAsState()
Column(
modifier = Modifier
.fillMaxSize()
.nestedScroll(rememberNestedScrollInteropConnection())
) {
BasicTextField(
value = query,
val interactionSource = remember { MutableInteractionSource() }
val focus = LocalFocusManager.current
Dragger(
modifier = Modifier.padding(vertical = 6.dp).align(Alignment.CenterHorizontally)
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
textStyle = BodyRegular.copy(
color = colorResource(id = R.color.text_primary)
),
onValueChange = {
query = it.also {
onQueryChanged(it)
.padding(
top = 10.dp,
start = 16.dp,
end = 16.dp
)
.background(
color = colorResource(id = R.color.shape_transparent),
shape = RoundedCornerShape(10.dp)
)
.height(40.dp)
) {
Image(
painter = painterResource(id = R.drawable.ic_search_18),
contentDescription = "Search icon",
modifier = Modifier
.align(Alignment.CenterVertically)
.padding(
start = 11.dp
)
)
BasicTextField(
value = query,
modifier = Modifier
.fillMaxWidth()
.padding(start = 6.dp)
.align(Alignment.CenterVertically)
,
textStyle = BodyRegular.copy(
color = colorResource(id = R.color.text_primary)
),
onValueChange = {
query = it.also {
onQueryChanged(it)
}
},
singleLine = true,
maxLines = 1,
decorationBox = @Composable { innerTextField ->
TextFieldDefaults.OutlinedTextFieldDecorationBox(
value = query,
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 = colorResource(id = R.color.shape_transparent)
),
border = {},
contentPadding = PaddingValues()
)
}
},
)
)
}
LazyColumn(
modifier = Modifier.weight(1.0f)
) {
items.forEachIndexed { idx, item ->
item(key = item.id) {
if (idx == 0) {
Spacer(modifier = Modifier.height(10.dp))
}
GlobalSearchItem(
globalSearchItemView = item,
onObjectClicked = onObjectClicked
onObjectClicked = {
if (isKeyboardOpen) {
focus.clearFocus(true)
}
focus.clearFocus(true)
onObjectClicked(it)
}
)
if (idx != items.lastIndex) {
Divider(paddingStart = 16.dp, paddingEnd = 16.dp)
} else {
Spacer(modifier = Modifier.height(48.dp))
}
}
}
@ -94,19 +188,24 @@ private fun GlobalSearchItem(
onObjectClicked: (GlobalSearchItemView) -> Unit
) {
Box(
modifier = Modifier.fillMaxWidth().clickable {
onObjectClicked(globalSearchItemView)
}
modifier = Modifier
.fillMaxWidth()
.clickable {
onObjectClicked(globalSearchItemView)
}
) {
Box(
GlobalSearchObjectIcon(
icon = globalSearchItemView.icon,
iconSize = 48.dp,
modifier = Modifier
.padding(
start = 16.dp,
top = 12.dp,
bottom = 12.dp
)
.size(48.dp)
.background(Color.Red)
),
onTaskIconClicked = {
// Do nothing
}
)
Column(
modifier = Modifier
@ -287,6 +386,35 @@ private fun DefaultMetaStatusRelation(
)
}
@Composable
fun GlobalSearchObjectIcon(
icon: ObjectIcon,
modifier: Modifier,
iconSize: Dp = 48.dp,
onTaskIconClicked: (Boolean) -> Unit = {}
) {
when (icon) {
is ObjectIcon.Profile.Avatar -> DefaultProfileAvatarIcon(modifier, iconSize, icon)
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
)
}
else -> {
// Draw nothing.
}
}
}
@Preview(
name = "Dark Mode",
showBackground = true,
@ -306,7 +434,8 @@ private fun DefaultGlobalSearchItemViewPreview() {
title = "Autechre",
type = "Band",
meta = GlobalSearchItemView.Meta.None,
layout = ObjectType.Layout.BASIC
layout = ObjectType.Layout.BASIC,
icon = ObjectIcon.Basic.Avatar("A")
),
onObjectClicked = {}
)
@ -334,7 +463,8 @@ private fun DefaultGlobalSearchItemViewWithBlockMetaPreview() {
snippet = "Autechre are an English electronic music duo consisting of Rob Brown and Sean Booth, both from Rochdale, Greater Manchester. ",
highlights = emptyList()
),
layout = ObjectType.Layout.BASIC
layout = ObjectType.Layout.BASIC,
icon = ObjectIcon.Basic.Avatar("A")
),
onObjectClicked = {}
)
@ -365,7 +495,8 @@ private fun DefaultGlobalSearchItemViewBlockTwoHighlightsMetaPreview() {
IntRange(15, 23)
)
),
layout = ObjectType.Layout.BASIC
layout = ObjectType.Layout.BASIC,
icon = ObjectIcon.Basic.Avatar("A")
),
onObjectClicked = {}
)
@ -397,7 +528,8 @@ private fun DefaultGlobalSearchItemViewRelationTwoHighlightsMetaPreview() {
IntRange(15, 23)
)
),
layout = ObjectType.Layout.BASIC
layout = ObjectType.Layout.BASIC,
icon = ObjectIcon.Basic.Avatar("A")
),
onObjectClicked = {}
)
@ -426,7 +558,8 @@ private fun DefaultGlobalSearchItemViewTagRelationPreview() {
value = "IDM",
color = ThemeColor.TEAL
),
layout = ObjectType.Layout.BASIC
layout = ObjectType.Layout.BASIC,
icon = ObjectIcon.Basic.Avatar("A")
),
onObjectClicked = {}
)
@ -455,7 +588,8 @@ private fun DefaultGlobalSearchItemViewStatusRelationPreview() {
value = "IDM",
color = ThemeColor.TEAL
),
layout = ObjectType.Layout.BASIC
layout = ObjectType.Layout.BASIC,
icon = ObjectIcon.Basic.Avatar("A")
),
onObjectClicked = {}
)
@ -483,7 +617,8 @@ private fun DefaultGlobalSearchItemViewStatusRelationScreenPreview() {
title = "Autechre",
type = "Band",
meta = GlobalSearchItemView.Meta.None,
layout = ObjectType.Layout.BASIC
layout = ObjectType.Layout.BASIC,
icon = ObjectIcon.Basic.Avatar("A")
),
GlobalSearchItemView(
id = "ID2",
@ -495,7 +630,8 @@ private fun DefaultGlobalSearchItemViewStatusRelationScreenPreview() {
value = "IDM",
color = ThemeColor.TEAL
),
layout = ObjectType.Layout.BASIC
layout = ObjectType.Layout.BASIC,
icon = ObjectIcon.Basic.Avatar("A")
),
GlobalSearchItemView(
id = "ID3",
@ -507,7 +643,8 @@ private fun DefaultGlobalSearchItemViewStatusRelationScreenPreview() {
value = "IDM",
color = ThemeColor.TEAL
),
layout = ObjectType.Layout.BASIC
layout = ObjectType.Layout.BASIC,
icon = ObjectIcon.Basic.Avatar("A")
),
GlobalSearchItemView(
id = "ID4",
@ -522,7 +659,8 @@ private fun DefaultGlobalSearchItemViewStatusRelationScreenPreview() {
IntRange(15, 23)
)
),
layout = ObjectType.Layout.BASIC
layout = ObjectType.Layout.BASIC,
icon = ObjectIcon.Basic.Avatar("A")
),
GlobalSearchItemView(
id = "ID5",
@ -533,7 +671,8 @@ private fun DefaultGlobalSearchItemViewStatusRelationScreenPreview() {
snippet = "Autechre are an English electronic music duo consisting of Rob Brown and Sean Booth, both from Rochdale, Greater Manchester. ",
highlights = emptyList()
),
layout = ObjectType.Layout.BASIC
layout = ObjectType.Layout.BASIC,
icon = ObjectIcon.Basic.Avatar("A")
)
),
onObjectClicked = {}

View file

@ -57,7 +57,7 @@ fun ListWidgetObjectIcon(
}
@Composable
private fun DefaultTaskObjectIcon(
fun DefaultTaskObjectIcon(
modifier: Modifier,
iconSize: Dp,
icon: ObjectIcon.Task,
@ -82,7 +82,7 @@ private fun DefaultTaskObjectIcon(
}
@Composable
private fun DefaultObjectImageIcon(
fun DefaultObjectImageIcon(
url: Url,
modifier: Modifier,
iconSize: Dp
@ -98,7 +98,7 @@ private fun DefaultObjectImageIcon(
}
@Composable
private fun DefaultObjectBookmarkIcon(
fun DefaultObjectBookmarkIcon(
url: Url,
modifier: Modifier,
iconSize: Dp
@ -115,7 +115,7 @@ private fun DefaultObjectBookmarkIcon(
}
@Composable
private fun DefaultProfileAvatarIcon(
fun DefaultProfileAvatarIcon(
modifier: Modifier,
iconSize: Dp,
icon: ObjectIcon.Profile.Avatar
@ -145,7 +145,37 @@ private fun DefaultProfileAvatarIcon(
}
@Composable
private fun defaultProfileIconImage(
fun DefaultBasicAvatarIcon(
modifier: Modifier,
iconSize: Dp,
icon: ObjectIcon.Basic.Avatar
) {
Box(
modifier = modifier
.size(iconSize)
.background(
shape = RoundedCornerShape(12.dp),
color = colorResource(id = R.color.text_tertiary)
)
) {
Text(
text = icon
.name
.ifEmpty { stringResource(id = R.string.u) }
.take(1)
.uppercase(),
modifier = Modifier.align(Alignment.Center),
style = TextStyle(
fontSize = 28.sp,
fontWeight = FontWeight.SemiBold,
color = colorResource(id = R.color.text_white)
)
)
}
}
@Composable
fun defaultProfileIconImage(
icon: ObjectIcon.Profile.Image,
modifier: Modifier,
iconSize: Dp
@ -160,7 +190,7 @@ private fun defaultProfileIconImage(
}
@Composable
private fun DefaultEmojiObjectIcon(
fun DefaultEmojiObjectIcon(
modifier: Modifier,
iconSize: Dp,
icon: ObjectIcon.Basic.Emoji
@ -184,7 +214,7 @@ private fun DefaultEmojiObjectIcon(
}
@Composable
private fun DefaultFileObjectImageIcon(
fun DefaultFileObjectImageIcon(
fileName: String,
mime: String,
modifier: Modifier,

View file

@ -33,6 +33,7 @@ import com.anytypeio.anytype.domain.workspace.SpaceManager
import com.anytypeio.anytype.presentation.common.BaseViewModel
import com.anytypeio.anytype.presentation.home.OpenObjectNavigation
import com.anytypeio.anytype.presentation.home.navigation
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.presentation.objects.getProperName
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableSharedFlow
@ -88,7 +89,8 @@ class GlobalSearchViewModel(
views.value = results.mapNotNull { result ->
result.view(
storeOfObjectTypes = storeOfObjectTypes,
storeOfRelations = storeOfRelations
storeOfRelations = storeOfRelations,
urlBuilder = urlBuilder
)
}
}
@ -136,6 +138,7 @@ class GlobalSearchViewModel(
*/
data class GlobalSearchItemView(
val id: Id,
val icon: ObjectIcon,
val space: SpaceId,
val layout: ObjectType.Layout,
val title: String,
@ -168,7 +171,8 @@ data class GlobalSearchItemView(
suspend fun Command.SearchWithMeta.Result.view(
storeOfObjectTypes: StoreOfObjectTypes,
storeOfRelations: StoreOfRelations
storeOfRelations: StoreOfRelations,
urlBuilder: UrlBuilder
) : GlobalSearchItemView? {
if (wrapper.spaceId == null) return null
if (wrapper.layout == null) return null
@ -176,6 +180,11 @@ suspend fun Command.SearchWithMeta.Result.view(
val meta = metas.firstOrNull()
return GlobalSearchItemView(
id = obj,
icon = ObjectIcon.from(
obj = wrapper,
layout = wrapper.layout,
builder = urlBuilder
),
space = SpaceId(requireNotNull(wrapper.spaceId)),
layout = requireNotNull(wrapper.layout),
title = wrapper.getProperName(),