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

Feature | New page menu + new undo/redo buttons (#1196)

This commit is contained in:
Evgenii Kozlov 2020-12-21 18:13:04 +03:00 committed by GitHub
parent 5bcc165fcd
commit 04ace2224f
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 655 additions and 45 deletions

View file

@ -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<TextView>(R.id.btnPutBack).setOnClickListener {

View file

@ -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 {

View file

@ -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<String?>(TITLE_KEY)
private val status get() = SyncStatus.valueOf(arg(STATUS_KEY))
private val image get() = arg<String?>(IMAGE_KEY)
private val emoji get() = arg<String?>(EMOJI_KEY)
private val isProfile get() = arg<Boolean>(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()
}
}

View file

@ -0,0 +1,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="29dp"
android:height="28dp"
android:viewportWidth="29"
android:viewportHeight="28">
<path
android:fillColor="#EAE9E0"
android:pathData="M28.75,14C28.75,21.732 22.482,28 14.75,28C7.018,28 0.75,21.732 0.75,14C0.75,6.268 7.018,0 14.75,0C22.482,0 28.75,6.268 28.75,14Z" />
<path
android:fillColor="#ACA996"
android:fillType="evenOdd"
android:pathData="M9.5429,8.7929C9.9334,8.4024 10.5666,8.4024 10.9571,8.7929L19.9571,17.7929C20.3476,18.1834 20.3476,18.8166 19.9571,19.2071C19.5666,19.5976 18.9334,19.5976 18.5429,19.2071L9.5429,10.2071C9.1524,9.8166 9.1524,9.1834 9.5429,8.7929Z" />
<path
android:fillColor="#ACA996"
android:fillType="evenOdd"
android:pathData="M9.5429,19.2071C9.1524,18.8166 9.1524,18.1834 9.5429,17.7929L18.5429,8.7929C18.9334,8.4024 19.5666,8.4024 19.9571,8.7929C20.3476,9.1834 20.3476,9.8166 19.9571,10.2071L10.9571,19.2071C10.5666,19.5976 9.9334,19.5976 9.5429,19.2071Z" />
</vector>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/white" />
<corners
android:bottomLeftRadius="10dp"
android:bottomRightRadius="10dp" />
</shape>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/white" />
<corners android:radius="10dp" />
</shape>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/white" />
<corners
android:topLeftRadius="10dp"
android:topRightRadius="10dp" />
</shape>

View file

@ -0,0 +1,160 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="wrap_content"
android:paddingBottom="12dp"
android:background="@drawable/rectangle_doc_menu_background">
<FrameLayout
android:id="@+id/iconContainer"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/icon"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="center"
tools:src="@drawable/circle_solid_default" />
<com.anytypeio.anytype.core_ui.widgets.AvatarWidget
android:id="@+id/avatar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/circle_solid_default"
android:visibility="invisible" />
</FrameLayout>
<ImageView
android:id="@+id/closeButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="18dp"
android:src="@drawable/ic_doc_menu_close"
app:layout_constraintBottom_toBottomOf="@+id/iconContainer"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/iconContainer" />
<View
android:id="@+id/badge"
android:layout_width="8dp"
android:layout_height="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="3dp"
android:background="@drawable/circle_solid_default"
android:backgroundTint="@color/white"
app:layout_constraintStart_toEndOf="@+id/iconContainer"
app:layout_constraintTop_toBottomOf="@+id/tvTitle"
tools:background="@drawable/circle_solid_default"
tools:backgroundTint="@color/anytype_text_red" />
<TextView
android:id="@+id/tvTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="15dp"
android:layout_marginEnd="8dp"
android:fontFamily="@font/inter_medium"
android:textColor="@color/black"
android:textSize="15sp"
app:layout_constraintEnd_toStartOf="@+id/closeButton"
app:layout_constraintStart_toEndOf="@+id/iconContainer"
app:layout_constraintTop_toTopOf="parent"
tools:text="Spaceship Earth" />
<TextView
android:id="@+id/tvSubtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:textSize="13sp"
android:lineHeight="20sp"
app:layout_constraintBottom_toBottomOf="@+id/badge"
app:layout_constraintStart_toEndOf="@+id/badge"
app:layout_constraintTop_toTopOf="@+id/badge"
tools:text="Offline" />
<View
android:background="#DFDDD0"
android:id="@+id/statusDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="12dp"
app:layout_constraintTop_toBottomOf="@+id/iconContainer" />
<FrameLayout
android:id="@+id/searchOnPageContainer"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="12dp"
android:background="@drawable/rectangle_doc_menu_top"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/statusDivider">
<TextView
style="@style/DocMenuOptionTextStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="16dp"
android:text="@string/search_on_page" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:layout_marginEnd="12dp"
android:src="@drawable/ic_doc_menu_search" />
</FrameLayout>
<View
android:id="@+id/optionDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintTop_toBottomOf="@+id/searchOnPageContainer" />
<FrameLayout
android:id="@+id/archiveContainer"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:background="@drawable/rectangle_doc_menu_bottom"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/optionDivider">
<TextView
style="@style/DocMenuOptionTextStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="16dp"
android:text="@string/archive" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:layout_marginEnd="12dp"
android:src="@drawable/ic_doc_menu_move_to_bin" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -143,5 +143,6 @@ Do the computation of an expensive paragraph of text on a background thread:
<string name="telegram_app">https://t.me/joinchat/BsRYeRURhKcXDOyDA7_NLw</string>
<string name="telegram_web">http://www.telegram.me/joinchat/BsRYeRURhKcXDOyDA7_NLw</string>
<string name="fetching_your_account">Fetching your account…</string>
<string name="search_on_page">Search on page</string>
</resources>

View file

@ -179,4 +179,9 @@
<item name="cornerSizeBottomLeft">0dp</item>
</style>
<style name="DocMenuOptionTextStyle">
<item name="android:textSize">17sp</item>
<item name="android:textColor">@color/black</item>
</style>
</resources>

View file

@ -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)
}
}

