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

DROID-3409 Primitives | Layout conflict screen, part 1 (#2212)

This commit is contained in:
Konstantin Ivanov 2025-03-31 15:20:50 +02:00 committed by GitHub
parent bef75d1cbf
commit 6b5b8a46d0
Signed by: github
GPG key ID: B5690EEEBB952194
8 changed files with 361 additions and 163 deletions

View file

@ -5,7 +5,9 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.core.os.bundleOf
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.anytypeio.anytype.R
@ -25,6 +27,8 @@ import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetFragment
import com.anytypeio.anytype.core_utils.ui.proceed
import com.anytypeio.anytype.core_utils.ui.showActionableSnackBar
import com.anytypeio.anytype.databinding.FragmentObjectMenuBinding
import com.anytypeio.anytype.feature_object_type.ui.conflict.ConflictScreen
import com.anytypeio.anytype.feature_object_type.ui.conflict.ConflictScreenPreview
import com.anytypeio.anytype.presentation.objects.ObjectIcon
import com.anytypeio.anytype.presentation.objects.menu.ObjectMenuOptionsProvider
import com.anytypeio.anytype.presentation.objects.menu.ObjectMenuViewModelBase
@ -78,6 +82,16 @@ abstract class ObjectMenuBaseFragment :
)
)
}
binding.objectLayoutConflictScreen.apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
ConflictScreen(
showScreen = vm.showLayoutConflictScreen.collectAsStateWithLifecycle().value,
onResetClick = { },
onDismiss = { vm.onHideConflictScreen() }
)
}
}
}
override fun onStart() {
@ -88,6 +102,7 @@ abstract class ObjectMenuBaseFragment :
click(binding.optionRelations) { vm.onRelationsClicked() }
click(binding.optionCover) { vm.onCoverClicked(ctx = ctx, space = space) }
click(binding.debugGoroutines) { vm.onDiagnosticsGoroutinesClicked(ctx = ctx) }
click(binding.objectLayoutConflict) { vm.onShowConflictScreen(objectId = ctx, space = space) }
proceed(vm.actions) { actionAdapter.submitList(it) }
proceed(vm.toasts) { toast(it) }
@ -162,11 +177,15 @@ abstract class ObjectMenuBaseFragment :
snackbar.anchorView = binding.anchor
snackbar.show()
}
is ObjectMenuViewModelBase.Command.ShareDeeplinkToObject -> {
val intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, command.link)
putExtra(Intent.EXTRA_TITLE, getString(R.string.multiplayer_deeplink_to_your_object))
putExtra(
Intent.EXTRA_TITLE,
getString(R.string.multiplayer_deeplink_to_your_object)
)
type = "text/plain"
}
startActivity(Intent.createChooser(intent, null))

View file

