Skip to content

Commit fcc57d6

Browse files
Merge branch 'anyproto:main' into app-widget
2 parents 243ec93 + f1348de commit fcc57d6

File tree

6 files changed

+77
-19
lines changed

6 files changed

+77
-19
lines changed

app/src/main/java/com/anytypeio/anytype/ui/sets/ObjectSetFragment.kt

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import android.content.Intent
44
import android.content.res.ColorStateList
55
import android.graphics.Color
66
import android.net.Uri
7-
import android.os.Build
87
import android.os.Bundle
98
import android.text.InputType
109
import android.view.ContextThemeWrapper
@@ -48,7 +47,6 @@ import androidx.navigation.fragment.findNavController
4847
import androidx.recyclerview.widget.DividerItemDecoration
4948
import androidx.recyclerview.widget.RecyclerView
5049
import coil3.load
51-
import com.anytypeio.anytype.BuildConfig
5250
import com.anytypeio.anytype.R
5351
import com.anytypeio.anytype.core_models.Id
5452
import com.anytypeio.anytype.core_models.Key
@@ -57,7 +55,6 @@ import com.anytypeio.anytype.core_models.TimeInMillis
5755
import com.anytypeio.anytype.core_models.multiplayer.SpaceSyncAndP2PStatusState
5856
import com.anytypeio.anytype.core_models.primitives.SpaceId
5957
import com.anytypeio.anytype.core_ui.extensions.setEmojiOrNull
60-
import com.anytypeio.anytype.core_utils.clipboard.copyPlainTextToClipboard
6158
import com.anytypeio.anytype.core_ui.features.dataview.ViewerGridAdapter
6259
import com.anytypeio.anytype.core_ui.features.dataview.ViewerGridHeaderAdapter
6360
import com.anytypeio.anytype.core_ui.menu.ObjectHeaderContextMenu
@@ -80,6 +77,7 @@ import com.anytypeio.anytype.core_ui.widgets.dv.ViewersWidget
8077
import com.anytypeio.anytype.core_ui.widgets.text.TextInputWidget
8178
import com.anytypeio.anytype.core_ui.widgets.toolbar.DataViewInfo
8279
import com.anytypeio.anytype.core_utils.OnSwipeListener
80+
import com.anytypeio.anytype.core_utils.clipboard.copyPlainTextToClipboard
8381
import com.anytypeio.anytype.core_utils.ext.arg
8482
import com.anytypeio.anytype.core_utils.ext.argOrNull
8583
import com.anytypeio.anytype.core_utils.ext.argString
@@ -90,6 +88,7 @@ import com.anytypeio.anytype.core_utils.ext.hideKeyboard
9088
import com.anytypeio.anytype.core_utils.ext.hideSoftInput
9189
import com.anytypeio.anytype.core_utils.ext.invisible
9290
import com.anytypeio.anytype.core_utils.ext.safeNavigate
91+
import com.anytypeio.anytype.core_utils.ext.smoothScrollToFirst
9392
import com.anytypeio.anytype.core_utils.ext.startMarketPageOrWeb
9493
import com.anytypeio.anytype.core_utils.ext.subscribe
9594
import com.anytypeio.anytype.core_utils.ext.syncFocusWithImeVisibility
@@ -103,7 +102,7 @@ import com.anytypeio.anytype.di.common.componentManager
103102
import com.anytypeio.anytype.di.feature.DefaultComponentParam
104103
import com.anytypeio.anytype.presentation.editor.cover.CoverColor
105104
import com.anytypeio.anytype.presentation.editor.cover.CoverGradient
106-
import com.anytypeio.anytype.presentation.editor.editor.listener.ListenerType.Relation.*
105+
import com.anytypeio.anytype.presentation.editor.editor.listener.ListenerType.Relation.SetQuery
107106
import com.anytypeio.anytype.presentation.relations.value.tagstatus.RelationContext
108107
import com.anytypeio.anytype.presentation.sets.DataViewViewState
109108
import com.anytypeio.anytype.presentation.sets.ObjectSetCommand
@@ -124,8 +123,8 @@ import com.anytypeio.anytype.ui.objects.creation.ObjectTypeSelectionFragment
124123
import com.anytypeio.anytype.ui.objects.types.pickers.DataViewSelectSourceFragment
125124
import com.anytypeio.anytype.ui.objects.types.pickers.EmptyDataViewSelectSourceFragment
126125
import com.anytypeio.anytype.ui.objects.types.pickers.ObjectSelectTypeFragment
127-
import com.anytypeio.anytype.ui.objects.types.pickers.OnDataViewSelectSourceAction
128126
import com.anytypeio.anytype.ui.objects.types.pickers.ObjectTypeSelectionListener
127+
import com.anytypeio.anytype.ui.objects.types.pickers.OnDataViewSelectSourceAction
129128
import com.anytypeio.anytype.ui.relations.RelationDateValueFragment
130129
import com.anytypeio.anytype.ui.relations.RelationDateValueFragment.DateValueEditReceiver
131130
import com.anytypeio.anytype.ui.relations.RelationTextValueFragment
@@ -140,7 +139,6 @@ import com.anytypeio.anytype.ui.templates.EditorTemplateFragment.Companion.ARG_T
140139
import com.anytypeio.anytype.ui.templates.EditorTemplateFragment.Companion.ARG_TARGET_TYPE_KEY
141140
import com.anytypeio.anytype.ui.templates.EditorTemplateFragment.Companion.ARG_TEMPLATE_ID
142141
import javax.inject.Inject
143-
import kotlinx.coroutines.flow.filterNotNull
144142
import kotlinx.coroutines.flow.launchIn
145143
import kotlinx.coroutines.flow.onEach
146144
import timber.log.Timber
@@ -895,6 +893,31 @@ open class ObjectSetFragment :
895893
}
896894
}
897895