View file

@ -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))
}

View file

@ -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
}
}

View file

@ -0,0 +1,25 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#00000000"
android:pathData="M8.75,3C8.75,2.3096 9.3096,1.75 10,1.75H14C14.6904,1.75 15.25,2.3096 15.25,3V5.25H8.75V3Z"
android:strokeWidth="1.5"
android:strokeColor="#ACA996" />
<path
android:fillColor="#00000000"
android:pathData="M5.6577,19.5743L5.2705,5.25H18.7268L18.291,19.5836C18.2459,21.0691 17.0285,22.25 15.5423,22.25H8.4067C6.9168,22.25 5.6979,21.0636 5.6577,19.5743Z"
android:strokeWidth="1.5"
android:strokeColor="#ACA996" />
<path
android:fillColor="#ACA996"
android:pathData="M9.3125,8.001H10.8125V19.501H9.3125V8.001Z" />
<path
android:fillColor="#ACA996"
android:pathData="M13.3125,8.001H14.8125V19.501H13.3125V8.001Z" />
<path
android:fillColor="#ACA996"
android:pathData="M3.2485,6C2.8343,6 2.5,5.6642 2.5,5.25C2.5,4.8358 2.8358,4.5 3.25,4.5L20.75,4.5C21.1642,4.5 21.5,4.8358 21.5,5.25C21.5,5.6642 21.1668,6 20.7526,6C17.5914,6 6.2745,6 3.2485,6Z" />
</vector>

View file

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#ACA996"
android:fillType="evenOdd"
android:pathData="M14.7929,14.7929C15.1834,14.4024 15.8166,14.4024 16.2071,14.7929L21.7071,20.2929C22.0976,20.6834 22.0976,21.3166 21.7071,21.7071C21.3166,22.0976 20.6834,22.0976 20.2929,21.7071L14.7929,16.2071C14.4024,15.8166 14.4024,15.1834 14.7929,14.7929Z" />
<path
android:fillColor="#ACA996"
android:fillType="evenOdd"
android:pathData="M10,16.5C13.5899,16.5 16.5,13.5899 16.5,10C16.5,6.4102 13.5899,3.5 10,3.5C6.4102,3.5 3.5,6.4102 3.5,10C3.5,13.5899 6.4102,16.5 10,16.5ZM10,18C14.4183,18 18,14.4183 18,10C18,5.5817 14.4183,2 10,2C5.5817,2 2,5.5817 2,10C2,14.4183 5.5817,18 10,18Z" />
</vector>