@ -1,192 +1,216 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:layout_height="match_parent">
<View
android:id="@+id/dragger"
android:layout_width="48dp"
android:layout_height="4dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="6dp"
android:background="@drawable/dragger" />
<androidx.core.widget.NestedScrollView
android:id="@+id/scrollView"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:layout_constraintBottom_toTopOf="@id/rvContainer"
app:layout_constraintTop_toTopOf="parent">
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/lvOptions"
<View
android:id="@+id/dragger"
android:layout_width="48dp"
android:layout_height="4dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="6dp"
android:background="@drawable/dragger" />
<androidx.core.widget.NestedScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:layout_height="0dp"
android:layout_weight="1"
app:layout_constraintBottom_toTopOf="@id/rvContainer"
app:layout_constraintTop_toTopOf="parent">
<com.anytypeio.anytype.core_ui.widgets.ObjectMenuItemWidget
android:id="@+id/optionIcon"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:background="@drawable/default_ripple"
app:icon="@drawable/ic_obj_settings_icon_24"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dragger"
app:title="@string/icon" />
<View
android:id="@+id/iconDivider"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@color/shape_primary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/optionIcon" />
<com.anytypeio.anytype.core_ui.widgets.ObjectMenuItemWidget
android:id="@+id/optionCover"
<LinearLayout
android:id="@+id/lvOptions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/default_ripple"
app:icon="@drawable/ic_obj_settings_cover_24"
app:subtitle="@string/cover_description"
app:title="@string/cover" />
android:layout_height="match_parent"
android:orientation="vertical">
<View
android:id="@+id/coverDivider"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@color/shape_primary" />
<com.anytypeio.anytype.core_ui.widgets.ObjectMenuItemWidget
android:id="@+id/optionIcon"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:background="@drawable/default_ripple"
app:icon="@drawable/ic_obj_settings_icon_24"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/dragger"
app:title="@string/icon" />
<com.anytypeio.anytype.core_ui.widgets.ObjectMenuDescriptionItem
android:id="@+id/optionDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/default_ripple"
app:icon="@drawable/ic_obj_settings_description_24"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/coverDivider"
app:title="@string/description" />
<View
android:id="@+id/iconDivider"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@color/shape_primary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/optionIcon" />
<View
android:id="@+id/descriptionDivider"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@color/shape_primary" />
<com.anytypeio.anytype.core_ui.widgets.ObjectMenuItemWidget
android:id="@+id/optionCover"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/default_ripple"
app:icon="@drawable/ic_obj_settings_cover_24"
app:subtitle="@string/cover_description"
app:title="@string/cover" />
<com.anytypeio.anytype.core_ui.widgets.ObjectMenuItemWidget
android:id="@+id/optionRelations"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/default_ripple"
app:icon="@drawable/ic_obj_settings_fields_24"
app:title="@string/properties" />
<View
android:id="@+id/coverDivider"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@color/shape_primary" />
<View
android:id="@+id/relationsDivider"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@color/shape_primary" />
<com.anytypeio.anytype.core_ui.widgets.ObjectMenuDescriptionItem
android:id="@+id/optionDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/default_ripple"
app:icon="@drawable/ic_obj_settings_description_24"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/coverDivider"
app:title="@string/description" />
<com.anytypeio.anytype.core_ui.widgets.ObjectMenuItemWidget
android:id="@+id/optionHistory"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/default_ripple"
app:icon="@drawable/ic_obj_settings_history_24"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/relationsDivider"
app:title="@string/history" />
<View
android:id="@+id/descriptionDivider"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@color/shape_primary" />
<View
android:id="@+id/historyDivider"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@color/shape_primary" />
<com.anytypeio.anytype.core_ui.widgets.ObjectMenuItemWidget
android:id="@+id/optionRelations"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/default_ripple"
app:icon="@drawable/ic_obj_settings_fields_24"
app:title="@string/properties" />
<com.anytypeio.anytype.core_ui.widgets.ObjectMenuItemWidget
android:id="@+id/objectDiagnostics"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/default_ripple"
app:icon="@drawable/ic_object_menu_diagnostics"
app:title="@string/object_diagnostics" />
<View
android:id="@+id/relationsDivider"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@color/shape_primary" />
<View
android:id="@+id/objectDiagnosticsDivider"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@color/shape_primary" />
<com.anytypeio.anytype.core_ui.widgets.ObjectMenuItemWidget
android:id="@+id/optionHistory"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/default_ripple"
app:icon="@drawable/ic_obj_settings_history_24"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/relationsDivider"
app:title="@string/history" />
<com.anytypeio.anytype.core_ui.widgets.ObjectMenuItemWidget
android:id="@+id/debugGoroutines"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/default_ripple"
android:visibility="gone"
app:icon="@drawable/ic_object_menu_debug_goroutines"
app:title="Debug Goroutines"
tools:visibility="visible" />
<View
android:id="@+id/historyDivider"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@color/shape_primary" />
<View
android:id="@+id/debugGoroutinesDivider"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@color/shape_primary"
android:visibility="gone" />
<com.anytypeio.anytype.core_ui.widgets.ObjectMenuItemWidget
android:id="@+id/objectDiagnostics"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/default_ripple"
app:icon="@drawable/ic_object_menu_diagnostics"
app:title="@string/object_diagnostics" />
</LinearLayout>
<View
android:id="@+id/objectDiagnosticsDivider"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@color/shape_primary" />
</androidx.core.widget.NestedScrollView>
<com.anytypeio.anytype.core_ui.widgets.ObjectMenuItemWidget
android:id="@+id/debugGoroutines"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/default_ripple"
android:visibility="gone"
app:icon="@drawable/ic_object_menu_debug_goroutines"
app:title="Debug Goroutines"
tools:visibility="visible" />
<FrameLayout
android:id="@+id/rvContainer"
android:layout_width="match_parent"
android:layout_height="108dp"
android:layout_marginTop="12dp"
android:layout_weight="0">
<View
android:id="@+id/debugGoroutinesDivider"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="@color/shape_primary"
android:visibility="gone" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvActions"
<com.anytypeio.anytype.core_ui.widgets.ObjectMenuItemWidget
android:id="@+id/objectLayoutConflict"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/default_ripple"
android:visibility="visible"
app:icon="@drawable/ic_attention_24"
app:showArrow="false"
app:title="@string/object_conflict_menu_item_title"
tools:visibility="visible" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<FrameLayout
android:id="@+id/rvContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_vertical" />
android:layout_height="108dp"
android:layout_marginTop="12dp"
android:layout_weight="0">
</FrameLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvActions"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_vertical" />
<View
android:id="@+id/anchor"
</FrameLayout>
<View
android:id="@+id/anchor"
android:layout_width="match_parent"
android:layout_height="1dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</LinearLayout>
<androidx.compose.ui.platform.ComposeView
android:id="@+id/objectLayoutConflictScreen"
android:layout_width="match_parent"
android:layout_height="1dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal" />
</LinearLayout>
</FrameLayout>