896+
private fun scrollToObject(objectId: Id) {
897+
val viewer = when (val state = vm.currentViewer.value) {
898+
is DataViewViewState.Collection.Default -> state.viewer
899+
is DataViewViewState.Set.Default -> state.viewer
900+
is DataViewViewState.TypeSet.Default -> state.viewer
901+
else -> null
902+
}
903+
when (viewer) {
904+
is Viewer.GridView -> {
905+
rvRows.smoothScrollToFirst(viewer.rows) { it.id == objectId }
906+
}
907+
908+
is Viewer.GalleryView -> {
909+
binding.galleryView.smoothScrollToFirst(viewer.items) { it.objectId == objectId }
910+
}
911+
912+
is Viewer.ListView -> {
913+
binding.listView.smoothScrollToFirst(viewer.items) { it.objectId == objectId }
914+
}
915+
916+
else -> { /* no scroll for unsupported views */
917+
}
918+
}
919+
}
920+
898921
private fun bindHeader(header: SetOrCollectionHeaderState.Default) {
899922
setupHeaderMargins(header)
900923

@@ -1342,6 +1365,9 @@ open class ObjectSetFragment :
13421365
successToast = getString(R.string.link_copied)
13431366
)
13441367
}
1368+
is ObjectSetCommand.ScrollToObject -> {
1369+
scrollToObject(command.objectId)
1370+
}
13451371
}
13461372
}
13471373

core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/AndroidExtension.kt

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,10 @@ import android.util.TypedValue
2626
import android.util.TypedValue.COMPLEX_UNIT_DIP
2727
import android.view.TouchDelegate
2828
import android.view.View
29-
import android.view.ViewGroup
3029
import android.view.ViewTreeObserver
3130
import android.view.WindowManager
3231
import android.view.inputmethod.InputMethodManager
3332
import android.widget.EditText
34-
import android.widget.TextView
3533
import androidx.activity.result.contract.ActivityResultContract
3634
import androidx.annotation.DimenRes
3735
import androidx.annotation.IdRes
@@ -41,7 +39,6 @@ import androidx.core.graphics.BlendModeCompat
4139
import androidx.core.view.updateLayoutParams
4240
import androidx.fragment.app.Fragment
4341
import androidx.navigation.NavController
44-
import androidx.navigation.NavDirections
4542
import androidx.recyclerview.widget.RecyclerView
4643
import com.anytypeio.anytype.core_utils.const.FileConstants.REQUEST_FILE_SAF_CODE
4744
import com.anytypeio.anytype.core_utils.const.FileConstants.REQUEST_MEDIA_CODE
@@ -81,6 +78,22 @@ fun RecyclerView.ViewHolder.res(@StringRes res: Int): String {
8178
return itemView.context.resources.getString(res)
8279
}
8380