View file

@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#ACA996"
android:fillType="evenOdd"
android:pathData="M15.2196,2.4697C14.9267,2.7626 14.9267,3.2374 15.2196,3.5303L18.6892,7L15.2196,10.4697C14.9267,10.7626 14.9267,11.2374 15.2196,11.5303C15.5125,11.8232 15.9873,11.8232 16.2802,11.5303L20.8105,7L16.2802,2.4697C15.9873,2.1768 15.5125,2.1768 15.2196,2.4697Z" />
<path
android:fillColor="#00000000"
android:pathData="M10,19C6.6863,19 4,16.3137 4,13C4,9.6863 6.6863,7 10,7"
android:strokeWidth="1.5"
android:strokeColor="#ACA996" />
<path
android:fillColor="#ACA996"
android:pathData="M10,6.25h9v1.5h-9z" />
<path
android:fillColor="#ACA996"
android:pathData="M10,18.25h2v1.5h-2z" />
</vector>

View file

@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#ACA996"
android:fillType="evenOdd"
android:pathData="M8.7804,2.4697C9.0733,2.7626 9.0733,3.2374 8.7804,3.5303L5.3108,7L8.7804,10.4697C9.0733,10.7626 9.0733,11.2374 8.7804,11.5303C8.4875,11.8232 8.0127,11.8232 7.7198,11.5303L3.1895,7L7.7198,2.4697C8.0127,2.1768 8.4875,2.1768 8.7804,2.4697Z" />
<path
android:fillColor="#00000000"
android:pathData="M14,19C17.3137,19 20,16.3137 20,13C20,9.6863 17.3137,7 14,7"
android:strokeWidth="1.5"
android:strokeColor="#ACA996" />
<path
android:fillColor="#ACA996"
android:pathData="M14,6.25l-9,0l-0,1.5l9,0z" />
<path
android:fillColor="#ACA996"
android:pathData="M14,18.25l-2,0l-0,1.5l2,0z" />
</vector>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#F3F2EC" />
<corners
android:topLeftRadius="8dp"
android:topRightRadius="8dp" />
</shape>

View file

@ -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" />
<ImageView
android:id="@+id/btnUndo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:src="@drawable/ic_undo"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btnRedo"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/btnRedo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:src="@drawable/ic_redo"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/toolbarMenu"
app:layout_constraintTop_toTopOf="parent" />
</merge>

View file

@ -242,4 +242,10 @@
<item quantity="other">%d pages selected</item>
</plurals>
<string name="sync_status_unknown">Preparing…</string>
<string name="sync_status_offline">No connection</string>
<string name="sync_status_syncing">Syncing…</string>
<string name="sync_status_synced">Synced</string>
<string name="sync_status_failed">Not synced</string>
</resources>

View file

@ -0,0 +1,11 @@
package com.anytypeio.anytype.core_utils.ext
import androidx.fragment.app.Fragment
inline fun <reified T> Fragment.arg(key: String): T {
return requireArguments().get(key) as T
}
inline fun <reified T> Fragment.argOrNull(key: String): T? {
return requireArguments().get(key) as T?
}

View file

@ -121,6 +121,9 @@ class PageViewModel(
val syncStatus = MutableStateFlow<SyncStatus?>(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<Content.Text>().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<Content.Text>().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 }
)
)
}

View file

@ -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()

View file

@ -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(

View file

@ -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()
}
}
)
}

View file

@ -1,12 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">
<TextView
android:id="@+id/textView4"
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
@ -31,7 +30,7 @@
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView4" />
app:layout_constraintTop_toBottomOf="@+id/title" />
<TextView
android:id="@+id/textView3"
@ -48,7 +47,7 @@
app:layout_constraintTop_toBottomOf="@+id/textView2" />
<TextView
android:id="@+id/textView5"
android:id="@+id/subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"