From 04ace2224f0c018465b3f453912da621d3d5b9d2 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Mon, 21 Dec 2020 18:13:04 +0300 Subject: [PATCH] Feature | New page menu + new undo/redo buttons (#1196) --- .../anytype/ui/archive/ArchiveFragment.kt | 3 + .../anytypeio/anytype/ui/page/PageFragment.kt | 68 +++++--- .../ui/page/sheets/DocMenuBottomSheet.kt | 145 ++++++++++++++++ .../main/res/drawable/ic_doc_menu_close.xml | 17 ++ .../drawable/rectangle_doc_menu_bottom.xml | 8 + .../drawable/rectangle_doc_menu_default.xml | 6 + .../res/drawable/rectangle_doc_menu_top.xml | 8 + .../layout/fragment_doc_menu_bottom_sheet.xml | 160 ++++++++++++++++++ app/src/main/res/values/strings.xml | 1 + app/src/main/res/values/styles.xml | 5 + .../core_ui/extensions/IconExtension.kt | 35 ++++ .../anytype/core_ui/widgets/AvatarWidget.kt | 4 +- .../widgets/toolbar/DocumentTopToolbar.kt | 12 ++ .../res/drawable/ic_doc_menu_move_to_bin.xml | 25 +++ .../main/res/drawable/ic_doc_menu_search.xml | 14 ++ core-ui/src/main/res/drawable/ic_redo.xml | 21 +++ core-ui/src/main/res/drawable/ic_undo.xml | 21 +++ .../rectangle_doc_menu_background.xml | 7 + .../layout/widget_document_top_toolbar.xml | 22 ++- core-ui/src/main/res/values/strings.xml | 6 + .../core_utils/ext/FragmentExtensions.kt | 11 ++ .../presentation/page/PageViewModel.kt | 59 ++++++- .../presentation/page/editor/Command.kt | 15 +- .../presentation/page/editor/Intent.kt | 8 +- .../presentation/page/editor/Orchestrator.kt | 12 +- sample/src/main/res/layout/activity_span.xml | 7 +- 26 files changed, 655 insertions(+), 45 deletions(-) create mode 100644 app/src/main/java/com/anytypeio/anytype/ui/page/sheets/DocMenuBottomSheet.kt create mode 100644 app/src/main/res/drawable/ic_doc_menu_close.xml create mode 100644 app/src/main/res/drawable/rectangle_doc_menu_bottom.xml create mode 100644 app/src/main/res/drawable/rectangle_doc_menu_default.xml create mode 100644 app/src/main/res/drawable/rectangle_doc_menu_top.xml create mode 100644 app/src/main/res/layout/fragment_doc_menu_bottom_sheet.xml create mode 100644 core-ui/src/main/java/com/anytypeio/anytype/core_ui/extensions/IconExtension.kt create mode 100644 core-ui/src/main/res/drawable/ic_doc_menu_move_to_bin.xml create mode 100644 core-ui/src/main/res/drawable/ic_doc_menu_search.xml create mode 100644 core-ui/src/main/res/drawable/ic_redo.xml create mode 100644 core-ui/src/main/res/drawable/ic_undo.xml create mode 100644 core-ui/src/main/res/drawable/rectangle_doc_menu_background.xml create mode 100644 core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/FragmentExtensions.kt diff --git a/app/src/main/java/com/anytypeio/anytype/ui/archive/ArchiveFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/archive/ArchiveFragment.kt index 4d498d2978..dd566a012a 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/archive/ArchiveFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/archive/ArchiveFragment.kt @@ -79,6 +79,9 @@ open class ArchiveFragment : NavigationFragment(R.layout.fragment_archive) { vm.onBackButtonPressed() }.launchIn(lifecycleScope) + topToolbar.undo.invisible() + topToolbar.redo.invisible() + with(bottomMenu) { update(COUNTER_INIT) findViewById(R.id.btnPutBack).setOnClickListener { diff --git a/app/src/main/java/com/anytypeio/anytype/ui/page/PageFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/page/PageFragment.kt index d930fc949a..bc34df4c65 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/page/PageFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/page/PageFragment.kt @@ -47,8 +47,6 @@ import com.anytypeio.anytype.core_ui.features.page.TurnIntoActionReceiver import com.anytypeio.anytype.core_ui.features.page.scrollandmove.DefaultScrollAndMoveTargetDescriptor import com.anytypeio.anytype.core_ui.features.page.scrollandmove.ScrollAndMoveStateListener import com.anytypeio.anytype.core_ui.features.page.scrollandmove.ScrollAndMoveTargetHighlighter -import com.anytypeio.anytype.core_ui.menu.DocumentPopUpMenu -import com.anytypeio.anytype.core_ui.menu.ProfilePopUpMenu import com.anytypeio.anytype.core_ui.reactive.clicks import com.anytypeio.anytype.core_ui.reactive.layoutChanges import com.anytypeio.anytype.core_ui.tools.ClipboardInterceptor @@ -85,6 +83,8 @@ import com.anytypeio.anytype.ui.page.modals.* import com.anytypeio.anytype.ui.page.modals.actions.BlockActionToolbarFactory import com.anytypeio.anytype.ui.page.modals.actions.DocumentIconActionMenuFragment import com.anytypeio.anytype.ui.page.modals.actions.ProfileIconActionMenuFragment +import com.anytypeio.anytype.ui.page.sheets.DocMenuBottomSheet +import com.anytypeio.anytype.ui.page.sheets.DocMenuBottomSheet.DocumentMenuActionReceiver import com.bumptech.glide.Glide import com.google.android.material.bottomsheet.BottomSheetBehavior import com.hbisoft.pickit.PickiT @@ -109,6 +109,7 @@ open class PageFragment : AddBlockFragment.AddBlockActionReceiver, TurnIntoActionReceiver, SelectProgrammingLanguageReceiver, + DocumentMenuActionReceiver, ClipboardInterceptor, PickiTCallbacks { @@ -469,6 +470,14 @@ open class PageFragment : vm.onBackButtonPressed() }.launchIn(lifecycleScope) + topToolbar.undo.clicks().onEach { + vm.onActionUndoClicked() + }.launchIn(lifecycleScope) + + topToolbar.redo.clicks().onEach { + vm.onActionRedoClicked() + }.launchIn(lifecycleScope) + mentionSuggesterToolbar.setupClicks( mentionClick = vm::onMentionSuggestClick, newPageClick = vm::onAddMentionNewPageClicked @@ -557,6 +566,8 @@ open class PageFragment : .launchIn(lifecycleScope) vm.syncStatus.onEach { status -> bindSyncStatus(status) }.launchIn(lifecycleScope) + vm.isUndoEnabled.onEach { topToolbar.setUndoState(it) }.launchIn(lifecycleScope) + vm.isRedoEnabled.onEach { topToolbar.setRedoState(it) }.launchIn(lifecycleScope) } private fun bindSyncStatus(status: SyncStatus?) { @@ -719,25 +730,25 @@ open class PageFragment : } } is Command.OpenDocumentMenu -> { - DocumentPopUpMenu( - context = requireContext(), - view = topToolbar.menu, - onArchiveClicked = vm::onArchiveThisPageClicked, - onRedoClicked = vm::onActionRedoClicked, - onUndoClicked = vm::onActionUndoClicked, - onEnterMultiSelect = vm::onEnterMultiSelectModeClicked, - onSearchClicked = vm::onEnterSearchModeClicked - ).show() + hideKeyboard() + val fr = DocMenuBottomSheet.new( + title = command.title, + status = command.status, + image = command.image, + emoji = command.emoji + ) + fr.show(childFragmentManager, null) } is Command.OpenProfileMenu -> { - ProfilePopUpMenu( - context = requireContext(), - view = topToolbar.menu, - onRedoClicked = vm::onActionRedoClicked, - onUndoClicked = vm::onActionUndoClicked, - onEnterMultiSelect = vm::onEnterMultiSelectModeClicked, - onSearchClicked = vm::onEnterSearchModeClicked - ).show() + hideKeyboard() + val fr = DocMenuBottomSheet.new( + title = command.title, + status = command.status, + image = command.image, + emoji = command.emoji, + isProfile = true + ) + fr.show(childFragmentManager, null) } is Command.OpenFullScreenImage -> { val screen = FullScreenPictureFragment.new(command.target, command.url).apply { @@ -854,7 +865,6 @@ open class PageFragment : } private fun render(state: ControlPanelState) { - if (state.navigationToolbar.isVisible) { placeholder.requestFocus() hideKeyboard() @@ -1218,6 +1228,19 @@ open class PageFragment : vm.onSelectProgrammingLanguageClicked(target, key) } + override fun onArchiveClicked() { + vm.onArchiveThisPageClicked() + } + + override fun onSearchOnPageClicked() { + vm.onEnterSearchModeClicked() + } + + override fun onDismissBlockActionToolbar() { + Blurry.delete(root) + vm.onDismissBlockActionMenu(childFragmentManager.backStackEntryCount > 0) + } + //------------ End of Anytype Custom Context Menu ------------ companion object { @@ -1242,11 +1265,6 @@ open class PageFragment : const val TAG_ALERT = "tag.alert" const val TAG_LINK = "tag.link" } - - override fun onDismissBlockActionToolbar() { - Blurry.delete(root) - vm.onDismissBlockActionMenu(childFragmentManager.backStackEntryCount > 0) - } } interface OnFragmentInteractionListener { diff --git a/app/src/main/java/com/anytypeio/anytype/ui/page/sheets/DocMenuBottomSheet.kt b/app/src/main/java/com/anytypeio/anytype/ui/page/sheets/DocMenuBottomSheet.kt new file mode 100644 index 0000000000..040db89527 --- /dev/null +++ b/app/src/main/java/com/anytypeio/anytype/ui/page/sheets/DocMenuBottomSheet.kt @@ -0,0 +1,145 @@ +package com.anytypeio.anytype.ui.page.sheets + +import android.graphics.Color +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.os.bundleOf +import androidx.lifecycle.lifecycleScope +import com.anytypeio.anytype.R +import com.anytypeio.anytype.core_ui.extensions.* +import com.anytypeio.anytype.core_ui.reactive.clicks +import com.anytypeio.anytype.core_utils.ext.arg +import com.anytypeio.anytype.core_utils.ext.firstDigitByHash +import com.anytypeio.anytype.core_utils.ext.gone +import com.anytypeio.anytype.core_utils.ext.visible +import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetFragment +import com.anytypeio.anytype.domain.common.Url +import com.anytypeio.anytype.domain.status.SyncStatus +import kotlinx.android.synthetic.main.fragment_doc_menu_bottom_sheet.* +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach + +class DocMenuBottomSheet : BaseBottomSheetFragment() { + + private val title get() = arg(TITLE_KEY) + private val status get() = SyncStatus.valueOf(arg(STATUS_KEY)) + private val image get() = arg(IMAGE_KEY) + private val emoji get() = arg(EMOJI_KEY) + private val isProfile get() = arg(IS_PROFILE_KEY) + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? = inflater.inflate(R.layout.fragment_doc_menu_bottom_sheet, container, false) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + bindTitle() + bindSyncStatus(status) + closeButton.clicks().onEach { dismiss() }.launchIn(lifecycleScope) + searchOnPageContainer + .clicks() + .onEach { + (parentFragment as? DocumentMenuActionReceiver)?.onSearchOnPageClicked() + dismiss() + } + .launchIn(lifecycleScope) + archiveContainer + .clicks() + .onEach { + (parentFragment as? DocumentMenuActionReceiver)?.onArchiveClicked() + dismiss() + } + .launchIn(lifecycleScope) + + if (image != null && !isProfile) icon.setImageOrNull(image) + if (emoji != null && !isProfile) icon.setEmojiOrNull(emoji) + + if (isProfile) { + avatar.visible() + image?.let { avatar.icon(it) } ?: avatar.bind( + name = title.orEmpty(), + color = title.orEmpty().firstDigitByHash().let { + requireContext().avatarColor(it) + } + ) + archiveContainer.gone() + searchOnPageContainer.setBackgroundResource(R.drawable.rectangle_doc_menu_default) + } + } + + private fun bindTitle() { + tvTitle.text = title ?: getString(R.string.untitled) + } + + private fun bindSyncStatus(status: SyncStatus) { + when (status) { + SyncStatus.UNKNOWN -> { + badge.tint( + color = requireContext().color(R.color.sync_status_red) + ) + tvSubtitle.setText(R.string.sync_status_unknown) + } + SyncStatus.FAILED -> { + badge.tint( + color = requireContext().color(R.color.sync_status_red) + ) + tvSubtitle.setText(R.string.sync_status_failed) + } + SyncStatus.OFFLINE -> { + badge.tint( + color = requireContext().color(R.color.sync_status_red) + ) + tvSubtitle.setText(R.string.sync_status_offline) + } + SyncStatus.SYNCING -> { + badge.tint( + color = requireContext().color(R.color.sync_status_orange) + ) + tvSubtitle.setText(R.string.sync_status_syncing) + } + SyncStatus.SYNCED -> { + badge.tint( + color = requireContext().color(R.color.sync_status_green) + ) + tvSubtitle.setText(R.string.sync_status_synced) + } + else -> badge.tint(Color.WHITE) + } + } + + override fun injectDependencies() {} + override fun releaseDependencies() {} + + companion object { + fun new( + title: String?, + status: SyncStatus, + image: Url?, + emoji: String?, + isProfile: Boolean = false + ) = DocMenuBottomSheet().apply { + arguments = bundleOf( + TITLE_KEY to title, + STATUS_KEY to status.name, + IMAGE_KEY to image, + EMOJI_KEY to emoji, + IS_PROFILE_KEY to isProfile + ) + } + + private const val TITLE_KEY = "arg.doc-menu-bottom-sheet.title" + private const val IMAGE_KEY = "arg.doc-menu-bottom-sheet.image" + private const val EMOJI_KEY = "arg.doc-menu-bottom-sheet.emoji" + private const val STATUS_KEY = "arg.doc-menu-bottom-sheet.status" + private const val IS_PROFILE_KEY = "arg.doc-menu-bottom-sheet.is-profile" + } + + interface DocumentMenuActionReceiver { + fun onArchiveClicked() + fun onSearchOnPageClicked() + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_doc_menu_close.xml b/app/src/main/res/drawable/ic_doc_menu_close.xml new file mode 100644 index 0000000000..218a51985d --- /dev/null +++ b/app/src/main/res/drawable/ic_doc_menu_close.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/app/src/main/res/drawable/rectangle_doc_menu_bottom.xml b/app/src/main/res/drawable/rectangle_doc_menu_bottom.xml new file mode 100644 index 0000000000..85cd5c33dc --- /dev/null +++ b/app/src/main/res/drawable/rectangle_doc_menu_bottom.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rectangle_doc_menu_default.xml b/app/src/main/res/drawable/rectangle_doc_menu_default.xml new file mode 100644 index 0000000000..effbe27c32 --- /dev/null +++ b/app/src/main/res/drawable/rectangle_doc_menu_default.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rectangle_doc_menu_top.xml b/app/src/main/res/drawable/rectangle_doc_menu_top.xml new file mode 100644 index 0000000000..f975a7aa12 --- /dev/null +++ b/app/src/main/res/drawable/rectangle_doc_menu_top.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_doc_menu_bottom_sheet.xml b/app/src/main/res/layout/fragment_doc_menu_bottom_sheet.xml new file mode 100644 index 0000000000..5b127c6fc4 --- /dev/null +++ b/app/src/main/res/layout/fragment_doc_menu_bottom_sheet.xml @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 27068775d3..c7d100772b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -143,5 +143,6 @@ Do the computation of an expensive paragraph of text on a background thread: https://t.me/joinchat/BsRYeRURhKcXDOyDA7_NLw http://www.telegram.me/joinchat/BsRYeRURhKcXDOyDA7_NLw Fetching your account… + Search on page diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 28fc5848cd..e1aaa7163e 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -179,4 +179,9 @@ 0dp + + \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/extensions/IconExtension.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/extensions/IconExtension.kt new file mode 100644 index 0000000000..5f5efe048f --- /dev/null +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/extensions/IconExtension.kt @@ -0,0 +1,35 @@ +package com.anytypeio.anytype.core_ui.extensions + +import android.widget.ImageView +import com.anytypeio.anytype.emojifier.Emojifier +import com.bumptech.glide.Glide +import com.bumptech.glide.load.engine.DiskCacheStrategy +import timber.log.Timber + +fun ImageView.setEmojiOrNull(unicode: String?) { + if (unicode != null) + try { + Glide + .with(this) + .load(Emojifier.uri(unicode)) + .diskCacheStrategy(DiskCacheStrategy.ALL) + .into(this) + } catch (e: Throwable) { + Timber.e(e, "Error while setting emoji icon for: $unicode") + } + else + setImageDrawable(null) +} + +fun ImageView.setImageOrNull(image: String?) { + if (image != null) { + Glide + .with(this) + .load(image) + .centerInside() + .circleCrop() + .into(this) + } else { + setImageDrawable(null) + } +} \ No newline at end of file diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/AvatarWidget.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/AvatarWidget.kt index 5badf1dc2f..e0fae323aa 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/AvatarWidget.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/AvatarWidget.kt @@ -2,7 +2,6 @@ package com.anytypeio.anytype.core_ui.widgets import android.content.Context import android.content.res.ColorStateList -import android.graphics.BitmapFactory import android.graphics.Color import android.util.AttributeSet import android.util.TypedValue @@ -13,7 +12,6 @@ import com.anytypeio.anytype.core_utils.ext.invisible import com.anytypeio.anytype.core_utils.ext.visible import com.bumptech.glide.Glide import kotlinx.android.synthetic.main.widget_avatar.view.* -import java.io.ByteArrayInputStream class AvatarWidget : FrameLayout { @@ -56,7 +54,7 @@ class AvatarWidget : FrameLayout { fun bind(name: String, color: Int? = null) { initials.visible() - initials.text = if (name.isNotEmpty()) name.first().toUpperCase().toString() else "" + initials.text = if (name.isNotEmpty()) name.first().toUpperCase().toString() else name icon.invisible() backgroundTintList = ColorStateList.valueOf(color ?: randomColor(name)) } diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/DocumentTopToolbar.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/DocumentTopToolbar.kt index e13b0e2691..53ef34252c 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/DocumentTopToolbar.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/widgets/toolbar/DocumentTopToolbar.kt @@ -20,6 +20,8 @@ class DocumentTopToolbar : ConstraintLayout { val title: TextView get() = toolbarTitle val emoji: TextView get() = toolbarEmojiIcon val image: ImageView get() = toolbarImageIcon + val undo: ImageView get() = btnUndo + val redo: ImageView get() = btnRedo constructor( context: Context @@ -41,4 +43,14 @@ class DocumentTopToolbar : ConstraintLayout { private fun inflate() { LayoutInflater.from(context).inflate(R.layout.widget_document_top_toolbar, this) } + + fun setUndoState(isEnabled: Boolean) { + //btnUndo.alpha = if (isEnabled) 1.0f else 0.3f + //btnUndo.isEnabled = isEnabled + } + + fun setRedoState(isEnabled: Boolean) { + //btnRedo.alpha = if (isEnabled) 1.0f else 0.3f + //btnRedo.isEnabled = isEnabled + } } \ No newline at end of file diff --git a/core-ui/src/main/res/drawable/ic_doc_menu_move_to_bin.xml b/core-ui/src/main/res/drawable/ic_doc_menu_move_to_bin.xml new file mode 100644 index 0000000000..52b23fcc9c --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_doc_menu_move_to_bin.xml @@ -0,0 +1,25 @@ + + + + + + + diff --git a/core-ui/src/main/res/drawable/ic_doc_menu_search.xml b/core-ui/src/main/res/drawable/ic_doc_menu_search.xml new file mode 100644 index 0000000000..f60dadb2a0 --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_doc_menu_search.xml @@ -0,0 +1,14 @@ + + + + diff --git a/core-ui/src/main/res/drawable/ic_redo.xml b/core-ui/src/main/res/drawable/ic_redo.xml new file mode 100644 index 0000000000..f914b7106a --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_redo.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/core-ui/src/main/res/drawable/ic_undo.xml b/core-ui/src/main/res/drawable/ic_undo.xml new file mode 100644 index 0000000000..cf364b711b --- /dev/null +++ b/core-ui/src/main/res/drawable/ic_undo.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/core-ui/src/main/res/drawable/rectangle_doc_menu_background.xml b/core-ui/src/main/res/drawable/rectangle_doc_menu_background.xml new file mode 100644 index 0000000000..45ebe3bf81 --- /dev/null +++ b/core-ui/src/main/res/drawable/rectangle_doc_menu_background.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/core-ui/src/main/res/layout/widget_document_top_toolbar.xml b/core-ui/src/main/res/layout/widget_document_top_toolbar.xml index 1d0df37e69..d14ff8cdf0 100644 --- a/core-ui/src/main/res/layout/widget_document_top_toolbar.xml +++ b/core-ui/src/main/res/layout/widget_document_top_toolbar.xml @@ -53,7 +53,7 @@ android:textSize="15sp" android:visibility="invisible" app:layout_constraintBottom_toBottomOf="@+id/toolbarIconContainer" - app:layout_constraintEnd_toStartOf="@+id/toolbarMenu" + app:layout_constraintEnd_toStartOf="@+id/btnUndo" app:layout_constraintStart_toEndOf="@+id/toolbarIconContainer" app:layout_constraintTop_toTopOf="@+id/toolbarIconContainer" tools:text="Documents title" /> @@ -80,4 +80,24 @@ app:layout_constraintTop_toTopOf="parent" tools:backgroundTint="@color/anytype_text_green" /> + + + + \ No newline at end of file diff --git a/core-ui/src/main/res/values/strings.xml b/core-ui/src/main/res/values/strings.xml index 1efcec9f33..9f754a0c28 100644 --- a/core-ui/src/main/res/values/strings.xml +++ b/core-ui/src/main/res/values/strings.xml @@ -242,4 +242,10 @@ %d pages selected + Preparing… + No connection + Syncing… + Synced + Not synced + diff --git a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/FragmentExtensions.kt b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/FragmentExtensions.kt new file mode 100644 index 0000000000..4b352efb10 --- /dev/null +++ b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/FragmentExtensions.kt @@ -0,0 +1,11 @@ +package com.anytypeio.anytype.core_utils.ext + +import androidx.fragment.app.Fragment + +inline fun Fragment.arg(key: String): T { + return requireArguments().get(key) as T +} + +inline fun Fragment.argOrNull(key: String): T? { + return requireArguments().get(key) as T? +} \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/page/PageViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/page/PageViewModel.kt index 7cf2422ec6..114bb73f2d 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/page/PageViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/page/PageViewModel.kt @@ -121,6 +121,9 @@ class PageViewModel( val syncStatus = MutableStateFlow(null) + val isUndoEnabled = MutableStateFlow(false) + val isRedoEnabled = MutableStateFlow(false) + val searchResultScrollPosition = MutableStateFlow(NO_SEARCH_RESULT_POSITION) private val session = MutableStateFlow(Session.IDLE) @@ -929,14 +932,58 @@ class PageViewModel( check(content is Content.Smart) when (content.type) { Content.Smart.Type.PROFILE -> { - dispatch(command = Command.OpenProfileMenu) + val details = orchestrator.stores.details.current().details + dispatch( + command = Command.OpenProfileMenu( + status = syncStatus.value ?: SyncStatus.UNKNOWN, + title = try { + blocks.title().content().text + } catch (e: Throwable) { + null + }, + emoji = details[context]?.iconEmoji?.let { name -> + if (name.isNotEmpty()) + name + else + null + }, + image = details[context]?.iconImage?.let { name -> + if (name.isNotEmpty()) + urlBuilder.image(name) + else + null + } + ) + ) viewModelScope.sendEvent( analytics = analytics, eventName = POPUP_PROFILE_MENU ) } Content.Smart.Type.PAGE -> { - dispatch(command = Command.OpenDocumentMenu) + val details = orchestrator.stores.details.current().details + dispatch( + command = Command.OpenDocumentMenu( + status = syncStatus.value ?: SyncStatus.UNKNOWN, + title = try { + blocks.title().content().text + } catch (e: Throwable) { + null + }, + emoji = details[context]?.iconEmoji?.let { name -> + if (name.isNotEmpty()) + name + else + null + }, + image = details[context]?.iconImage?.let { name -> + if (name.isNotEmpty()) + urlBuilder.image(name) + else + null + } + ) + ) viewModelScope.sendEvent( analytics = analytics, eventName = POPUP_DOCUMENT_MENU @@ -1438,7 +1485,9 @@ class PageViewModel( viewModelScope.launch { orchestrator.proxies.intents.send( Intent.Document.Undo( - context = context + context = context, + onSuccessSideEffect = { isUndoEnabled.value = true }, + onFailureSideEffect = { isUndoEnabled.value = false } ) ) } @@ -1448,7 +1497,9 @@ class PageViewModel( viewModelScope.launch { orchestrator.proxies.intents.send( Intent.Document.Redo( - context = context + context = context, + onSuccessSideEffect = { isRedoEnabled.value = true }, + onFailureSideEffect = { isRedoEnabled.value = false } ) ) } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/page/editor/Command.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/page/editor/Command.kt index d020b00343..2dd2731b71 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/page/editor/Command.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/page/editor/Command.kt @@ -2,6 +2,7 @@ package com.anytypeio.anytype.presentation.page.editor import com.anytypeio.anytype.domain.common.Id import com.anytypeio.anytype.domain.common.Url +import com.anytypeio.anytype.domain.status.SyncStatus import com.anytypeio.anytype.presentation.page.editor.model.BlockView sealed class Command { @@ -65,9 +66,19 @@ sealed class Command { val url: Url ) : Command() - object OpenDocumentMenu : Command() + data class OpenDocumentMenu( + val status: SyncStatus, + val title: String?, + val emoji: String?, + val image: String? + ) : Command() - object OpenProfileMenu : Command() + data class OpenProfileMenu( + val status: SyncStatus, + val title: String?, + val emoji: String?, + val image: String? + ) : Command() object AlertDialog : Command() diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/page/editor/Intent.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/page/editor/Intent.kt index d70896fae3..f1a8afd107 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/page/editor/Intent.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/page/editor/Intent.kt @@ -9,11 +9,15 @@ sealed class Intent { sealed class Document : Intent() { class Undo( - val context: Id + val context: Id, + val onSuccessSideEffect: () -> Unit, + val onFailureSideEffect: () -> Unit ) : Document() class Redo( - val context: Id + val context: Id, + val onSuccessSideEffect: () -> Unit, + val onFailureSideEffect: () -> Unit ) : Document() class UpdateTitle( diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/page/editor/Orchestrator.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/page/editor/Orchestrator.kt index c56fae6f8e..f1fde0a6fb 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/page/editor/Orchestrator.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/page/editor/Orchestrator.kt @@ -425,7 +425,7 @@ class Orchestrator( context = intent.context ) ).proceed( - failure = defaultOnError, + failure = defaultOnError.also { intent.onFailureSideEffect }, success = { payload -> val event = EventAnalytics.Anytype( name = BLOCK_REDO, @@ -435,7 +435,9 @@ class Orchestrator( middleware = System.currentTimeMillis() ) ) - defaultPayloadWithEvent(Pair(payload, event)) + defaultPayloadWithEvent(Pair(payload, event)).also { + intent.onSuccessSideEffect() + } } ) } @@ -446,7 +448,7 @@ class Orchestrator( context = intent.context ) ).proceed( - failure = defaultOnError, + failure = defaultOnError.also { intent.onFailureSideEffect() }, success = { payload -> val event = EventAnalytics.Anytype( name = BLOCK_UNDO, @@ -456,7 +458,9 @@ class Orchestrator( middleware = System.currentTimeMillis() ) ) - defaultPayloadWithEvent(Pair(payload, event)) + defaultPayloadWithEvent(Pair(payload, event)).also { + intent.onSuccessSideEffect() + } } ) } diff --git a/sample/src/main/res/layout/activity_span.xml b/sample/src/main/res/layout/activity_span.xml index 6dd6854ce8..9b4d74ba70 100644 --- a/sample/src/main/res/layout/activity_span.xml +++ b/sample/src/main/res/layout/activity_span.xml @@ -1,12 +1,11 @@ + app:layout_constraintTop_toBottomOf="@+id/title" />