81+
/**
82+
* Scrolls to the first item matching the predicate.
83+
* Uses [View.post] to ensure adapter updates complete before scrolling.
84+
*/
85+
fun <T> RecyclerView.smoothScrollToFirst(
86+
items: List<T>,
87+
predicate: (T) -> Boolean
88+
) {
89+
val position = items.indexOfFirst(predicate)
90+
if (position >= 0) {
91+
post {
92+
smoothScrollToPosition(position)
93+
}
94+
}
95+
}
96+
8497
@Deprecated("Fix getColumnIndex() issue!")
8598
fun Uri.parsePath(context: Context): String {
8699

presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetCommand.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,6 @@ sealed class ObjectSetCommand {
130130
data class Browse(val url: String) : ObjectSetCommand()
131131

132132
data class CopyLinkToClipboard(val link: String) : ObjectSetCommand()
133+
134+
data class ScrollToObject(val objectId: Id) : ObjectSetCommand()
133135
}

presentation/src/main/java/com/anytypeio/anytype/presentation/sets/ObjectSetViewModel.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,9 @@ class ObjectSetViewModel(
243243

244244
private val selectedTypeFlow: MutableStateFlow<ObjectWrapper.Type?> = MutableStateFlow(null)
245245

246+
247+
private val pendingScrollToObject = MutableStateFlow<Id?>(null)
248+
246249
val navPanelState = permission.map { permission ->
247250
NavPanelState.fromPermission(
248251
permission = permission,
@@ -702,6 +705,10 @@ class ObjectSetViewModel(
702705
}.distinctUntilChanged().collect { viewState ->
703706
Timber.d("subscribeToDataViewViewer, newViewerState:[$viewState]")
704707
_currentViewer.value = viewState
708+
pendingScrollToObject.value?.let { objectId ->
709+
pendingScrollToObject.value = null
710+
dispatch(ObjectSetCommand.ScrollToObject(objectId))
711+
}
705712
}
706713
}
707714
}
@@ -1571,6 +1578,7 @@ class ObjectSetViewModel(
15711578
onFailure = { Timber.e(it, "Error while creating new record") },
15721579
onSuccess = { result ->
15731580
action?.invoke(result)
1581+
pendingScrollToObject.value = result.objectId
15741582
proceedWithNewDataViewObject(result)
15751583
sendAnalyticsObjectCreateEvent(
15761584
startTime = startTime,

presentation/src/main/java/com/anytypeio/anytype/presentation/sets/SearchRelationViewModel.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ abstract class SearchRelationViewModel(
6161
/**
6262
* Fetches properties from the DataView's relationLinks and maps them to [SimpleRelationView].
6363
* After mapping, filters out relations that are:
64-
* - Hidden (isHidden = true)
64+
* - Hidden (isHidden = true), except for Name and Done which are always shown
6565
* - Of disallowed formats (RELATIONS, EMOJI, UNDEFINED)
6666
*
6767
* @see notAllowedRelations for filtering logic
@@ -98,7 +98,7 @@ abstract class SearchRelationViewModel(
9898

9999
private fun notAllowedRelations(relation: SimpleRelationView): Boolean =
100100
notAllowedRelationFormats.contains(relation.format)
101-
|| (relation.isHidden)
101+
|| (relation.key != Relations.NAME && relation.key != Relations.DONE && relation.isHidden)
102102

103103
fun onSearchQueryChanged(txt: String) {
104104
viewModelScope.launch { query.send(txt) }

presentation/src/test/java/com/anytypeio/anytype/presentation/sets/SearchRelationViewModelTest.kt

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ import com.anytypeio.anytype.domain.objects.DefaultStoreOfRelations
1212
import com.anytypeio.anytype.domain.objects.StoreOfRelations
1313
import com.anytypeio.anytype.presentation.MockRelationFactory.relationCustomNumber
1414
import com.anytypeio.anytype.presentation.MockRelationFactory.relationCustomText
15+
import com.anytypeio.anytype.presentation.MockRelationFactory.relationDone
1516
import com.anytypeio.anytype.presentation.MockRelationFactory.relationIconEmoji
1617
import com.anytypeio.anytype.presentation.MockRelationFactory.relationLastModifiedDate
18+
import com.anytypeio.anytype.presentation.MockRelationFactory.relationLayout
1719
import com.anytypeio.anytype.presentation.MockRelationFactory.relationName
1820
import com.anytypeio.anytype.presentation.MockRelationFactory.relationStatus
1921
import com.anytypeio.anytype.presentation.MockRelationFactory.relationTag
@@ -441,10 +443,13 @@ class SearchRelationViewModelTest {
441443

442444
@ExperimentalTime
443445
@Test
444-
fun `hidden relations are filtered out from results`() = runTest {
446+
fun `hidden relations are filtered out from results except Name and Done`() = runTest {
445447
// SETUP - include both hidden and non-hidden relations
446-
// relationName has isHidden=true, relationStatus has isHidden=false
447-
val relations = listOf(relationName, relationStatus)
448+
// relationName has isHidden=true but should appear (special case)
449+
// relationDone has isHidden=true but should appear (special case)
450+
// relationStatus has isHidden=false and should appear
451+
// relationLayout has isHidden=true and should be filtered out
452+
val relations = listOf(relationName, relationDone, relationStatus, relationLayout)
448453

449454
val state = MutableStateFlow(
450455
MockObjectSetFactory.makeDefaultSetObjectState(
@@ -461,14 +466,18 @@ class SearchRelationViewModelTest {
461466

462467
vm.onStart(viewerId = viewerId)
463468

464-
// TESTING - only non-hidden relations should appear
469+
// TESTING - Name and Done should appear despite being hidden, but relationLayout should be filtered
465470
vm.views.test {
466471
delay(100)
467472
val result = awaitItem()
468-
// Only relationStatus should appear (isHidden=false)
469-
// relationName should be filtered out (isHidden=true)
470-
assertEquals(1, result.size)
471-
assertEquals("Status", result.first().title)
473+
// Should have relationName, relationDone, and relationStatus
474+
// relationLayout should be filtered out
475+
assertEquals(3, result.size)
476+
val titles = result.map { it.title }
477+
assert(titles.contains("Name")) { "Name should be visible despite isHidden=true" }
478+
assert(titles.contains("Done")) { "Done should be visible despite isHidden=true" }
479+
assert(titles.contains("Status")) { "Status should be visible" }
480+
assert(!titles.contains("Layout")) { "Layout should be filtered out (hidden, not special case)" }
472481
cancelAndConsumeRemainingEvents()
473482
}
474483
}

0 commit comments

Comments
 (0)