View file

@ -6,6 +6,8 @@ import android.view.LayoutInflater
import android.widget.LinearLayout
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.databinding.WidgetObjectMenuItemBinding
import com.anytypeio.anytype.core_utils.ext.invisible
import com.anytypeio.anytype.core_utils.ext.visible
class ObjectMenuItemWidget @JvmOverloads constructor(
context: Context,
@ -26,6 +28,12 @@ class ObjectMenuItemWidget @JvmOverloads constructor(
val attrs = context.obtainStyledAttributes(set, R.styleable.ObjectMenuItemWidget, 0, 0)
tvTitle.text = attrs.getString(R.styleable.ObjectMenuItemWidget_title)
ivIcon.setImageResource(attrs.getResourceId(R.styleable.ObjectMenuItemWidget_icon, -1))
val showArrow = attrs.getBoolean(R.styleable.ObjectMenuItemWidget_showArrow, true)
if (showArrow) {
ivArrow.visible()
} else {
ivArrow.invisible()
}
attrs.recycle()
}
}

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,0L12,0A12,12 0,0 1,24 12L24,12A12,12 0,0 1,12 24L12,24A12,12 0,0 1,0 12L0,12A12,12 0,0 1,12 0z"
android:fillColor="@color/glyph_active"/>
<path
android:pathData="M13.071,5.333H10.938V13.667H13.071V5.333ZM12.019,18.679C12.746,18.679 13.335,18.09 13.335,17.363C13.335,16.636 12.746,16.047 12.019,16.047C11.292,16.047 10.703,16.636 10.703,17.363C10.703,18.09 11.292,18.679 12.019,18.679Z"
android:fillColor="@color/glyph_white"
android:fillType="evenOdd"/>
</vector>

View file

@ -49,6 +49,7 @@
<attr name="title" format="string" />
<attr name="subtitle" format="string" />
<attr name="icon" format="reference" />
<attr name="showArrow" format="boolean" />
</declare-styleable>
<declare-styleable name="TextInputWidget">
<attr name="ignoreDragAndDrop" format="boolean" />

