diff --git a/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt index cbc9c18906..166fe1e918 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt @@ -10,11 +10,13 @@ import android.graphics.Point import android.net.Uri import android.os.Build import android.os.Bundle +import android.util.Log import android.view.ContextThemeWrapper import android.view.LayoutInflater import android.view.MotionEvent import android.view.View import android.view.ViewGroup +import android.widget.FrameLayout import android.view.animation.DecelerateInterpolator import android.view.animation.LinearInterpolator import android.view.animation.OvershootInterpolator @@ -42,8 +44,11 @@ import androidx.core.animation.doOnStart import androidx.core.os.bundleOf import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP +import androidx.core.view.WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE +import androidx.core.view.WindowInsetsAnimationCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.isVisible +import androidx.core.view.setPadding import androidx.core.view.updateLayoutParams import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle @@ -430,6 +435,8 @@ open class EditorFragment : NavigationFragment(R.layout.f threshold = dimen(R.dimen.default_toolbar_height), thresholdPadding = dimen(R.dimen.dp_8) ) { isHeaderOverlaid -> + Log.d("Test1983", "Header overlaid: $isHeaderOverlaid") + updateStatusBarForHeaderOverlay(isHeaderOverlaid) if (isHeaderOverlaid) { binding.topToolbar.setBackgroundColor(0) binding.topToolbar.container.animate().alpha(0f) @@ -444,7 +451,7 @@ open class EditorFragment : NavigationFragment(R.layout.f } } } else { - binding.topToolbar.setBackgroundColor(requireContext().color(R.color.defaultCanvasColor)) + binding.topToolbar.setBackgroundColor(requireContext().color(R.color.background_primary)) binding.topToolbar.container.animate().alpha(1f) .setDuration(DEFAULT_TOOLBAR_ANIM_DURATION) .start() @@ -554,6 +561,7 @@ open class EditorFragment : NavigationFragment(R.layout.f override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + setupEdgeToEdge(view) setupWindowInsetAnimation() dndDelegate.init(blockAdapter, vm, this) @@ -664,7 +672,7 @@ open class EditorFragment : NavigationFragment(R.layout.f .onEach { vm.onDocumentMenuClicked() } .launchIn(lifecycleScope) - binding.topToolbar.back + binding.topToolbar.backContainer .clicks() .throttleFirst() .onEach { vm.onBackButtonPressed() } @@ -861,11 +869,177 @@ open class EditorFragment : NavigationFragment(R.layout.f } } - open fun setupWindowInsetAnimation() { - if (BuildConfig.USE_NEW_WINDOW_INSET_API && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - binding.toolbar.syncTranslationWithImeVisibility( - dispatchMode = DISPATCH_MODE_STOP + private fun setupEdgeToEdge(view: View) { + + // Apply window insets to handle edge-to-edge display + ViewCompat.setOnApplyWindowInsetsListener(view) { _, insets -> + val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) + val statusBars = insets.getInsets(WindowInsetsCompat.Type.statusBars()) + val ime = insets.getInsets(WindowInsetsCompat.Type.ime()) + + // Extend topToolbar to include status bar area + binding.topToolbar.updateLayoutParams { + height = dimen(R.dimen.default_toolbar_height) + statusBars.top + } + // Add top padding to topToolbar content to push it below status bar + binding.topToolbar.setPadding( + binding.topToolbar.paddingLeft, + statusBars.top, + binding.topToolbar.paddingRight, + binding.topToolbar.paddingBottom ) + + // Apply top padding to search toolbar for status bar + binding.searchToolbar.updateLayoutParams { + topMargin = systemBars.top + } + + // Apply top padding to multi-select toolbar for status bar + binding.multiSelectTopToolbar.updateLayoutParams { + topMargin = systemBars.top + } + + // Handle BlockToolbarWidget positioning with IME + val imeHeight = ime.bottom + if (imeHeight > 0) { + // IME is visible, position toolbar above it + binding.toolbar.visibility = View.VISIBLE + binding.toolbar.translationY = -imeHeight.toFloat() + } else { + // IME is hidden, hide toolbar or position at bottom + binding.toolbar.translationY = 0f + } + + // Handle other bottom toolbars + val bottomPadding = if (ime.bottom > 0) ime.bottom else systemBars.bottom + binding.markupToolbar.updateLayoutParams { + bottomMargin = bottomPadding + } +// +// // Handle Compose views at the bottom +// binding.chooseTypeWidget.updateLayoutParams { +// bottomMargin = bottomPadding +// } +// binding.syncStatusWidget.updateLayoutParams { +// bottomMargin = if (ime.bottom > 0) 0 else systemBars.bottom +// } +// binding.editorDatePicker.updateLayoutParams { +// bottomMargin = bottomPadding +// } +// binding.attachToChatPanel.updateLayoutParams { +// bottomMargin = bottomPadding +// } +// +// // Apply bottom padding to other bottom toolbars +// binding.mentionSuggesterToolbar.setPadding( +// binding.mentionSuggesterToolbar.paddingLeft, +// binding.mentionSuggesterToolbar.paddingTop, +// binding.mentionSuggesterToolbar.paddingRight, +// bottomPadding +// ) +// binding.slashWidget.setPadding( +// binding.slashWidget.paddingLeft, +// binding.slashWidget.paddingTop, +// binding.slashWidget.paddingRight, +// bottomPadding +// ) +// binding.scrollAndMoveBottomAction.setPadding( +// binding.scrollAndMoveBottomAction.paddingLeft, +// binding.scrollAndMoveBottomAction.paddingTop, +// binding.scrollAndMoveBottomAction.paddingRight, +// systemBars.bottom +// ) +// +// // Apply bottom margin to the bottom sheets container +// binding.panels.setPadding( +// binding.panels.paddingLeft, +// binding.panels.paddingTop, +// binding.panels.paddingRight, +// systemBars.bottom +// ) + + //insets + WindowInsetsCompat.CONSUMED + } + + // Animate toolbar in sync with IME + ViewCompat.setWindowInsetsAnimationCallback( + view, + object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_CONTINUE_ON_SUBTREE) { + + override fun onPrepare(animation: WindowInsetsAnimationCompat) { + super.onPrepare(animation) + // Ensure toolbar is visible when IME animation starts + if (animation.typeMask and WindowInsetsCompat.Type.ime() != 0) { + binding.toolbar.visibility = View.VISIBLE + } + } + + override fun onProgress( + insets: WindowInsetsCompat, + runningAnimations: MutableList + ): WindowInsetsCompat { + val imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom + + // Always keep toolbar visible during animation + // Only animate the translation + binding.toolbar.translationY = if (imeHeight > 0) -imeHeight.toFloat() else 0f + + return insets + } + + override fun onEnd(animation: WindowInsetsAnimationCompat) { + super.onEnd(animation) + // Only hide toolbar after animation completely ends and IME is hidden + if (animation.typeMask and WindowInsetsCompat.Type.ime() != 0) { + val currentImeHeight = ViewCompat.getRootWindowInsets(view) + ?.getInsets(WindowInsetsCompat.Type.ime())?.bottom ?: 0 + if (currentImeHeight == 0) { + binding.toolbar.visibility = View.INVISIBLE + } + } + } + } + ) + } + + private fun updateStatusBarForHeaderOverlay(isHeaderOverlaid: Boolean) { + // Control topToolbar background visibility based on header overlay state + if (isHeaderOverlaid) { + // When header is not overlaid (scrolled up), make topToolbar transparent + binding.topToolbar.setBackgroundColor(android.graphics.Color.TRANSPARENT) + } else { + // When header is overlaid (visible), show topToolbar with background + binding.topToolbar.setBackgroundColor(requireContext().color(R.color.background_primary)) + } + + // Update status bar icon color based on current state and theme + val windowInsetsController = ViewCompat.getWindowInsetsController(requireActivity().window.decorView) + val uiMode = requireContext().resources.configuration.uiMode + val isDarkMode = (uiMode and android.content.res.Configuration.UI_MODE_NIGHT_MASK) == android.content.res.Configuration.UI_MODE_NIGHT_YES + + if (isHeaderOverlaid) { + // When showing transparent status bar, adjust icons based on content + val firstView = blockAdapter.views.firstOrNull() + val hasCover = firstView is BlockView.Title && firstView.hasCover + windowInsetsController?.isAppearanceLightStatusBars = if (hasCover) { + // For covers, usually need light icons + false + } else { + // For regular content, follow theme + !isDarkMode + } + } else { + // When showing colored status bar, follow theme + windowInsetsController?.isAppearanceLightStatusBars = !isDarkMode + } + } + + open fun setupWindowInsetAnimation() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { +// binding.toolbar.syncTranslationWithImeVisibility( +// dispatchMode = DISPATCH_MODE_STOP +// ) binding.chooseTypeWidget.syncTranslationWithImeVisibility( dispatchMode = DISPATCH_MODE_STOP ) @@ -1531,8 +1705,7 @@ open class EditorFragment : NavigationFragment(R.layout.f is BlockView.Title.Basic -> { resetTopToolbarTitle( text = title.text, - emoji = title.emoji, - image = title.image, + icon = title.icon ) if (title.hasCover) { val mng = binding.recycler.layoutManager as LinearLayoutManager @@ -1547,8 +1720,7 @@ open class EditorFragment : NavigationFragment(R.layout.f is BlockView.Title.Profile -> { resetTopToolbarTitle( text = title.text, - emoji = null, - image = title.image, + icon = title.icon ) if (title.hasCover) { val mng = binding.recycler.layoutManager as LinearLayoutManager @@ -1563,8 +1735,7 @@ open class EditorFragment : NavigationFragment(R.layout.f is BlockView.Title.Todo -> { resetTopToolbarTitle( text = title.text, - emoji = null, - image = title.image, + icon = title.icon ) if (title.hasCover) { val mng = binding.recycler.layoutManager as LinearLayoutManager @@ -1582,27 +1753,9 @@ open class EditorFragment : NavigationFragment(R.layout.f } } - private fun resetTopToolbarTitle(text: String?, emoji: String?, image: String?) { + private fun resetTopToolbarTitle(text: String?, icon: ObjectIcon) { binding.topToolbar.title.text = text - val iconView = binding.topToolbar.icon - when { - text.isNullOrBlank() -> { - iconView.setIcon(ObjectIcon.None) - iconView.gone() - } - !emoji.isNullOrBlank() -> { - iconView.setIcon(ObjectIcon.Basic.Emoji(emoji)) - iconView.visible() - } - !image.isNullOrBlank() -> { - iconView.setIcon(ObjectIcon.Basic.Image(image)) - iconView.visible() - } - else -> { - iconView.setIcon(ObjectIcon.None) - iconView.gone() - } - } + binding.topToolbar.icon.setIcon(icon) } open fun render(state: ControlPanelState) { @@ -1628,7 +1781,7 @@ open class EditorFragment : NavigationFragment(R.layout.f Main.TargetBlockType.Description -> BlockToolbarWidget.State.Description } } else { - binding.toolbar.invisible() + //binding.toolbar.invisible() } setMainMarkupToolbarState(state) @@ -2077,9 +2230,8 @@ open class EditorFragment : NavigationFragment(R.layout.f showTargeterWithAnimation() binding.recycler.addOnScrollListener(scrollAndMoveStateListener) - binding.multiSelectTopToolbar.invisible() + binding.multiSelectTopToolbar.gone() - showTopScrollAndMoveToolbar() binding.scrollAndMoveBottomAction.show() hideBlockActionPanel() @@ -2113,33 +2265,38 @@ open class EditorFragment : NavigationFragment(R.layout.f removeItemDecoration(scrollAndMoveTargetHighlighter) removeOnScrollListener(scrollAndMoveStateListener) } - hideTopScrollAndMoveToolbar() binding.scrollAndMoveBottomAction.hide() binding.targeter.invisible() scrollAndMoveTargetDescriptor.clear() } private fun hideSelectButton() { - if (binding.multiSelectTopToolbar.translationY >= 0) { + if (binding.multiSelectTopToolbar.alpha > 0f) { ObjectAnimator.ofFloat( binding.multiSelectTopToolbar, - SELECT_BUTTON_ANIMATION_PROPERTY, - -requireContext().dimen(R.dimen.dp_48) + "alpha", + 0f ).apply { duration = SELECT_BUTTON_HIDE_ANIMATION_DURATION interpolator = DecelerateInterpolator() - doOnEnd { if (hasBinding) binding.topToolbar.visible() } + doOnEnd { + if (hasBinding) { + binding.multiSelectTopToolbar.gone() + binding.topToolbar.visible() + } + } start() } } } private fun showSelectButton() { - if (binding.multiSelectTopToolbar.translationY < 0) { + if (binding.multiSelectTopToolbar.alpha < 1f) { + binding.multiSelectTopToolbar.visible() ObjectAnimator.ofFloat( binding.multiSelectTopToolbar, - SELECT_BUTTON_ANIMATION_PROPERTY, - 0f + "alpha", + 1f ).apply { duration = SELECT_BUTTON_SHOW_ANIMATION_DURATION interpolator = DecelerateInterpolator() @@ -2148,30 +2305,6 @@ open class EditorFragment : NavigationFragment(R.layout.f } } - private fun hideTopScrollAndMoveToolbar() { - ObjectAnimator.ofFloat( - binding.scrollAndMoveHint, - SELECT_BUTTON_ANIMATION_PROPERTY, - -requireContext().dimen(R.dimen.dp_48) - ).apply { - duration = SELECT_BUTTON_HIDE_ANIMATION_DURATION - interpolator = DecelerateInterpolator() - start() - } - } - - private fun showTopScrollAndMoveToolbar() { - ObjectAnimator.ofFloat( - binding.scrollAndMoveHint, - SELECT_BUTTON_ANIMATION_PROPERTY, - 0f - ).apply { - duration = SELECT_BUTTON_SHOW_ANIMATION_DURATION - interpolator = DecelerateInterpolator() - start() - } - } - override fun onClipboardAction(action: ClipboardInterceptor.Action) { when (action) { is ClipboardInterceptor.Action.Copy -> vm.onCopy(action.selection) diff --git a/app/src/main/java/com/anytypeio/anytype/ui/objects/ObjectFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/objects/ObjectFragment.kt new file mode 100644 index 0000000000..f42cc2fd38 --- /dev/null +++ b/app/src/main/java/com/anytypeio/anytype/ui/objects/ObjectFragment.kt @@ -0,0 +1,175 @@ +package com.anytypeio.anytype.ui.objects + +import android.graphics.Color +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.PopupMenu +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsControllerCompat +import androidx.core.view.updateLayoutParams +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.anytypeio.anytype.R +import com.anytypeio.anytype.databinding.FragmentObjectBinding + +class ObjectFragment : Fragment(R.layout.fragment_object) { + + private var _binding: FragmentObjectBinding? = null + private val binding get() = _binding!! + + private var lightStatusBars: Boolean = true + private var coverVisible: Boolean = true + private var imePaddingEnabled: Boolean = true + private var lastInsets: WindowInsetsCompat? = null + + override fun onViewCreated(view: android.view.View, savedInstanceState: android.os.Bundle?) { + super.onViewCreated(view, savedInstanceState) + _binding = FragmentObjectBinding.bind(view) + + // Temporary: show recycler to verify layout + // --- Demo Recycler for refactor bring-up --- + val demoAdapter = DemoAdapter() + binding.recycler.adapter = demoAdapter + binding.recycler.visibility = View.VISIBLE + demoAdapter.submitList( + listOf( + "Title block", + "Paragraph block", + "Checklist block", + "Code block", + "Divider", + "Callout", + "Quote" + ) + ) + + // Enable edge-to-edge for system bars + val window = requireActivity().window + androidx.core.view.WindowCompat.setDecorFitsSystemWindows(window, false) + + // Make status bar icons dark on light backgrounds (toggle as needed when testing) + val controller = androidx.core.view.WindowInsetsControllerCompat(window, binding.root) + controller.isAppearanceLightStatusBars = true + + binding.topEdgeCover.setBackgroundColor(Color.parseColor("#FF00AA")) // magenta + binding.topEdgeCover.visibility = if (coverVisible) View.VISIBLE else View.GONE + + // Apply WindowInsets to size the top cover to the status bar height and + // provide bottom padding for the sheet (recycler + toolbars) vs nav/IME. + androidx.core.view.ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets -> + val status = insets.getInsets(androidx.core.view.WindowInsetsCompat.Type.statusBars()) + val nav = insets.getInsets(androidx.core.view.WindowInsetsCompat.Type.navigationBars()) + val ime = insets.getInsets(androidx.core.view.WindowInsetsCompat.Type.ime()) + + // Extend topToolbar to include status bar area + binding.topToolbar.updateLayoutParams { + height += status.top + } + // Add top padding to topToolbar content to push it below status bar + binding.topToolbar.setPadding( + binding.topToolbar.paddingLeft, + status.top, + binding.topToolbar.paddingRight, + binding.topToolbar.paddingBottom + ) + + // Pad the content bottom by the largest of nav bar or IME + val bottom = maxOf(nav.bottom, ime.bottom) + binding.sheet.setPadding( + binding.sheet.paddingLeft, + binding.sheet.paddingTop, + binding.sheet.paddingRight, + bottom + ) + + insets + } + + //testing + binding.debugFab.setOnClickListener { view -> + val popup = PopupMenu(requireContext(), view) + popup.menu.add(0, 1, 0, "Light status bars: toggle") + popup.menu.add(0, 2, 1, "Top cover: show/hide") + popup.menu.add(0, 3, 2, "Top cover: randomize color") + popup.menu.add(0, 4, 3, "Bottom padding: toggle IME/nav") + + popup.setOnMenuItemClickListener { item -> + when (item.itemId) { + 1 -> { + lightStatusBars = !lightStatusBars + val controller = WindowInsetsControllerCompat(requireActivity().window, binding.root) + controller.isAppearanceLightStatusBars = lightStatusBars + true + } + 2 -> { + coverVisible = !coverVisible + binding.topEdgeCover.visibility = if (coverVisible) View.VISIBLE else View.GONE + true + } + 3 -> { + val rnd = kotlin.random.Random(System.currentTimeMillis()) + val color = android.graphics.Color.argb(255, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256)) + binding.topEdgeCover.setBackgroundColor(color) + true + } + 4 -> { + imePaddingEnabled = !imePaddingEnabled + // re-apply latest insets immediately + lastInsets?.let { insets -> + val nav = insets.getInsets(WindowInsetsCompat.Type.navigationBars()) + val ime = insets.getInsets(WindowInsetsCompat.Type.ime()) + val bottom = if (imePaddingEnabled) maxOf(nav.bottom, ime.bottom) else 0 + binding.sheet.setPadding( + binding.sheet.paddingLeft, + binding.sheet.paddingTop, + binding.sheet.paddingRight, + bottom + ) + } + true + } + else -> false + } + } + popup.show() + } + } + + override fun onDestroyView() { + _binding = null + super.onDestroyView() + } +} + + +// --- Minimal demo adapter to visualize the refactored layout --- +private class DemoAdapter : ListAdapter(DIFF) { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DemoVH { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.item_block_title, parent, false) + return DemoVH(view) + } + + override fun onBindViewHolder(holder: DemoVH, position: Int) { + holder.bind(getItem(position)) + } + + companion object { + private val DIFF = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: String, newItem: String) = oldItem === newItem + override fun areContentsTheSame(oldItem: String, newItem: String) = oldItem == newItem + } + } +} + +private class DemoVH(itemView: View) : RecyclerView.ViewHolder(itemView) { + fun bind(title: String) { + // `item_block_title` is assumed to contain a TextView with id `title` + itemView.findViewById(com.anytypeio.anytype.R.id.title)?.text = title + } +} \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/sets/ObjectSetFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/sets/ObjectSetFragment.kt index 7fe8cc0a15..a5d7fb7792 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/sets/ObjectSetFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/sets/ObjectSetFragment.kt @@ -892,7 +892,7 @@ open class ObjectSetFragment : binding.topToolbar.root.findViewById(R.id.tvTopToolbarTitle).text = header.title.text binding.objectHeader.root.findViewById(R.id.docEmojiIconContainer).apply { - if (header.title.emoji != null) visible() else gone() + //if (header.title.emoji != null) visible() else gone() jobs += this.clicks() .throttleFirst() .onEach { vm.onObjectIconClicked() } @@ -914,8 +914,8 @@ open class ObjectSetFragment : } } - binding.objectHeader.root.findViewById(R.id.emojiIcon) - .setEmojiOrNull(header.title.emoji) +// binding.objectHeader.root.findViewById(R.id.emojiIcon) +// .setEmojiOrNull(header.title.emoji) setCover( coverColor = header.title.coverColor, @@ -941,15 +941,15 @@ open class ObjectSetFragment : private fun setupHeaderMargins(header: SetOrCollectionHeaderState.Default) { when { - header.title.emoji != null -> { - title.updateLayoutParams { - topMargin = dimen(R.dimen.dp_12) - } - binding.objectHeader.docEmojiIconContainer.updateLayoutParams { - topMargin = - if (!header.title.hasCover) dimen(R.dimen.dp_12) else dimen(R.dimen.dp_72) - } - } +// header.title.emoji != null -> { +// title.updateLayoutParams { +// topMargin = dimen(R.dimen.dp_12) +// } +// binding.objectHeader.docEmojiIconContainer.updateLayoutParams { +// topMargin = +// if (!header.title.hasCover) dimen(R.dimen.dp_12) else dimen(R.dimen.dp_72) +// } +// } header.title.image != null -> { title.updateLayoutParams { topMargin = dimen(R.dimen.dp_10) diff --git a/app/src/main/res/layout/fragment_editor.xml b/app/src/main/res/layout/fragment_editor.xml index 9ae27a19c0..8ec316c1db 100644 --- a/app/src/main/res/layout/fragment_editor.xml +++ b/app/src/main/res/layout/fragment_editor.xml @@ -53,7 +53,7 @@ android:layout_width="0dp" android:layout_height="48dp" android:background="@color/defaultCanvasColor" - android:translationY="-48dp" + android:visibility="gone" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -99,16 +99,6 @@ app:layout_constraintStart_toStartOf="parent" tools:visibility="visible" /> - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/graph.xml b/app/src/main/res/navigation/graph.xml index 8a2001210a..ab2263c1a7 100644 --- a/app/src/main/res/navigation/graph.xml +++ b/app/src/main/res/navigation/graph.xml @@ -10,7 +10,7 @@ app:startDestination="@id/pageScreen"> () if (newBlock is BlockView.Title.Basic && oldBlock is BlockView.Title.Basic) { - if (newBlock.emoji != oldBlock.emoji || newBlock.image != oldBlock.image) + if (newBlock.icon != oldBlock.icon) changes.add(TITLE_ICON_CHANGED) if (newBlock.coverColor != oldBlock.coverColor || newBlock.coverGradient != oldBlock.coverGradient diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/other/Title.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/other/Title.kt index 5b0c13ec98..b5f3a42528 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/other/Title.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/other/Title.kt @@ -4,22 +4,17 @@ import android.content.Context import android.text.Spannable import android.util.TypedValue import android.view.View -import android.widget.FrameLayout.LayoutParams +import android.widget.FrameLayout import android.widget.ImageView import android.widget.TextView -import androidx.compose.ui.platform.ComposeView import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.updateLayoutParams -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleEventObserver -import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.RecyclerView import coil3.ImageLoader import coil3.imageLoader import coil3.load import coil3.request.ImageRequest import coil3.request.crossfade -import coil3.request.transformations import coil3.target.Target import coil3.video.VideoFrameDecoder import coil3.video.videoFrameMillis @@ -43,16 +38,12 @@ import com.anytypeio.anytype.core_utils.ext.dimen import com.anytypeio.anytype.core_utils.ext.gone import com.anytypeio.anytype.core_utils.ext.removeSpans import com.anytypeio.anytype.core_utils.ext.visible -import com.anytypeio.anytype.emojifier.Emojifier import com.anytypeio.anytype.presentation.editor.cover.CoverColor import com.anytypeio.anytype.presentation.editor.cover.CoverGradient import com.anytypeio.anytype.presentation.editor.editor.KeyPressedEvent import com.anytypeio.anytype.presentation.editor.editor.listener.ListenerType import com.anytypeio.anytype.presentation.editor.editor.model.BlockView -import com.google.android.exoplayer2.DefaultLoadControl -import com.google.android.exoplayer2.ExoPlayer -import com.google.android.exoplayer2.MediaItem -import com.google.android.exoplayer2.ui.StyledPlayerView +import com.anytypeio.anytype.presentation.objects.ObjectIcon import timber.log.Timber sealed class Title(view: View) : BlockViewHolder(view), TextHolder { @@ -176,11 +167,8 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { } open fun setImage(item: BlockView.Title) { - Timber.d("Setting image for ${item.id}, image=${item.image}") - item.image?.let { url -> - image.visible() - image.load(url) - } ?: apply { image.setImageDrawable(null) } + // Default no-op implementation + // Each subclass handles its own icon logic using ObjectIconWidget } open fun processPayloads( @@ -262,10 +250,8 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { class Document(val binding: ItemBlockTitleBinding) : Title(binding.root) { - override val icon: View = binding.docEmojiIconContainer - override val image: ImageView = binding.imageIcon - private val emoji: ImageView = binding.emojiIcon - private val emojiFallback: TextView = binding.emojiIconFallback + override val icon: ObjectIconWidget = binding.objectIconWidget + override val image: ImageView = binding.objectIconWidget.binding.ivImage override val selectionView: View = itemView override val root: View = itemView @@ -286,62 +272,35 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { onCoverClicked = onCoverClicked, click = click ) - setEmoji(item) + setIcon(item) applySearchHighlights(item) - image.setOnClickListener { - click( - ListenerType.Picture.TitleView( - item = item - ) - ) - } - if (item.mode == BlockView.Mode.EDIT) { icon.setOnClickListener { onPageIconClicked() } - image.setOnClickListener { onPageIconClicked() } } - setupIconVisibility(item) } - private fun setupIconVisibility(item: BlockView.Title.Basic) { - when { - item.image != null -> { - binding.imageIcon.visible() - binding.docEmojiIconContainer.gone() - binding.title.updateLayoutParams { - topMargin = dimen(R.dimen.dp_10) - } - binding.imageIcon.updateLayoutParams { - topMargin = - if (!item.hasCover) dimen(R.dimen.dp_51) else dimen(R.dimen.dp_102) + private fun setIcon(item: BlockView.Title.Basic) { + icon.setIcon(item.icon) + + // Adjust ObjectIconWidget size based on icon type + when (item.icon) { + is ObjectIcon.Basic.Emoji -> { + icon.updateLayoutParams { + width = dimen(R.dimen.dp_88) + height = dimen(R.dimen.dp_88) + topMargin = if (item.hasCover) dimen(R.dimen.dp_164) else dimen(R.dimen.dp_120) } } - - item.emoji != null -> { - binding.imageIcon.gone() - binding.docEmojiIconContainer.visible() - binding.title.updateLayoutParams { - topMargin = dimen(R.dimen.dp_12) - } - binding.docEmojiIconContainer.updateLayoutParams { - topMargin = - if (!item.hasCover) dimen(R.dimen.dp_60) else dimen(R.dimen.dp_120) + is ObjectIcon.Basic.Image -> { + icon.updateLayoutParams { + width = dimen(R.dimen.dp_104) + height = dimen(R.dimen.dp_104) + topMargin = if (item.hasCover) dimen(R.dimen.dp_148) else dimen(R.dimen.dp_120) } } - else -> { - binding.imageIcon.gone() - binding.docEmojiIconContainer.gone() - if (!item.hasCover) { - content.updateLayoutParams { - topMargin = dimen(R.dimen.dp_80) - } - } else { - content.updateLayoutParams { - topMargin = dimen(R.dimen.dp_16) - } - } + //do nothing for other icon types } } } @@ -353,13 +312,8 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { super.processPayloads(payloads, item) if (item is BlockView.Title.Basic) { payloads.forEach { payload -> - if (payload.isTitleIconChanged) { - setEmoji(item) - setImage(item) - setupIconVisibility(item) - } - if (payload.isCoverChanged) { - setupIconVisibility(item) + if (payload.isTitleIconChanged || payload.isCoverChanged) { + setIcon(item) } if (payload.isSearchHighlightChanged) { applySearchHighlights(item) @@ -368,36 +322,6 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { } } - private fun setEmoji(item: BlockView.Title.Basic) { - try { - if (!item.emoji.isNullOrEmpty()) { - try { - val adapted = Emojifier.safeUri(item.emoji!!) - if (adapted != Emojifier.Config.EMPTY_URI) { - emojiFallback.text = "" - emojiFallback.gone() - emoji.load(adapted) { - memoryCachePolicy(coil3.request.CachePolicy.ENABLED) - diskCachePolicy(coil3.request.CachePolicy.ENABLED) - } - emoji.visible() - } else { - emoji.setImageDrawable(null) - emoji.gone() - emojiFallback.text = item.emoji - emojiFallback.visible() - } - } catch (e: Throwable) { - Timber.w(e, "Error while setting emoji icon for: ${item.emoji}") - } - } else { - emoji.setImageDrawable(null) - } - } catch (e: Throwable) { - Timber.w(e, "Could not set emoji icon") - } - } - override fun applyTextColor(item: BlockView.Title) { setTextColor(item.color) } @@ -409,19 +333,11 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { class Profile(val binding: ItemBlockTitleProfileBinding) : Title(binding.root) { - override val icon: View = binding.docProfileIconContainer - override val image: ImageView = binding.imageIcon + override val icon: ObjectIconWidget = binding.objectIconWidget + override val image: ImageView = binding.objectIconWidget.binding.ivImage override val content: TextInputWidget = binding.title override val selectionView: View = itemView - private val gradientView: ComposeView - get() = binding - .docProfileIconContainer - .findViewById(R.id.gradient) - - private val iconText = binding.imageText - private var hasImage = false - init { content.setSpannableFactory(DefaultSpannableFactory()) } @@ -437,41 +353,20 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { onCoverClicked = onCoverClicked, click = click ) - setupMargins(item) + setIcon(item) applySearchHighlights(item) if (item.mode == BlockView.Mode.EDIT) { icon.setOnClickListener { onProfileIconClicked(ListenerType.ProfileImageIcon) } } } - override fun setImage(item: BlockView.Title) { - item.image?.let { url -> - iconText.text = "" - gradientView.gone() - hasImage = true - image.visible() - image.load(url) { - transformations(coil3.transform.CircleCropTransformation()) - } - } ?: apply { - hasImage = false - gradientView.gone() - setIconText(item.text) - image.setImageDrawable(null) - } - } - - private fun setIconText(name: String?) { - if (name.isNullOrEmpty()) { - iconText.text = "U" - } else { - iconText.text = name.first().uppercaseChar().toString() - } + private fun setIcon(item: BlockView.Title.Profile) { + icon.setIcon(item.icon) } - fun onTitleTextChanged(text: String) { - if (!hasImage) { - setIconText(text) + fun onTitleTextChanged(item: BlockView, text: String) { + if (item is BlockView.Title.Profile && item.icon is ObjectIcon.Profile.Avatar) { + icon.setIcon(ObjectIcon.Profile.Avatar(text)) } } @@ -483,14 +378,11 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { if (item is BlockView.Title.Profile) { payloads.forEach { payload -> if (payload.isTitleIconChanged) { - setImage(item) + setIcon(item) } if (payload.isSearchHighlightChanged) { applySearchHighlights(item) } - if (payload.isCoverChanged) { - setupMargins(item) - } } } } @@ -502,21 +394,15 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { override fun applyBackground(item: BlockView.Title) { binding.title.setBlockBackgroundColor(item.background) } - - private fun setupMargins(item: BlockView.Title.Profile) { - binding.docProfileIconContainer.updateLayoutParams { - topMargin = if (!item.hasCover) dimen(R.dimen.dp_46) else dimen(R.dimen.dp_92) - } - } } class Todo(val binding: ItemBlockTitleTodoBinding) : Title(binding.root) { - override val icon: View = binding.todoTitleCheckbox - override val image: ImageView = binding.todoTitleCheckbox + override val icon: ObjectIconWidget = binding.objectIconWidget + override val image: ImageView = binding.objectIconWidget.binding.ivImage override val selectionView: View = itemView - val checkbox = binding.todoTitleCheckbox + val checkbox = binding.objectIconWidget.checkbox var isLocked: Boolean = false override val root: View = itemView @@ -528,7 +414,6 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { fun bind( item: BlockView.Title.Todo, - onPageIconClicked: () -> Unit, onCoverClicked: () -> Unit, click: (ListenerType) -> Unit ) { @@ -538,7 +423,7 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { click = click ) setLocked(item.mode) - checkbox.isSelected = item.isChecked + setIcon(item) applySearchHighlights(item) } @@ -559,13 +444,17 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { if (payload.isSearchHighlightChanged) { applySearchHighlights(item) } - if (payload.isTitleCheckboxChanged) { - checkbox.isSelected = item.isChecked + if (payload.isTitleCheckboxChanged || payload.isCoverChanged) { + setIcon(item) } } } } + private fun setIcon(item: BlockView.Title.Todo) { + icon.setIcon(item.icon) + } + override fun applyTextColor(item: BlockView.Title) { setTextColor(item.color) } @@ -583,13 +472,6 @@ sealed class Title(view: View) : BlockViewHolder(view), TextHolder { override val root: View = itemView override val content: TextInputWidget = binding.title - init { - icon.binding.ivImage.updateLayoutParams { - height = itemView.resources.getDimension(R.dimen.dp_80).toInt() - width = itemView.resources.getDimension(R.dimen.dp_64).toInt() - } - } - fun bind( item: BlockView.Title.File, ) { diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/tools/EditorHeaderOverlayDetector.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/tools/EditorHeaderOverlayDetector.kt index c4ee074bdf..7c6f60a02a 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/tools/EditorHeaderOverlayDetector.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/tools/EditorHeaderOverlayDetector.kt @@ -32,26 +32,12 @@ class EditorHeaderOverlayDetector( val root = holder.binding.root val title = holder.binding.title val cover = holder.binding.cover - val emojiIconContainer = holder.binding.docEmojiIconContainer - val imageIconContainer = holder.binding.imageIcon - onHeaderOverlaid = when { - emojiIconContainer.isVisible -> { - if (cover.isVisible) { - (emojiIconContainer.top + root.top >= threshold + thresholdPadding) - } else { - (emojiIconContainer.top + root.top >= (threshold / 2) + thresholdPadding) - } - } - imageIconContainer.isVisible -> { - if (cover.isVisible) { - (imageIconContainer.top + root.top >= threshold + thresholdPadding) - } else { - (imageIconContainer.top + root.top >= (threshold / 2) + thresholdPadding) - } - } - else -> { - root.top + title.top >= threshold + thresholdPadding - } + onHeaderOverlaid = if (cover.isVisible) { + // When cover is visible, check if title top is below threshold + (root.top + title.top >= threshold + thresholdPadding) + } else { + // When cover is not visible, check if title top is below half threshold + (root.top + title.top >= (threshold / 2) + thresholdPadding) } } is Title.Todo -> { @@ -66,7 +52,7 @@ class EditorHeaderOverlayDetector( is Title.Profile -> { val container = holder.itemView val cover = holder.binding.cover - val icon = holder.binding.docProfileIconContainer + val icon = holder.binding.objectIconWidget val title = holder.binding.title onHeaderOverlaid = if (cover.isVisible) { (container.top + icon.bottom >= threshold + thresholdPadding) diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/ObjectIconWidget.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/ObjectIconWidget.kt index 61f02808d4..c975adb12d 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/ObjectIconWidget.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/ObjectIconWidget.kt @@ -11,6 +11,7 @@ import android.view.LayoutInflater import android.view.View import android.widget.FrameLayout import android.widget.ImageView +import androidx.compose.ui.unit.dp import androidx.core.view.updateLayoutParams import com.anytypeio.anytype.core_models.Url import com.anytypeio.anytype.core_ui.R @@ -50,6 +51,7 @@ class ObjectIconWidget @JvmOverloads constructor( private var imageCornerRadius: Float = 0F private var isImageWithCorners: Boolean = false private val density = context.resources.displayMetrics.density + private var withBackgrounds: Boolean = false init { setupAttributeValues(attrs) @@ -117,6 +119,13 @@ class ObjectIconWidget @JvmOverloads constructor( fun setIcon(icon: ObjectIcon) { // Reset backgrounds + if (withBackgrounds) { + binding.root.setBackgroundResource(R.drawable.bg_object_header_icon_container) + binding.emojiContainer.setBackgroundResource(R.drawable.bg_object_header_emoji_container) + } else { + binding.root.background = null + binding.emojiContainer.background = null + } binding.ivImage.background = null when (icon) { @@ -225,6 +234,13 @@ class ObjectIconWidget @JvmOverloads constructor( emoji: String?, fallback: ObjectIcon.TypeIcon.Fallback ) { + if (withBackgrounds) { + binding.root.setBackgroundResource(R.drawable.bg_object_header_icon_container) + binding.emojiContainer.setBackgroundResource(R.drawable.bg_object_header_emoji_container) + } else { + binding.root.background = null + binding.emojiContainer.background = null + } if (!emoji.isNullOrBlank()) { with(binding) { ivCheckbox.invisible() @@ -240,10 +256,7 @@ class ObjectIconWidget @JvmOverloads constructor( if (adapted != Emojifier.Config.EMPTY_URI) { binding.tvEmojiFallback.gone() binding.ivEmoji.visible() - binding.ivEmoji.load(adapted) { - diskCachePolicy(CachePolicy.ENABLED) - memoryCachePolicy(CachePolicy.ENABLED) - } + binding.ivEmoji.load(adapted) } else { setTypeIcon(fallback) } @@ -306,7 +319,7 @@ class ObjectIconWidget @JvmOverloads constructor( private fun setTask(isChecked: Boolean?) { with(binding) { ivCheckbox.visible() - ivCheckbox.background = context.drawable(R.drawable.ic_data_view_grid_checkbox_selector) + ivCheckbox.background = context.drawable(R.drawable.ic_object_icon_task_selector) ivCheckbox.isActivated = isChecked ?: false initialContainer.invisible() emojiContainer.invisible() @@ -400,7 +413,10 @@ class ObjectIconWidget @JvmOverloads constructor( val backgroundDrawable = GradientDrawable().apply { shape = GradientDrawable.RECTANGLE setColor(context.color(R.color.shape_transparent_secondary)) - cornerRadius = getCornerRadiusInPx() + cornerRadius = if (imageCornerRadius > 2) + imageCornerRadius + else + getCornerRadiusInPx() } val layers = arrayOf(backgroundDrawable, iconDrawable) diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/ScrollAndMoveHintWidget.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/ScrollAndMoveHintWidget.kt deleted file mode 100644 index f659f43545..0000000000 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/ScrollAndMoveHintWidget.kt +++ /dev/null @@ -1,66 +0,0 @@ -package com.anytypeio.anytype.core_ui.widgets - -import android.animation.ObjectAnimator -import android.content.Context -import android.util.AttributeSet -import android.view.LayoutInflater -import android.view.animation.AccelerateInterpolator -import android.view.animation.DecelerateInterpolator -import android.widget.FrameLayout -import androidx.core.animation.doOnEnd -import androidx.core.animation.doOnStart -import com.anytypeio.anytype.core_ui.R -import com.anytypeio.anytype.core_utils.ext.invisible -import com.anytypeio.anytype.core_utils.ext.visible - -class ScrollAndMoveHintWidget : FrameLayout { - - constructor(context: Context) : this(context, null) - constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) - constructor( - context: Context, - attrs: AttributeSet?, - defStyleAttr: Int - ) : super(context, attrs, defStyleAttr) { - init() - } - - fun init() { - LayoutInflater.from(context).inflate(R.layout.widget_sam_hint, this) - } - - fun showWithAnimation() { - ObjectAnimator.ofFloat( - this, - ANIMATED_PROPERTY, - TRANSLATION_INVISIBLE, - TRANSLATION_VISIBLE - ).apply { - duration = ANIMATION_DURATION - interpolator = DecelerateInterpolator() - doOnStart { visible() } - start() - } - } - - fun hideWithAnimation() { - ObjectAnimator.ofFloat( - this, - ANIMATED_PROPERTY, - TRANSLATION_VISIBLE, - TRANSLATION_INVISIBLE - ).apply { - duration = ANIMATION_DURATION - interpolator = AccelerateInterpolator() - doOnEnd { invisible() } - start() - } - } - - companion object { - const val ANIMATION_DURATION = 300L - const val ANIMATED_PROPERTY = "translationY" - const val TRANSLATION_VISIBLE = 0f - const val TRANSLATION_INVISIBLE = -1000f - } -} \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/ObjectTopToolbar.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/ObjectTopToolbar.kt index e51d910cd5..8c32f33fd0 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/ObjectTopToolbar.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/ObjectTopToolbar.kt @@ -1,8 +1,6 @@ package com.anytypeio.anytype.core_ui.widgets.toolbar import android.content.Context -import android.content.res.ColorStateList -import android.graphics.Color import android.util.AttributeSet import android.view.LayoutInflater import android.view.View @@ -13,8 +11,6 @@ import com.anytypeio.anytype.core_ui.R import com.anytypeio.anytype.core_ui.databinding.WidgetObjectTopToolbarBinding import com.anytypeio.anytype.core_ui.widgets.ObjectIconWidget import com.anytypeio.anytype.core_ui.widgets.StatusBadgeWidget -import com.anytypeio.anytype.core_utils.ext.invisible -import com.anytypeio.anytype.core_utils.ext.visible class ObjectTopToolbar @JvmOverloads constructor( context: Context, @@ -26,6 +22,7 @@ class ObjectTopToolbar @JvmOverloads constructor( ) val status: StatusBadgeWidget get() = binding.statusBadge + val backContainer: View get() = binding.topBackButton val back: View get() = binding.ivTopBackButton val menu: View get() = binding.threeDotsButton val container: ViewGroup get() = binding.titleContainer @@ -41,16 +38,10 @@ class ObjectTopToolbar @JvmOverloads constructor( overCover: Boolean ) = with(binding) { if (overCover) { - menu.setBackgroundResource(R.drawable.rect_object_menu_button_default) - //topBackButton.setBackgroundResource(R.drawable.rect_object_menu_button_default) statusBadge.setBackgroundResource(R.drawable.rect_object_menu_button_default) - ivThreeDots.imageTintList = ColorStateList.valueOf(Color.WHITE) - ivTopBackButton.imageTintList = ColorStateList.valueOf(Color.WHITE) } else { menu.background = null topBackButton.background = null - ivTopBackButton.imageTintList = null - ivThreeDots.imageTintList = null statusBadge.background = null } } diff --git a/core-ui/src/main/res/drawable/bg_object_header_emoji_container.xml b/core-ui/src/main/res/drawable/bg_object_header_emoji_container.xml new file mode 100644 index 0000000000..c221025155 --- /dev/null +++ b/core-ui/src/main/res/drawable/bg_object_header_emoji_container.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/core-ui/src/main/res/drawable/bg_object_header_icon_container.xml b/core-ui/src/main/res/drawable/bg_object_header_icon_container.xml new file mode 100644 index 0000000000..f9a59aac42 --- /dev/null +++ b/core-ui/src/main/res/drawable/bg_object_header_icon_container.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/core-ui/src/main/res/drawable/bg_object_header_icon_profile_container.xml b/core-ui/src/main/res/drawable/bg_object_header_icon_profile_container.xml new file mode 100644 index 0000000000..4cf7e3d7e2 --- /dev/null +++ b/core-ui/src/main/res/drawable/bg_object_header_icon_profile_container.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/core-ui/src/main/res/drawable/ic_object_icon_task_selector.xml b/core-ui/src/main/res/drawable/ic_object_icon_task_selector.xml new file mode 100644 index 0000000000..f45a4185a3 --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_object_icon_task_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/core-ui/src/main/res/drawable/ic_todo_title_checkbox.xml b/core-ui/src/main/res/drawable/ic_todo_title_checkbox.xml index ecf8eb24b3..d441351763 100644 --- a/core-ui/src/main/res/drawable/ic_todo_title_checkbox.xml +++ b/core-ui/src/main/res/drawable/ic_todo_title_checkbox.xml @@ -7,5 +7,5 @@ android:strokeWidth="1" android:pathData="M7.563,1.5L20.563,1.5A6,6 0,0 1,26.563 7.5L26.563,20.5A6,6 0,0 1,20.563 26.5L7.563,26.5A6,6 0,0 1,1.563 20.5L1.563,7.5A6,6 0,0 1,7.563 1.5z" android:fillColor="@color/transparent_black" - android:strokeColor="@color/gray"/> + android:strokeColor="@color/glyph_active"/> diff --git a/core-ui/src/main/res/layout/item_block_title.xml b/core-ui/src/main/res/layout/item_block_title.xml index 3c254df2e6..82c0aeacca 100644 --- a/core-ui/src/main/res/layout/item_block_title.xml +++ b/core-ui/src/main/res/layout/item_block_title.xml @@ -1,93 +1,51 @@ - - - + android:layout_height="wrap_content" + android:orientation="vertical"> + android:layout_width="match_parent" + android:layout_height="wrap_content"> - - + tools:background="@color/orange" + tools:visibility="gone" /> + + - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/core-ui/src/main/res/layout/item_block_title_file.xml b/core-ui/src/main/res/layout/item_block_title_file.xml index fc8509ea10..35df724c9c 100644 --- a/core-ui/src/main/res/layout/item_block_title_file.xml +++ b/core-ui/src/main/res/layout/item_block_title_file.xml @@ -17,16 +17,16 @@ diff --git a/core-ui/src/main/res/layout/item_block_title_profile.xml b/core-ui/src/main/res/layout/item_block_title_profile.xml index ed525a200c..323265fa90 100644 --- a/core-ui/src/main/res/layout/item_block_title_profile.xml +++ b/core-ui/src/main/res/layout/item_block_title_profile.xml @@ -19,38 +19,24 @@ tools:visibility="visible" /> - - - - - + android:transitionName="@string/logo_transition" + app:imageSize="112dp" + app:initialTextSize="64sp" + tools:visibility="visible" /> diff --git a/core-ui/src/main/res/layout/item_block_title_todo.xml b/core-ui/src/main/res/layout/item_block_title_todo.xml index f7ddb73214..4a79643592 100644 --- a/core-ui/src/main/res/layout/item_block_title_todo.xml +++ b/core-ui/src/main/res/layout/item_block_title_todo.xml @@ -20,34 +20,31 @@ tools:background="@color/orange" tools:visibility="gone" /> - + app:layout_goneMarginTop="@dimen/dp_126" /> diff --git a/core-ui/src/main/res/layout/widget_object_icon.xml b/core-ui/src/main/res/layout/widget_object_icon.xml index c14b5d04a7..45c276a1d9 100644 --- a/core-ui/src/main/res/layout/widget_object_icon.xml +++ b/core-ui/src/main/res/layout/widget_object_icon.xml @@ -24,6 +24,7 @@ android:id="@+id/ivImage" android:layout_width="match_parent" android:layout_height="match_parent" + android:layout_gravity="center" android:scaleType="centerCrop"/> + android:layout_width="38dp" + android:layout_height="match_parent" + android:layout_gravity="end|center_vertical"> diff --git a/core-ui/src/main/res/layout/widget_sam_hint.xml b/core-ui/src/main/res/layout/widget_sam_hint.xml deleted file mode 100644 index 24c3b0c011..0000000000 --- a/core-ui/src/main/res/layout/widget_sam_hint.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - \ No newline at end of file diff --git a/core-ui/src/main/res/values/dimens.xml b/core-ui/src/main/res/values/dimens.xml index 8fc5d10d42..6b9a8d9e83 100644 --- a/core-ui/src/main/res/values/dimens.xml +++ b/core-ui/src/main/res/values/dimens.xml @@ -27,10 +27,11 @@ 1dp 16dp 12dp - 32dp 20dp 24dp - 104dp + 28dp + 32dp + 34dp 40dp 46dp 48dp @@ -40,9 +41,16 @@ 80dp 51dp 60dp + 88dp 92dp + 96dp 102dp + 104dp 120dp + 124dp + 126dp + 148dp + 164dp 203dp 443dp @@ -137,7 +145,7 @@ 24dp 22dp 24sp - 188dp + 232dp 14dp 12sp 11sp diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/BlockView.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/BlockView.kt index 080897594c..d81e76ca98 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/BlockView.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/model/BlockView.kt @@ -660,12 +660,12 @@ sealed class BlockView : ViewType { override var coverGradient: String? = null, override val background: ThemeColor = ThemeColor.DEFAULT, override val color: ThemeColor = ThemeColor.DEFAULT, - val emoji: String? = null, override val image: String? = null, override val mode: Mode = Mode.EDIT, override var cursor: Int? = null, override val searchFields: List = emptyList(), - override val hint: String? = null + override val hint: String? = null, + val icon: ObjectIcon ) : Title(), Searchable { override fun getViewType() = HOLDER_TITLE } @@ -765,6 +765,7 @@ sealed class BlockView : ViewType { override var cursor: Int? = null, override val searchFields: List = emptyList(), override val hint: String? = null, + val icon: ObjectIcon ) : Title(), Searchable { override fun getViewType() = HOLDER_PROFILE_TITLE } @@ -788,7 +789,8 @@ sealed class BlockView : ViewType { override var cursor: Int? = null, override val searchFields: List = emptyList(), var isChecked: Boolean = false, - override val hint: String? = null + override val hint: String? = null, + val icon: ObjectIcon ) : Title(), Searchable { override fun getViewType() = HOLDER_TODO_TITLE } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/render/DefaultBlockViewRenderer.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/render/DefaultBlockViewRenderer.kt index fcb8db32a3..6cc2486177 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/render/DefaultBlockViewRenderer.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/render/DefaultBlockViewRenderer.kt @@ -72,6 +72,20 @@ class DefaultBlockViewRenderer @Inject constructor( onRenderFlag: (BlockViewRenderer.RenderFlag) -> Unit, ): List { + val obj = details.getObject(id = context) + + if (obj?.isValid != true) { + Timber.w("Object with id $context is not valid, skipping rendering") + return emptyList() + } + + val objType = storeOfObjectTypes.getTypeOfObject(obj) + + if (objType?.isValid != true) { + Timber.w("Object type for object with id $context is not valid, skipping rendering") + return emptyList() + } + val children = getValue(anchor) val result = mutableListOf() @@ -85,8 +99,7 @@ class DefaultBlockViewRenderer @Inject constructor( isPreviousBlockMedia = false when (content.style) { Content.Text.Style.TITLE -> { - val obj = details.getObject(id = context) - if (obj?.layout == ObjectType.Layout.NOTE) { + if (obj.layout == ObjectType.Layout.NOTE) { // Workaround for skipping title block in objects with note layout Timber.d("Skipping title rendering for object with note layout or nullable object") } else { @@ -100,7 +113,7 @@ class DefaultBlockViewRenderer @Inject constructor( root = root, restrictions = restrictions, currentObject = obj, - storeOfObjectTypes = storeOfObjectTypes, + objType = objType ) ) } @@ -1417,8 +1430,8 @@ class DefaultBlockViewRenderer @Inject constructor( root: Block, focus: Focus, restrictions: List, - currentObject: ObjectWrapper.Basic?, - storeOfObjectTypes: StoreOfObjectTypes + currentObject: ObjectWrapper.Basic, + objType: ObjectWrapper.Type ): BlockView.Title { val focusTarget = focus.target @@ -1448,22 +1461,20 @@ class DefaultBlockViewRenderer @Inject constructor( if (mode == EditorMode.Edit) Mode.EDIT else Mode.READ } - return when (currentObject?.layout) { + return when (currentObject.layout) { ObjectType.Layout.BASIC -> { BlockView.Title.Basic( mode = blockMode, id = block.id, text = content.text, - emoji = currentObject.iconEmoji?.takeIf { it.isNotBlank() }, - image = currentObject.iconImage?.takeIf { it.isNotBlank() } - ?.let { urlBuilder.medium(it) }, isFocused = resolveIsFocused(focus, block), cursor = cursor, coverColor = coverContainer.coverColor, coverImage = coverContainer.coverImage, coverGradient = coverContainer.coverGradient, background = block.parseThemeBackgroundColor(), - color = block.textColor() + color = block.textColor(), + icon = currentObject.objectIcon(builder = urlBuilder, objType = objType) ) } ObjectType.Layout.TODO -> { @@ -1478,7 +1489,8 @@ class DefaultBlockViewRenderer @Inject constructor( coverGradient = coverContainer.coverGradient, isChecked = content.isChecked == true, background = block.parseThemeBackgroundColor(), - color = block.textColor() + color = block.textColor(), + icon = currentObject.objectIcon(builder = urlBuilder, objType = objType) ) } ObjectType.Layout.PROFILE, ObjectType.Layout.PARTICIPANT -> { @@ -1495,7 +1507,8 @@ class DefaultBlockViewRenderer @Inject constructor( coverImage = coverContainer.coverImage, coverGradient = coverContainer.coverGradient, background = block.parseThemeBackgroundColor(), - color = block.textColor() + color = block.textColor(), + icon = currentObject.objectIcon(builder = urlBuilder, objType = objType) ) } ObjectType.Layout.VIDEO -> { @@ -1506,7 +1519,7 @@ class DefaultBlockViewRenderer @Inject constructor( videoUrl = urlBuilder.video( currentObject.id ), - icon = currentObject.objectIcon(builder = urlBuilder), + icon = currentObject.objectIcon(builder = urlBuilder, objType = objType), isFocused = resolveIsFocused(focus, block), cursor = cursor, coverColor = coverContainer.coverColor, @@ -1532,7 +1545,7 @@ class DefaultBlockViewRenderer @Inject constructor( coverGradient = coverContainer.coverGradient, background = block.parseThemeBackgroundColor(), color = block.textColor(), - icon = currentObject.objectIcon(builder = urlBuilder, objType = objType), + icon = currentObject.objectIcon(builder = urlBuilder, objType = objType) ) } ObjectType.Layout.IMAGE -> { @@ -1542,7 +1555,7 @@ class DefaultBlockViewRenderer @Inject constructor( id = block.id, text = content.text, image = if (!icon.isNullOrEmpty()) urlBuilder.large(icon) else null, - icon = currentObject.objectIcon(builder = urlBuilder), + icon = currentObject.objectIcon(builder = urlBuilder, objType = objType), isFocused = resolveIsFocused(focus, block), cursor = cursor, coverColor = coverContainer.coverColor, @@ -1558,19 +1571,16 @@ class DefaultBlockViewRenderer @Inject constructor( mode = blockMode, id = block.id, text = content.text, - emoji = currentObject?.iconEmoji?.takeIf { it.isNotBlank() }, - image = currentObject?.iconImage?.takeIf { it.isNotBlank() }?.let { - urlBuilder.medium(it) - }, isFocused = resolveIsFocused(focus, block), cursor = cursor, coverColor = coverContainer.coverColor, coverImage = coverContainer.coverImage, coverGradient = coverContainer.coverGradient, background = block.parseThemeBackgroundColor(), - color = block.textColor() + color = block.textColor(), + icon = currentObject.objectIcon(builder = urlBuilder, objType = objType), ).also { - Timber.w("Unexpected layout for title: ${currentObject?.layout}") + Timber.w("Unexpected layout for title: ${currentObject.layout}") } } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/ObjectSetRenderMapper.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/ObjectSetRenderMapper.kt index 75c2423d8d..3e6fb0c142 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/ObjectSetRenderMapper.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/relations/ObjectSetRenderMapper.kt @@ -24,6 +24,8 @@ import com.anytypeio.anytype.presentation.editor.cover.CoverImageHashProvider import com.anytypeio.anytype.presentation.editor.editor.model.BlockView import com.anytypeio.anytype.presentation.extension.getObject import com.anytypeio.anytype.presentation.extension.isValueRequired +import com.anytypeio.anytype.presentation.mapper.objectIcon +import com.anytypeio.anytype.presentation.objects.ObjectIcon import com.anytypeio.anytype.presentation.mapper.toCheckboxView import com.anytypeio.anytype.presentation.mapper.toDateView import com.anytypeio.anytype.presentation.mapper.toGridRecordRows @@ -218,11 +220,11 @@ fun title( return BlockView.Title.Basic( id = title.id, text = wrapper?.name.orEmpty(), - emoji = wrapper?.iconEmoji.orNull(), image = wrapper?.iconImage?.takeIf { it.isNotBlank() }?.let { urlBuilder.medium(it) }, coverImage = coverContainer.coverImage, coverColor = coverContainer.coverColor, - coverGradient = coverContainer.coverGradient + coverGradient = coverContainer.coverGradient, + icon = wrapper?.objectIcon(builder = urlBuilder) ?: ObjectIcon.None ) }