View file

@ -0,0 +1,113 @@
package com.anytypeio.anytype.feature_object_type.ui.conflict
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
import com.anytypeio.anytype.core_ui.foundation.Dragger
import com.anytypeio.anytype.core_ui.views.BodyCalloutRegular
import com.anytypeio.anytype.core_ui.views.ButtonPrimary
import com.anytypeio.anytype.core_ui.views.ButtonSecondary
import com.anytypeio.anytype.core_ui.views.ButtonSize
import com.anytypeio.anytype.core_ui.views.HeadlineSubheading
import com.anytypeio.anytype.feature_object_type.R
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ConflictScreen(
modifier: Modifier = Modifier,
showScreen: Boolean,
onResetClick: () -> Unit,
onDismiss: () -> Unit,
) {
val bottomSheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true
)
if (showScreen) {
ModalBottomSheet(
modifier = modifier,
dragHandle = {
Column {
Spacer(modifier = Modifier.height(6.dp))
Dragger()
Spacer(modifier = Modifier.height(6.dp))
}
},
scrimColor = colorResource(id = R.color.modal_screen_outside_background),
containerColor = colorResource(id = R.color.background_secondary),
shape = RoundedCornerShape(16.dp),
sheetState = bottomSheetState,
onDismissRequest = {
onDismiss()
}
) {
Spacer(modifier = Modifier.height(20.dp))
Text(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp),
textAlign = TextAlign.Center,
style = HeadlineSubheading,
color = colorResource(id = R.color.text_primary),
text = stringResource(id = R.string.object_conflict_screen_title)
)
Spacer(modifier = Modifier.height(4.dp))
Text(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp),
textAlign = TextAlign.Center,
style = BodyCalloutRegular,
color = colorResource(id = R.color.text_primary),
text = stringResource(id = R.string.object_conflict_screen_description)
)
Spacer(modifier = Modifier.height(20.dp))
ButtonPrimary(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp),
text = stringResource(R.string.object_conflict_screen_action_button),
size = ButtonSize.LargeSecondary,
onClick = {
onResetClick()
}
)
Spacer(modifier = Modifier.height(8.dp))
ButtonSecondary(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp),
text = stringResource(R.string.cancel),
size = ButtonSize.LargeSecondary,
onClick = {
onDismiss()
}
)
Spacer(modifier = Modifier.height(10.dp))
}
}
}
@DefaultPreviews
@Composable
fun ConflictScreenPreview() {
ConflictScreen(
showScreen = true,
onResetClick = {},
onDismiss = {}
)
}

View file

@ -2010,4 +2010,10 @@ Please provide specific details of your needs here.</string>
<string name="object_type_icon_change_title_search_hint">Search..."</string>
<string name="space_invite_link_copied">Space invite link copied!</string>
<string name="object_conflict_screen_title">Resolve layout conflict</string>
<string name="object_conflict_screen_description">This layout differs from the type\'s default. Reset to match?</string>
<string name="object_conflict_screen_action_button">Reset to default</string>
<string name="object_conflict_menu_item_title">Resolve layout conflict</string>
</resources>

View file

@ -96,6 +96,8 @@ abstract class ObjectMenuViewModelBase(
abstract fun onDescriptionClicked(ctx: Id, space: Id)
abstract fun onRelationsClicked()
val showLayoutConflictScreen = MutableStateFlow(false)
fun onHistoryClicked(ctx: Id, space: Id) {
viewModelScope.launch {
commands.emit(Command.OpenHistoryScreen(ctx, space))
@ -478,6 +480,18 @@ abstract class ObjectMenuViewModelBase(
}
}
fun onShowConflictScreen(objectId: Id, space: Id) {
viewModelScope.launch {
showLayoutConflictScreen.value = true
}
}
fun onHideConflictScreen() {
viewModelScope.launch {
showLayoutConflictScreen.value = false
}
}
sealed class Command {
data object OpenObjectIcons : Command()
data object OpenSetIcons : Command()