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

Editor | Feature | Slash widget, part 2 (#1446)

* Editor | Feature | Slash widget, main, style, media items (#1425)

* slash widget style items

* ci off

* fixes

* fixes

* remove legacy tests

* fixes

* remove link from slash widget

* show media items

* fixes

* fixes

* fix

* move slash widget logic to region space

* fix

* Editor | Feature | Slash widget, relation items (#1439)

* slash widget style items

* ci off

* fixes

* fixes

* remove legacy tests

* fixes

* remove link from slash widget

* show media items

* fixes

* fixes

* fix

* add object type

* move slash widget logic to region space

* fix

* slash relation model

* add concat adapter

* fixes

* show sub header + relations

* relations test

* add paddings

* pr fixes

* Editor | Feature | Slash widget, show object types (#1443)

* show object types

* fix tests

* lot of fixes

* fixes

* fix

* fix

* fixes

* pr fix

* Editor | Feature | Slash widget, style, media clicks (#1445)

* fixes

* rename

* create object type

* fix

* ci

* fix test
This commit is contained in:
Konstantin Ivanov 2021-05-05 15:27:32 +03:00 committed by GitHub
parent 21cb51e789
commit d84ab2319c
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
52 changed files with 2541 additions and 661 deletions

View file

@ -1,7 +1,7 @@
on:
pull_request:
# add "synchronize" in "types", in order to trigger workflow for pull request commit(s) pushes.
types: [opened]
types: [synchronize]
branches: [develop]
name: Run debug unit tests
jobs:

View file

@ -0,0 +1,498 @@
package com.anytypeio.anytype.features.editor
import android.os.Bundle
import android.view.KeyEvent
import androidx.core.os.bundleOf
import androidx.fragment.app.testing.FragmentScenario
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.test.espresso.action.ViewActions
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_ui.widgets.text.TextInputWidget
import com.anytypeio.anytype.emojifier.data.DefaultDocumentEmojiIconProvider
import com.anytypeio.anytype.features.editor.base.EditorTestSetup
import com.anytypeio.anytype.features.editor.base.TestPageFragment
import com.anytypeio.anytype.mocking.MockDataFactory
import com.anytypeio.anytype.presentation.page.PageViewModel
import com.anytypeio.anytype.ui.page.PageFragment
import com.anytypeio.anytype.utils.*
import com.bartoszlipinski.disableanimationsrule.DisableAnimationsRule
import kotlinx.android.synthetic.main.fragment_page.*
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@LargeTest
class SlashTextWatcherTesting : EditorTestSetup() {
@get:Rule
val animationsRule = DisableAnimationsRule()
@get:Rule
val coroutineTestRule = CoroutinesTestRule()
private val args = bundleOf(PageFragment.ID_KEY to root)
private val defaultDetails = Block.Details(
mapOf(
root to Block.Fields(
mapOf(
"iconEmoji" to DefaultDocumentEmojiIconProvider.DOCUMENT_SET.random()
)
)
)
)
private val title = Block(
id = MockDataFactory.randomUuid(),
content = Block.Content.Text(
style = Block.Content.Text.Style.TITLE,
text = "SlashTextWatcherTesting",
marks = emptyList()
),
children = emptyList(),
fields = Block.Fields.empty()
)
private val header = Block(
id = MockDataFactory.randomUuid(),
content = Block.Content.Layout(
type = Block.Content.Layout.Type.HEADER
),
fields = Block.Fields.empty(),
children = listOf(title.id)
)
@Before
override fun setup() {
super.setup()
}
@Test
fun testShouldShowSlashWidgetOnSlashInEmptyText() {
val paragraph = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields.empty(),
children = emptyList(),
content = Block.Content.Text(
text = "",
marks = emptyList(),
style = Block.Content.Text.Style.P
)
)
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(
type = Block.Content.Smart.Type.PAGE
),
children = listOf(header.id, paragraph.id)
)
val document = listOf(page, header, title, paragraph)
stubInterceptEvents()
stubInterceptThreadStatus()
stubUpdateText()
stubOpenDocument(document, defaultDetails)
launchFragment(args)
with(R.id.recycler.rVMatcher()) {
onItemView(1, R.id.textContent).perform(ViewActions.click())
onItemView(1, R.id.textContent)
.perform(ViewActions.pressKey(KeyEvent.KEYCODE_SLASH))
}
R.id.slashWidget.matchView().checkIsDisplayed()
advance(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
}
@Test
fun testShouldShowSlashWidgetOnSlashAfterSpace() {
val paragraph = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields.empty(),
children = emptyList(),
content = Block.Content.Text(
text = "Foo",
marks = emptyList(),
style = Block.Content.Text.Style.P
)
)
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(
type = Block.Content.Smart.Type.PAGE
),
children = listOf(header.id, paragraph.id)
)
val document = listOf(page, header, title, paragraph)
stubInterceptEvents()
stubInterceptThreadStatus()
stubUpdateText()
stubOpenDocument(document, defaultDetails)
launchFragment(args)
with(R.id.recycler.rVMatcher()) {
onItemView(1, R.id.textContent).perform(ViewActions.click())
onItemView(1, R.id.textContent)
.perform(ViewActions.pressKey(KeyEvent.KEYCODE_SPACE))
onItemView(1, R.id.textContent)
.perform(ViewActions.pressKey(KeyEvent.KEYCODE_SLASH))
}
R.id.slashWidget.matchView().checkIsDisplayed()
advance(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
}
@Test
fun testShouldShowSlashWidgetOnSlashAfterText() {
val paragraph = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields.empty(),
children = emptyList(),
content = Block.Content.Text(
text = "Foo",
marks = emptyList(),
style = Block.Content.Text.Style.P
)
)
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(
type = Block.Content.Smart.Type.PAGE
),
children = listOf(header.id, paragraph.id)
)
val document = listOf(page, header, title, paragraph)
stubInterceptEvents()
stubInterceptThreadStatus()
stubUpdateText()
stubOpenDocument(document, defaultDetails)
launchFragment(args)
with(R.id.recycler.rVMatcher()) {
onItemView(1, R.id.textContent).perform(ViewActions.click())
onItemView(1, R.id.textContent)
.perform(ViewActions.pressKey(KeyEvent.KEYCODE_SLASH))
}
R.id.slashWidget.matchView().checkIsDisplayed()
advance(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
}
@Test
fun testShouldShowSlashWidgetOnSlashBeforeText() {
val paragraph = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields.empty(),
children = emptyList(),
content = Block.Content.Text(
text = "Foo",
marks = emptyList(),
style = Block.Content.Text.Style.P
)
)
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(
type = Block.Content.Smart.Type.PAGE
),
children = listOf(header.id, paragraph.id)
)
val document = listOf(page, header, title, paragraph)
stubInterceptEvents()
stubInterceptThreadStatus()
stubUpdateText()
stubOpenDocument(document, defaultDetails)
launchFragment(args)
with(R.id.recycler.rVMatcher()) {
onItemView(1, R.id.textContent).perform(ViewActions.click())
onItemView(1, R.id.textContent).perform(ViewActions.replaceText("Foo"))
onItemView(1, R.id.textContent)
.perform(ViewActions.pressKey(KeyEvent.KEYCODE_SLASH))
}
R.id.slashWidget.matchView().checkIsDisplayed()
advance(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
}
@Test
fun testShouldShowSlashWidgetOnSlashAfterSlash() {
val paragraph = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields.empty(),
children = emptyList(),
content = Block.Content.Text(
text = "",
marks = emptyList(),
style = Block.Content.Text.Style.P
)
)
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(
type = Block.Content.Smart.Type.PAGE
),
children = listOf(header.id, paragraph.id)
)
val document = listOf(page, header, title, paragraph)
stubInterceptEvents()
stubInterceptThreadStatus()
stubUpdateText()
stubOpenDocument(document, defaultDetails)
launchFragment(args)
with(R.id.recycler.rVMatcher()) {
onItemView(1, R.id.textContent).perform(ViewActions.click())
onItemView(1, R.id.textContent)
.perform(ViewActions.pressKey(KeyEvent.KEYCODE_SLASH))
onItemView(1, R.id.textContent)
.perform(ViewActions.pressKey(KeyEvent.KEYCODE_SLASH))
}
R.id.slashWidget.matchView().checkIsDisplayed()
advance(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
}
@Test
fun testShouldShowSlashWidgetOnInsertTextWithSlashAfterSlash() {
val paragraph = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields.empty(),
children = emptyList(),
content = Block.Content.Text(
text = "Foo/",
marks = emptyList(),
style = Block.Content.Text.Style.P
)
)
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(
type = Block.Content.Smart.Type.PAGE
),
children = listOf(header.id, paragraph.id)
)
val document = listOf(page, header, title, paragraph)
stubInterceptEvents()
stubInterceptThreadStatus()
stubUpdateText()
stubOpenDocument(document, defaultDetails)
launchFragment(args)
with(R.id.recycler.rVMatcher()) {
onItemView(1, R.id.textContent).perform(ViewActions.click())
onItemView(1, R.id.textContent)
.perform(ViewActions.pressKey(KeyEvent.KEYCODE_SLASH))
}
R.id.slashWidget.matchView().checkIsDisplayed()
advance(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
}
@Test
fun testShouldCloseMenuWhenCharSlashDeleted() {
val paragraph = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields.empty(),
children = emptyList(),
content = Block.Content.Text(
text = "Foo",
marks = emptyList(),
style = Block.Content.Text.Style.P
)
)
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(
type = Block.Content.Smart.Type.PAGE
),
children = listOf(header.id, paragraph.id)
)
val document = listOf(page, header, title, paragraph)
stubInterceptEvents()
stubInterceptThreadStatus()
stubUpdateText()
stubOpenDocument(document, defaultDetails)
launchFragment(args)
with(R.id.recycler.rVMatcher()) {
onItemView(1, R.id.textContent).perform(ViewActions.click())
onItemView(1, R.id.textContent)
.perform(ViewActions.pressKey(KeyEvent.KEYCODE_SLASH))
onItemView(1, R.id.textContent)
.perform(ViewActions.pressKey(KeyEvent.KEYCODE_DEL))
}
R.id.slashWidget.matchView().checkIsNotDisplayed()
advance(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
}
@Test
fun testShouldCloseMenuWhenClickInBlock() {
val paragraph = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields.empty(),
children = emptyList(),
content = Block.Content.Text(
text = "Foo Bar",
marks = emptyList(),
style = Block.Content.Text.Style.P
)
)
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(
type = Block.Content.Smart.Type.PAGE
),
children = listOf(header.id, paragraph.id)
)
val document = listOf(page, header, title, paragraph)
stubInterceptEvents()
stubInterceptThreadStatus()
stubUpdateText()
stubOpenDocument(document, defaultDetails)
val scenario = launchFragment(args)
with(R.id.recycler.rVMatcher()) {
onItemView(1, R.id.textContent).perform(ViewActions.click())
onItemView(1, R.id.textContent)
.perform(ViewActions.pressKey(KeyEvent.KEYCODE_SLASH))
Thread.sleep(300)
R.id.slashWidget.matchView().checkIsDisplayed()
onItemView(1, R.id.textContent).perform(ViewActions.click())
Thread.sleep(300)
}
R.id.slashWidget.matchView().checkIsNotDisplayed()
advance(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
}
@Test
fun testShouldCloseMenuWhenFocusIsChanged() {
val paragraph = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields.empty(),
children = emptyList(),
content = Block.Content.Text(
text = "Foo",
marks = emptyList(),
style = Block.Content.Text.Style.P
)
)
val paragraph2 = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields.empty(),
children = emptyList(),
content = Block.Content.Text(
text = "Bar",
marks = emptyList(),
style = Block.Content.Text.Style.P
)
)
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(
type = Block.Content.Smart.Type.PAGE
),
children = listOf(header.id, paragraph.id, paragraph2.id)
)
val document = listOf(page, header, title, paragraph, paragraph2)
stubInterceptEvents()
stubInterceptThreadStatus()
stubUpdateText()
stubOpenDocument(document, defaultDetails)
launchFragment(args)
with(R.id.recycler.rVMatcher()) {
onItemView(2, R.id.textContent).perform(ViewActions.click())
onItemView(2, R.id.textContent).perform(ViewActions.pressKey(KeyEvent.KEYCODE_SLASH))
Thread.sleep(300)
R.id.slashWidget.matchView().checkIsDisplayed()
onItemView(1, R.id.textContent).perform(ViewActions.click())
Thread.sleep(300)
}
R.id.slashWidget.matchView().checkIsNotDisplayed()
advance(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
}
// STUBBING & SETUP
private fun launchFragment(args: Bundle): FragmentScenario<TestPageFragment> {
return launchFragmentInContainer(
fragmentArgs = args,
themeResId = R.style.AppTheme
)
}
/**
* Moves coroutines clock time.
*/
private fun advance(millis: Long) {
coroutineTestRule.advanceTime(millis)
}
}

View file

@ -0,0 +1,603 @@
package com.anytypeio.anytype.features.editor
import android.os.Bundle
import android.view.KeyEvent
import androidx.core.os.bundleOf
import androidx.fragment.app.testing.FragmentScenario
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.Relation
import com.anytypeio.anytype.core_ui.features.page.slash.holders.MainMenuHolder
import com.anytypeio.anytype.emojifier.data.DefaultDocumentEmojiIconProvider
import com.anytypeio.anytype.features.editor.base.EditorTestSetup
import com.anytypeio.anytype.features.editor.base.TestPageFragment
import com.anytypeio.anytype.mocking.MockDataFactory
import com.anytypeio.anytype.presentation.page.PageViewModel
import com.anytypeio.anytype.ui.page.PageFragment
import com.anytypeio.anytype.utils.*
import com.bartoszlipinski.disableanimationsrule.DisableAnimationsRule
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@LargeTest
class SlashWidgetTesting : EditorTestSetup() {
@get:Rule
val animationsRule = DisableAnimationsRule()
@get:Rule
val coroutineTestRule = CoroutinesTestRule()
private val args = bundleOf(PageFragment.ID_KEY to root)
private val defaultDetails = Block.Details(
mapOf(
root to Block.Fields(
mapOf(
"iconEmoji" to DefaultDocumentEmojiIconProvider.DOCUMENT_SET.random()
)
)
)
)
private val title = Block(
id = MockDataFactory.randomUuid(),
content = Block.Content.Text(
style = Block.Content.Text.Style.TITLE,
text = "SlashTextWatcherTesting",
marks = emptyList()
),
children = emptyList(),
fields = Block.Fields.empty()
)
private val header = Block(
id = MockDataFactory.randomUuid(),
content = Block.Content.Layout(
type = Block.Content.Layout.Type.HEADER
),
fields = Block.Fields.empty(),
children = listOf(title.id)
)
@Before
override fun setup() {
super.setup()
}
/**
* Slash widget, all cases (SH - some sub header, STYLE - all style + markup items)
* Tests:
* 1. empty block | show MAIN items, BACK invisible | +
* 2. not empty block | show MAIN items, BACK invisible | +
* 3. show MAIN items | click STYLE | show SH + STYLE items, BACK visible | +
* 4. show MAIN items | click STYLE | show SH + STYLE items | BACK clicked | show MAIN items | +
* 5. show MAIN items | click MEDIA | show SH + MEDIA items, BACK visible | +
* 6. show MAIN items | click MEDIA | BACK clicked | show MAIN items | +
* 7. show MAIN items | click RELATIONS | show SH + RELATIONS, BACK visible | +
* 8. show MAIN items | click OBJECTS | show SH + OBJECT TYPES, BACK visible | -
* 9. show MAIN items | click MEDIA | click FILE | no focus, slash widget is invisible, add file block | -
*/
//region {Test 1}
@Test
fun testShouldShowMainItems() {
val paragraph = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields.empty(),
children = emptyList(),
content = Block.Content.Text(
text = "",
marks = emptyList(),
style = Block.Content.Text.Style.P
)
)
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(
type = Block.Content.Smart.Type.PAGE
),
children = listOf(header.id, paragraph.id)
)
val document = listOf(page, header, title, paragraph)
stubInterceptEvents()
stubInterceptThreadStatus()
stubUpdateText()
stubOpenDocument(document, defaultDetails)
launchFragment(args)
with(R.id.recycler.rVMatcher()) {
onItemView(1, R.id.textContent).perform(ViewActions.click())
onItemView(1, R.id.textContent)
.perform(ViewActions.pressKey(KeyEvent.KEYCODE_SLASH))
}
//TESTING
with(R.id.rvSlash.rVMatcher()) {
onItemView(0, R.id.textMain).checkHasText(R.string.slash_widget_main_style)
onItemView(1, R.id.textMain).checkHasText(R.string.slash_widget_main_media)
onItemView(2, R.id.textMain).checkHasText(R.string.slash_widget_main_objects)
checkIsRecyclerSize(9)
}
advance(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
}
//endregion
//region {Test 2}
@Test
fun testShouldAlsoShowMainItems() {
val paragraph = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields.empty(),
children = emptyList(),
content = Block.Content.Text(
text = "FooBar",
marks = emptyList(),
style = Block.Content.Text.Style.P
)
)
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(
type = Block.Content.Smart.Type.PAGE
),
children = listOf(header.id, paragraph.id)
)
val document = listOf(page, header, title, paragraph)
stubInterceptEvents()
stubInterceptThreadStatus()
stubUpdateText()
stubOpenDocument(document, defaultDetails)
launchFragment(args)
with(R.id.recycler.rVMatcher()) {
onItemView(1, R.id.textContent).perform(ViewActions.click())
onItemView(1, R.id.textContent)
.perform(ViewActions.pressKey(KeyEvent.KEYCODE_SLASH))
}
//TESTING
with(R.id.rvSlash.rVMatcher()) {
onItemView(0, R.id.textMain).checkHasText(R.string.slash_widget_main_style)
onItemView(1, R.id.textMain).checkHasText(R.string.slash_widget_main_media)
onItemView(2, R.id.textMain).checkHasText(R.string.slash_widget_main_objects)
checkIsRecyclerSize(9)
}
advance(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
}
//endregion
//region {Test 3}
@Test
fun testShouldShowStyleItems() {
val paragraph = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields.empty(),
children = emptyList(),
content = Block.Content.Text(
text = "FooBar",
marks = emptyList(),
style = Block.Content.Text.Style.P
)
)
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(
type = Block.Content.Smart.Type.PAGE
),
children = listOf(header.id, paragraph.id)
)
val document = listOf(page, header, title, paragraph)
stubInterceptEvents()
stubInterceptThreadStatus()
stubUpdateText()
stubOpenDocument(document, defaultDetails)
launchFragment(args)
with(R.id.recycler.rVMatcher()) {
onItemView(1, R.id.textContent).perform(ViewActions.click())
onItemView(1, R.id.textContent)
.perform(ViewActions.pressKey(KeyEvent.KEYCODE_SLASH))
}
//TESTING
with(R.id.rvSlash.rVMatcher()) {
onItemView(0, R.id.textMain).performClick()
onItemView(0, R.id.subheader).checkHasText(R.string.slash_widget_main_style)
onItemView(1, R.id.tvTitle).checkHasText(R.string.slash_widget_style_text)
onItemView(2, R.id.tvTitle).checkHasText(R.string.slash_widget_style_title)
onItemView(3, R.id.tvTitle).checkHasText(R.string.slash_widget_style_heading)
checkIsRecyclerSize(15)
}
onView(withId(R.id.flBack)).checkIsDisplayed()
advance(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
}
//endregion
//region {Test 4}
@Test
fun testShouldNavigateFromStyleToMain() {
val paragraph = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields.empty(),
children = emptyList(),
content = Block.Content.Text(
text = "FooBar",
marks = emptyList(),
style = Block.Content.Text.Style.P
)
)
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(
type = Block.Content.Smart.Type.PAGE
),
children = listOf(header.id, paragraph.id)
)
val document = listOf(page, header, title, paragraph)
stubInterceptEvents()
stubInterceptThreadStatus()
stubUpdateText()
stubOpenDocument(document, defaultDetails)
launchFragment(args)
with(R.id.recycler.rVMatcher()) {
onItemView(1, R.id.textContent).perform(ViewActions.click())
onItemView(1, R.id.textContent)
.perform(ViewActions.pressKey(KeyEvent.KEYCODE_SLASH))
}
with(R.id.rvSlash.rVMatcher()) {
onItemView(0, R.id.textMain).performClick()
onItemView(0, R.id.flBack).performClick()
}
//TESTING
with(R.id.rvSlash.rVMatcher()) {
onItemView(0, R.id.textMain).checkHasText(R.string.slash_widget_main_style)
onItemView(1, R.id.textMain).checkHasText(R.string.slash_widget_main_media)
onItemView(2, R.id.textMain).checkHasText(R.string.slash_widget_main_objects)
checkIsRecyclerSize(9)
}
advance(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
}
//endregion
//region {Test 5}
@Test
fun testShouldShowMediaItems() {
val paragraph = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields.empty(),
children = emptyList(),
content = Block.Content.Text(
text = "FooBar",
marks = emptyList(),
style = Block.Content.Text.Style.P
)
)
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(
type = Block.Content.Smart.Type.PAGE
),
children = listOf(header.id, paragraph.id)
)
val document = listOf(page, header, title, paragraph)
stubInterceptEvents()
stubInterceptThreadStatus()
stubUpdateText()
stubOpenDocument(document, defaultDetails)
launchFragment(args)
with(R.id.recycler.rVMatcher()) {
onItemView(1, R.id.textContent).perform(ViewActions.click())
onItemView(1, R.id.textContent)
.perform(ViewActions.pressKey(KeyEvent.KEYCODE_SLASH))
}
//TESTING
with(R.id.rvSlash.rVMatcher()) {
onItemView(1, R.id.textMain).performClick()
onItemView(0, R.id.subheader).checkHasText(R.string.slash_widget_main_media)
onItemView(1, R.id.tvTitle).checkHasText(R.string.slash_widget_media_file)
onItemView(2, R.id.tvTitle).checkHasText(R.string.slash_widget_media_picture)
onItemView(3, R.id.tvTitle).checkHasText(R.string.slash_widget_media_video)
checkIsRecyclerSize(6)
}
onView(withId(R.id.flBack)).checkIsDisplayed()
advance(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
}
//endregion
//region {Test 6}
@Test
fun testShouldNavigateFromMediaToMain() {
val paragraph = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields.empty(),
children = emptyList(),
content = Block.Content.Text(
text = "FooBar",
marks = emptyList(),
style = Block.Content.Text.Style.P
)
)
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(
type = Block.Content.Smart.Type.PAGE
),
children = listOf(header.id, paragraph.id)
)
val document = listOf(page, header, title, paragraph)
stubInterceptEvents()
stubInterceptThreadStatus()
stubUpdateText()
stubOpenDocument(document, defaultDetails)
launchFragment(args)
with(R.id.recycler.rVMatcher()) {
onItemView(1, R.id.textContent).perform(ViewActions.click())
onItemView(1, R.id.textContent)
.perform(ViewActions.pressKey(KeyEvent.KEYCODE_SLASH))
}
with(R.id.rvSlash.rVMatcher()) {
onItemView(1, R.id.textMain).performClick()
onItemView(0, R.id.flBack).performClick()
}
//TESTING
with(R.id.rvSlash.rVMatcher()) {
onItemView(0, R.id.textMain).checkHasText(R.string.slash_widget_main_style)
onItemView(1, R.id.textMain).checkHasText(R.string.slash_widget_main_media)
onItemView(2, R.id.textMain).checkHasText(R.string.slash_widget_main_objects)
checkIsRecyclerSize(9)
}
advance(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
}
//endregion
//region {Test 7}
@Test
fun testShouldShowRelations() {
val paragraph = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields.empty(),
children = emptyList(),
content = Block.Content.Text(
text = "FooBar",
marks = emptyList(),
style = Block.Content.Text.Style.P
)
)
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(
type = Block.Content.Smart.Type.PAGE
),
children = listOf(header.id, paragraph.id)
)
val document = listOf(page, header, title, paragraph)
val relation1 = Relation(
key = MockDataFactory.randomString(),
name = "Name",
format = Relation.Format.SHORT_TEXT,
source = Relation.Source.DETAILS
)
val relation2 = Relation(
key = MockDataFactory.randomString(),
name = "Number",
format = Relation.Format.NUMBER,
source = Relation.Source.DETAILS
)
val relation1Value = "Earth"
val relation2Value = 1619609201.0
val relations = listOf(relation1, relation2)
val details = Block.Details(
mapOf(
root to Block.Fields(
mapOf(
"iconEmoji" to DefaultDocumentEmojiIconProvider.DOCUMENT_SET.random(),
relation1.key to relation1Value,
relation2.key to relation2Value
)
)
)
)
stubInterceptEvents()
stubInterceptThreadStatus()
stubUpdateText()
stubOpenDocument(document, details, relations)
launchFragment(args)
with(R.id.recycler.rVMatcher()) {
onItemView(1, R.id.textContent).perform(ViewActions.click())
onItemView(1, R.id.textContent)
.perform(ViewActions.pressKey(KeyEvent.KEYCODE_SLASH))
}
//TESTING
onView(withId(R.id.rvSlash)).perform(RecyclerViewActions.scrollToPosition<MainMenuHolder>(3))
with(R.id.rvSlash.rVMatcher()) {
onItemView(3, R.id.textMain).performClick()
onItemView(0, R.id.tvSectionName).checkHasText(R.string.slash_widget_main_relations)
onItemView(1, R.id.tvRelationTitle).checkHasText(relation1.name)
onItemView(1, R.id.tvRelationValue).checkHasText(relation1Value)
onItemView(2, R.id.tvRelationTitle).checkHasText(relation2.name)
onItemView(2, R.id.tvRelationValue).checkHasText(relation2Value.toString())
checkIsRecyclerSize(3)
}
advance(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
}
//endregion
//region {Test 8}
@Test
fun testShouldShowObjectTypes() {
val paragraph = Block(
id = MockDataFactory.randomUuid(),
fields = Block.Fields.empty(),
children = emptyList(),
content = Block.Content.Text(
text = "FooBar",
marks = emptyList(),
style = Block.Content.Text.Style.P
)
)
val page = Block(
id = root,
fields = Block.Fields(emptyMap()),
content = Block.Content.Smart(
type = Block.Content.Smart.Type.PAGE
),
children = listOf(header.id, paragraph.id)
)
val document = listOf(page, header, title, paragraph)
val objectTypes = listOf(
ObjectType(
url = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
emoji = MockDataFactory.randomString(),
layout = ObjectType.Layout.PAGE,
relations = emptyList(),
description = MockDataFactory.randomString(),
isHidden = false
),
ObjectType(
url = MockDataFactory.randomUuid(),
name = MockDataFactory.randomString(),
emoji = MockDataFactory.randomString(),
layout = ObjectType.Layout.PAGE,
relations = emptyList(),
description = MockDataFactory.randomString(),
isHidden = false
)
)
stubInterceptEvents()
stubInterceptThreadStatus()
stubUpdateText()
stubOpenDocument(document, defaultDetails)
stubGetObjectTypes(objectTypes)
launchFragment(args)
with(R.id.recycler.rVMatcher()) {
onItemView(1, R.id.textContent).perform(ViewActions.click())
onItemView(1, R.id.textContent)
.perform(ViewActions.pressKey(KeyEvent.KEYCODE_SLASH))
}
//TESTING
onView(withId(R.id.rvSlash)).perform(RecyclerViewActions.scrollToPosition<MainMenuHolder>(2))
with(R.id.rvSlash.rVMatcher()) {
onItemView(2, R.id.textMain).performClick()
onItemView(0, R.id.subheader).checkHasText(R.string.slash_widget_main_objects_subheader)
onItemView(0, R.id.flBack).checkIsDisplayed()
}
advance(PageViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
}
//endregion
// STUBBING & SETUP
private fun launchFragment(args: Bundle): FragmentScenario<TestPageFragment> {
return launchFragmentInContainer(
fragmentArgs = args,
themeResId = R.style.AppTheme
)
}
/**
* Moves coroutines clock time.
*/
private fun advance(millis: Long) {
coroutineTestRule.advanceTime(millis)
}
}

View file

@ -12,6 +12,7 @@ import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.domain.base.Result
import com.anytypeio.anytype.domain.block.UpdateDivider
import com.anytypeio.anytype.domain.block.interactor.*
import com.anytypeio.anytype.domain.block.interactor.sets.GetObjectTypes
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.clipboard.Clipboard
import com.anytypeio.anytype.domain.clipboard.Copy
@ -73,7 +74,7 @@ open class EditorTestSetup {
lateinit var move: Move
lateinit var setRelationKey: SetRelationKey
lateinit var updateDetail: UpdateDetail
lateinit var getObjectTypes: GetObjectTypes
@Mock
lateinit var openPage: OpenPage
@ -210,6 +211,7 @@ open class EditorTestSetup {
removeDocCover = RemoveDocCover(repo)
turnIntoStyle = TurnIntoStyle(repo)
updateDetail = UpdateDetail(repo)
getObjectTypes = GetObjectTypes(repo)
TestPageFragment.testViewModelFactory = PageViewModelFactory(
openPage = openPage,
@ -276,7 +278,8 @@ open class EditorTestSetup {
setDocCoverImage = setDocCoverImage,
removeDocCover = removeDocCover,
detailModificationManager = InternalDetailModificationManager(stores.details),
updateDetail = updateDetail
updateDetail = updateDetail,
getObjectTypes = getObjectTypes
)
}
@ -366,6 +369,12 @@ open class EditorTestSetup {
}
}
fun stubGetObjectTypes(objectTypes: List<ObjectType>) {
repo.stub {
onBlocking { getTemplates() } doReturn objectTypes
}
}
fun stubUpdateTextStyle(
events: List<Event> = emptyList()
) {

View file

@ -2,7 +2,6 @@ package com.anytypeio.anytype.di.feature
import com.anytypeio.anytype.core_utils.di.scope.PerModal
import com.anytypeio.anytype.domain.block.interactor.sets.GetObjectTypes
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.presentation.page.picker.DocumentAddBlockViewModelFactory
import com.anytypeio.anytype.ui.page.modals.AddBlockFragment
import dagger.Module
@ -25,13 +24,6 @@ interface DocumentAddNewBlockSubComponent{
@Module
object DocumentAddNewBlockModule {
@JvmStatic
@Provides
@PerModal
fun provideGetObjectTypesUseCase(
repo: BlockRepository
): GetObjectTypes = GetObjectTypes(repo = repo)
@JvmStatic
@Provides
@PerModal

View file

@ -10,6 +10,7 @@ import com.anytypeio.anytype.core_utils.tools.Counter
import com.anytypeio.anytype.domain.`object`.UpdateDetail
import com.anytypeio.anytype.domain.block.UpdateDivider
import com.anytypeio.anytype.domain.block.interactor.*
import com.anytypeio.anytype.domain.block.interactor.sets.GetObjectTypes
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.clipboard.Clipboard
import com.anytypeio.anytype.domain.clipboard.Copy
@ -129,7 +130,8 @@ object EditorSessionModule {
analytics: Analytics,
dispatcher: Dispatcher<Payload>,
detailModificationManager: DetailModificationManager,
updateDetail: UpdateDetail
updateDetail: UpdateDetail,
getObjectTypes: GetObjectTypes
): PageViewModelFactory = PageViewModelFactory(
openPage = openPage,
closePage = closePage,
@ -152,7 +154,8 @@ object EditorSessionModule {
analytics = analytics,
dispatcher = dispatcher,
detailModificationManager = detailModificationManager,
updateDetail = updateDetail
updateDetail = updateDetail,
getObjectTypes = getObjectTypes
)
@JvmStatic
@ -682,4 +685,11 @@ object EditorUseCaseModule {
fun provideUpdateDetailUseCase(
repository: BlockRepository
) : UpdateDetail = UpdateDetail(repository)
@JvmStatic
@Provides
@PerScreen
fun provideGetObjectTypesUseCase(
repository: BlockRepository
) : GetObjectTypes = GetObjectTypes(repository)
}

View file

@ -528,11 +528,17 @@ open class PageFragment :
)
lifecycleScope.launch {
slashWidget.events.collect { item ->
slashWidget.clickEvents.collect { item ->
vm.onSlashItemClicked(item)
}
}
lifecycleScope.launch {
slashWidget.backEvent.collect {
vm.onSlashBackClicked()
}
}
lifecycleScope.launch {
styleToolbar.events.collect { event ->
vm.onStylingToolbarEvent(event)
@ -1102,8 +1108,8 @@ open class PageFragment :
if (!slashWidget.isVisible) {
showSlashWidget(this)
}
filter?.let {
slashWidget.filter(it)
command?.let {
slashWidget.onCommand(it)
}
} else {
slashWidget.gone()

View file

@ -162,7 +162,6 @@ open class RelationListFragment : BaseBottomSheetFragment(),
vm.onRelationTextValueChanged(
ctx = ctx,
value = text,
objectId = objectId,
relationId = relationId
)
}
@ -171,7 +170,6 @@ open class RelationListFragment : BaseBottomSheetFragment(),
vm.onRelationTextValueChanged(
ctx = ctx,
value = number,
objectId = objectId,
relationId = relationId
)
}
@ -184,7 +182,6 @@ open class RelationListFragment : BaseBottomSheetFragment(),
) {
vm.onRelationTextValueChanged(
ctx = ctx,
objectId = objectId,
relationId = relationId,
value = timeInSeconds
)

View file

@ -3,6 +3,7 @@ package com.anytypeio.anytype.core_ui.features.editor.holders.text
import android.text.Editable
import android.view.View
import androidx.core.view.updatePadding
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.features.page.SupportNesting
import com.anytypeio.anytype.core_ui.features.page.marks
@ -56,7 +57,7 @@ class Paragraph(
onBackPressedCallback = onBackPressedCallback
).also {
setupMentionWatcher(onMentionEvent)
//setupSlashWatcher(onSlashEvent)
setupSlashWatcher(onSlashEvent, item.getViewType())
}
override fun getMentionImageSizeAndPadding(): Pair<Int, Int> = with(itemView) {

View file

@ -3,6 +3,7 @@ package com.anytypeio.anytype.core_ui.features.page
import android.text.Editable
import android.text.Spannable
import android.widget.TextView
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_ui.common.*
import com.anytypeio.anytype.core_ui.extensions.applyMovementMethod
import com.anytypeio.anytype.core_ui.extensions.cursorYBottomCoordinate
@ -216,7 +217,8 @@ interface TextBlockHolder : TextHolder {
}
fun setupSlashWatcher(
onSlashEvent: (SlashEvent) -> Unit
onSlashEvent: (SlashEvent) -> Unit,
viewType: Int
) {
content.addTextChangedListener(
SlashTextWatcher { state ->
@ -228,7 +230,12 @@ interface TextBlockHolder : TextHolder {
)
)
SlashTextWatcherState.Stop -> onSlashEvent(SlashEvent.Stop)
is SlashTextWatcherState.Filter -> onSlashEvent(SlashEvent.Filter(state.text))
is SlashTextWatcherState.Filter -> onSlashEvent(
SlashEvent.Filter(
filter = state.text,
viewType = viewType
)
)
}
}
)

View file

@ -0,0 +1,45 @@
package com.anytypeio.anytype.core_ui.features.page.slash
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.features.page.slash.holders.MainMenuHolder
import com.anytypeio.anytype.presentation.page.editor.slash.SlashItem
class SlashMainAdapter(
private var items: List<SlashItem>,
private val clicks: (SlashItem) -> Unit
) : RecyclerView.Adapter<MainMenuHolder>() {
fun update(items: List<SlashItem>) {
this.items = items
notifyDataSetChanged()
}
fun clear() {
val size = items.size
if (size > 0) {
items = listOf()
notifyItemRangeRemoved(0, size)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainMenuHolder {
val inflater = LayoutInflater.from(parent.context)
return MainMenuHolder(
view = inflater.inflate(R.layout.item_slash_widget_main, parent, false)
).apply {
itemView.setOnClickListener {
clicks(items[bindingAdapterPosition])
}
}
}
override fun onBindViewHolder(holder: MainMenuHolder, position: Int) {
val item = items[position] as SlashItem.Main
holder.bind(item)
}
override fun getItemCount(): Int = items.size
}

View file

@ -0,0 +1,76 @@
package com.anytypeio.anytype.core_ui.features.page.slash
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.features.page.slash.holders.MediaMenuHolder
import com.anytypeio.anytype.core_ui.features.page.slash.holders.SubheaderMenuHolder
import com.anytypeio.anytype.presentation.page.editor.slash.SlashItem
import kotlinx.android.synthetic.main.item_slash_widget_subheader.view.*
class SlashMediaAdapter(
private var items: List<SlashItem>,
private val clicks: (SlashItem) -> Unit,
private val clickBack: () -> Unit
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
fun update(items: List<SlashItem>) {
this.items = items
notifyDataSetChanged()
}
fun clear() {
val size = items.size
if (size > 0) {
items = listOf()
notifyItemRangeRemoved(0, size)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
R.layout.item_slash_widget_style -> {
MediaMenuHolder(
view = inflater.inflate(viewType, parent, false)
).apply {
itemView.setOnClickListener {
clicks(items[bindingAdapterPosition])
}
}
}
R.layout.item_slash_widget_subheader -> {
SubheaderMenuHolder(
view = inflater.inflate(viewType, parent, false)
).apply {
itemView.flBack.setOnClickListener {
clickBack.invoke()
}
}
}
else -> throw IllegalArgumentException("Wrong viewtype:$viewType")
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is MediaMenuHolder -> {
val item = items[position] as SlashItem.Media
holder.bind(item)
}
is SubheaderMenuHolder -> {
val item = items[position] as SlashItem.Subheader
holder.bind(item)
}
}
}
override fun getItemViewType(position: Int): Int = when (val item = items[position]) {
is SlashItem.Media -> R.layout.item_slash_widget_style
is SlashItem.Subheader -> R.layout.item_slash_widget_subheader
else -> throw IllegalArgumentException("Wrong item type:$item")
}
override fun getItemCount(): Int = items.size
}

View file

@ -0,0 +1,75 @@
package com.anytypeio.anytype.core_ui.features.page.slash
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.features.page.slash.holders.*
import com.anytypeio.anytype.presentation.page.editor.slash.SlashItem
import kotlinx.android.synthetic.main.item_slash_widget_subheader.view.*
class SlashObjectTypesAdapter(
private var items: List<SlashItem>,
private val clicks: (SlashItem) -> Unit,
private val clickBack: () -> Unit
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
fun update(items: List<SlashItem>) {
this.items = items
notifyDataSetChanged()
}
fun clear() {
val size = items.size
if (size > 0) {
items = listOf()
notifyItemRangeRemoved(0, size)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
R.layout.item_slash_widget_object_type -> {
ObjectTypeMenuHolder(
view = inflater.inflate(viewType, parent, false)
).apply {
itemView.setOnClickListener {
clicks(items[bindingAdapterPosition])
}
}
}
R.layout.item_slash_widget_subheader -> {
SubheaderMenuHolder(
view = inflater.inflate(viewType, parent, false)
).apply {
itemView.flBack.setOnClickListener {
clickBack.invoke()
}
}
}
else -> throw IllegalArgumentException("Wrong viewtype:$viewType")
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when(holder) {
is ObjectTypeMenuHolder -> {
val item = items[position] as SlashItem.ObjectType
holder.bind(item)
}
is SubheaderMenuHolder -> {
val item = items[position] as SlashItem.Subheader
holder.bind(item)
}
}
}
override fun getItemCount(): Int = items.size
override fun getItemViewType(position: Int): Int = when (items[position]) {
is SlashItem.ObjectType -> R.layout.item_slash_widget_object_type
is SlashItem.Subheader -> R.layout.item_slash_widget_subheader
else -> throw IllegalArgumentException("Wrong item type:${items[position]} for SlashObjectTypeAdapter")
}
}

View file

@ -0,0 +1,193 @@
package com.anytypeio.anytype.core_ui.features.page.slash
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.features.editor.holders.relations.RelationViewHolder
import com.anytypeio.anytype.core_ui.features.relations.DocumentRelationAdapter
import com.anytypeio.anytype.core_utils.diff.DefaultDiffUtil
import com.anytypeio.anytype.core_utils.ext.gone
import com.anytypeio.anytype.presentation.relations.DocumentRelationView
import com.anytypeio.anytype.presentation.relations.RelationListViewModel
import timber.log.Timber
class SlashRelationsAdapter(
private var items: List<RelationListViewModel.Model>,
private val onRelationClicked: (RelationListViewModel.Model.Item) -> Unit,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
R.layout.item_relation_list_relation_default -> {
RelationViewHolder.Default(view = inflater.inflate(viewType, parent, false)).apply {
itemView.setOnClickListener {
val view = items[bindingAdapterPosition]
check(view is RelationListViewModel.Model.Item)
onRelationClicked(view)
}
itemView.findViewById<View>(R.id.featuredRelationCheckbox).apply {
gone()
}
}
}
R.layout.item_relation_list_relation_checkbox -> {
RelationViewHolder.Checkbox(view = inflater.inflate(viewType, parent, false))
.apply {
itemView.setOnClickListener {
val view = items[bindingAdapterPosition]
check(view is RelationListViewModel.Model.Item)
onRelationClicked(view)
}
itemView.findViewById<View>(R.id.featuredRelationCheckbox).apply {
gone()
}
}
}
R.layout.item_relation_list_relation_object -> {
RelationViewHolder.Object(view = inflater.inflate(viewType, parent, false)).apply {
itemView.setOnClickListener {
val view = items[bindingAdapterPosition]
check(view is RelationListViewModel.Model.Item)
onRelationClicked(view)
}
itemView.findViewById<View>(R.id.featuredRelationCheckbox).apply {
gone()
}
}
}
R.layout.item_relation_list_relation_status -> {
RelationViewHolder.Status(view = inflater.inflate(viewType, parent, false)).apply {
itemView.setOnClickListener {
val view = items[bindingAdapterPosition]
check(view is RelationListViewModel.Model.Item)
onRelationClicked(view)
}
itemView.findViewById<View>(R.id.featuredRelationCheckbox).apply {
gone()
}
}
}
R.layout.item_relation_list_relation_tag -> {
RelationViewHolder.Tags(view = inflater.inflate(viewType, parent, false)).apply {
itemView.setOnClickListener {
val view = items[bindingAdapterPosition]
check(view is RelationListViewModel.Model.Item)
onRelationClicked(view)
}
itemView.findViewById<View>(R.id.featuredRelationCheckbox).apply {
gone()
}
}
}
R.layout.item_relation_list_relation_file -> {
RelationViewHolder.File(view = inflater.inflate(viewType, parent, false)).apply {
itemView.setOnClickListener {
val view = items[bindingAdapterPosition]
check(view is RelationListViewModel.Model.Item)
onRelationClicked(view)
}
itemView.findViewById<View>(R.id.featuredRelationCheckbox).apply {
gone()
}
}
}
R.layout.item_relation_list_section -> {
DocumentRelationAdapter.SectionViewHolder(
view = inflater.inflate(
viewType,
parent,
false
)
)
}
else -> throw IllegalStateException("Unexpected view type: $viewType")
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item = items[position]
when (holder) {
is RelationViewHolder.Status -> {
check(item is RelationListViewModel.Model.Item)
val view = item.view
check(view is DocumentRelationView.Status)
holder.bind(view)
}
is RelationViewHolder.Checkbox -> {
check(item is RelationListViewModel.Model.Item)
val view = item.view
check(view is DocumentRelationView.Checkbox)
holder.bind(view)
}
is RelationViewHolder.Tags -> {
check(item is RelationListViewModel.Model.Item)
val view = item.view
check(view is DocumentRelationView.Tags)
holder.bind(view)
}
is RelationViewHolder.Object -> {
check(item is RelationListViewModel.Model.Item)
val view = item.view
check(view is DocumentRelationView.Object)
holder.bind(view)
}
is RelationViewHolder.File -> {
check(item is RelationListViewModel.Model.Item)
val view = item.view
check(view is DocumentRelationView.File)
holder.bind(view)
}
is RelationViewHolder.Default -> {
check(item is RelationListViewModel.Model.Item)
val view = item.view
check(view is DocumentRelationView.Default)
holder.bind(view)
}
is DocumentRelationAdapter.SectionViewHolder -> {
check(item is RelationListViewModel.Model.Section)
holder.bind(item)
}
else -> {
Timber.d("Skipping binding for: $holder")
}
}
}
override fun getItemCount(): Int = items.size
override fun getItemViewType(position: Int): Int = when (val item = items[position]) {
is RelationListViewModel.Model.Item -> {
when (item.view) {
is DocumentRelationView.Checkbox -> R.layout.item_relation_list_relation_checkbox
is DocumentRelationView.Object -> R.layout.item_relation_list_relation_object
is DocumentRelationView.Status -> R.layout.item_relation_list_relation_status
is DocumentRelationView.Tags -> R.layout.item_relation_list_relation_tag
is DocumentRelationView.File -> R.layout.item_relation_list_relation_file
else -> R.layout.item_relation_list_relation_default
}
}
RelationListViewModel.Model.Section.Featured -> R.layout.item_relation_list_section
RelationListViewModel.Model.Section.Other -> R.layout.item_relation_list_section
RelationListViewModel.Model.Section.NoSection -> R.layout.item_relation_list_section
}
fun update(update: List<RelationListViewModel.Model>) {
Timber.d("Updating adapter: $update")
val differ = DefaultDiffUtil(old = items, new = update)
val result = DiffUtil.calculateDiff(differ, false)
items = update
result.dispatchUpdatesTo(this)
}
fun clear() {
val size = items.size
if (size > 0) {
items = listOf()
notifyItemRangeRemoved(0, size)
}
}
}

View file

@ -4,13 +4,15 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.features.page.slash.holders.MainMenuHolder
import com.anytypeio.anytype.core_ui.features.page.slash.holders.StyleMenuHolder
import com.anytypeio.anytype.core_ui.features.page.slash.holders.SubheaderMenuHolder
import com.anytypeio.anytype.presentation.page.editor.slash.SlashItem
import kotlinx.android.synthetic.main.item_slash_widget_subheader.view.*
class SlashWidgetAdapter(
class SlashStyleAdapter(
private var items: List<SlashItem>,
private val clicks: (SlashItem) -> Unit
private val clicks: (SlashItem) -> Unit,
private val clickBack: () -> Unit
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
fun update(items: List<SlashItem>) {
@ -18,18 +20,17 @@ class SlashWidgetAdapter(
notifyDataSetChanged()
}
fun clear() {
val size = items.size
if (size > 0) {
items = listOf()
notifyItemRangeRemoved(0, size)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
R.layout.item_slash_widget_main -> {
MainMenuHolder(
view = inflater.inflate(viewType, parent, false)
).apply {
itemView.setOnClickListener {
clicks(items[bindingAdapterPosition])
}
}
}
R.layout.item_slash_widget_style -> {
StyleMenuHolder(
view = inflater.inflate(viewType, parent, false)
@ -39,30 +40,36 @@ class SlashWidgetAdapter(
}
}
}
R.layout.item_slash_widget_subheader -> {
SubheaderMenuHolder(
view = inflater.inflate(viewType, parent, false)
).apply {
itemView.flBack.setOnClickListener {
clickBack.invoke()
}
}
}
else -> throw IllegalArgumentException("Wrong viewtype:$viewType")
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is MainMenuHolder -> {
val item = items[position] as SlashItem.Main
holder.bind(item)
}
is StyleMenuHolder -> {
val item = items[position] as SlashItem.Style
holder.bind(item)
}
is SubheaderMenuHolder -> {
val item = items[position] as SlashItem.Subheader
holder.bind(item)
}
}
}
override fun getItemViewType(position: Int): Int = when (items[position]) {
is SlashItem.Main -> R.layout.item_slash_widget_main
override fun getItemViewType(position: Int): Int = when (val item = items[position]) {
is SlashItem.Style -> R.layout.item_slash_widget_style
is SlashItem.Actions -> R.layout.item_slash_widget_actions
is SlashItem.Alignment -> R.layout.item_slash_widget_alignment
is SlashItem.Media -> R.layout.item_slash_widget_media
is SlashItem.Other -> R.layout.item_slash_widget_other
is SlashItem.Subheader -> R.layout.item_slash_widget_subheader
else -> throw IllegalArgumentException("Wrong item type:$item")
}
override fun getItemCount(): Int = items.size

View file

@ -4,11 +4,13 @@ import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.LinearLayoutManager
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.tools.SlashHelper
import com.anytypeio.anytype.presentation.page.editor.slash.SlashCommand
import com.anytypeio.anytype.presentation.page.editor.slash.SlashItem
import kotlinx.android.synthetic.main.widget_editor_plus.view.*
import kotlinx.android.synthetic.main.widget_editor_slash.view.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.consumeAsFlow
@ -18,29 +20,129 @@ class SlashWidget @JvmOverloads constructor(
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
private val channel = Channel<SlashItem>()
val events = channel.consumeAsFlow()
private val _clickEvents = Channel<SlashItem>()
val clickEvents = _clickEvents.consumeAsFlow()
private val slashAdapter by lazy {
SlashWidgetAdapter(items = listOf()) { channel.offer(it) }
private val _backEvent = Channel<Boolean>()
val backEvent = _backEvent.consumeAsFlow()
private val mainAdapter by lazy {
SlashMainAdapter(
items = listOf(),
clicks = { _clickEvents.offer(it) }
)
}
private val styleAdapter by lazy {
SlashStyleAdapter(
items = listOf(),
clicks = { _clickEvents.offer(it) },
clickBack = { _backEvent.offer(true) }
)
}
private val mediaAdapter by lazy {
SlashMediaAdapter(
items = listOf(),
clicks = { _clickEvents.offer(it) },
clickBack = { _backEvent.offer(true) }
)
}
private val objectTypesAdapter by lazy {
SlashObjectTypesAdapter(
items = listOf(),
clicks = { _clickEvents.offer(it) },
clickBack = { _backEvent.offer(true) }
)
}
private val relationsAdapter by lazy {
SlashRelationsAdapter(
items = listOf(),
onRelationClicked = {}
)
}
private val concatAdapter = ConcatAdapter(
mainAdapter,
styleAdapter,
mediaAdapter,
objectTypesAdapter,
relationsAdapter
)
init {
LayoutInflater.from(context).inflate(R.layout.widget_editor_plus, this)
LayoutInflater.from(context).inflate(R.layout.widget_editor_slash, this)
setup(context)
}
private fun setup(context: Context) {
with(recyclerView) {
with(rvSlash) {
val lm = LinearLayoutManager(context)
layoutManager = lm
adapter = slashAdapter
adapter = concatAdapter
}
}
fun filter(filter: String) {
val items = SlashHelper.getSlashItems(filter.removePrefix("/"))
slashAdapter.update(items)
fun onCommand(command: SlashCommand) {
when (command) {
is SlashCommand.ShowMainItems -> {
mainAdapter.update(command.items)
rvSlash.smoothScrollToPosition(0)
styleAdapter.clear()
mediaAdapter.clear()
objectTypesAdapter.clear()
relationsAdapter.clear()
}
is SlashCommand.ShowStyleItems -> {
styleAdapter.update(command.items)
rvSlash.smoothScrollToPosition(0)
mainAdapter.clear()
mediaAdapter.clear()
objectTypesAdapter.clear()
relationsAdapter.clear()
}
is SlashCommand.ShowMediaItems -> {
mediaAdapter.update(command.items)
rvSlash.smoothScrollToPosition(0)
mainAdapter.clear()
styleAdapter.clear()
objectTypesAdapter.clear()
relationsAdapter.clear()
}
is SlashCommand.ShowRelations -> {
relationsAdapter.update(command.relations)
rvSlash.smoothScrollToPosition(0)
mainAdapter.clear()
styleAdapter.clear()
mediaAdapter.clear()
objectTypesAdapter.clear()
}
is SlashCommand.ShowObjectTypes -> {
objectTypesAdapter.update(command.items)
rvSlash.smoothScrollToPosition(0)
mainAdapter.clear()
styleAdapter.clear()
mediaAdapter.clear()
relationsAdapter.clear()
}
SlashCommand.ShowOtherItems -> TODO()
is SlashCommand.FilterItems -> {
val filter = command.filter.removePrefix(SLASH_PREFIX)
val items = SlashHelper.filterSlashItems(
filter = filter,
viewType = command.viewType
)
styleAdapter.update(items)
}
}
}
fun getWidgetMinHeight() = with(context.resources) {
@ -51,5 +153,6 @@ class SlashWidget @JvmOverloads constructor(
companion object {
const val MIN_VISIBLE_ITEMS = 4
const val SLASH_PREFIX = "/"
}
}

View file

@ -2,13 +2,50 @@ package com.anytypeio.anytype.core_ui.features.page.slash.holders
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.presentation.page.editor.slash.SlashItem
import kotlinx.android.synthetic.main.item_slash_widget_main.view.*
class MainMenuHolder(view: View): RecyclerView.ViewHolder(view) {
class MainMenuHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind(item: SlashItem.Main) = with(itemView) {
textMain.setText(item.title)
iconMain.setImageResource(item.icon)
when (item) {
SlashItem.Main.Actions -> {
textMain.setText(R.string.slash_widget_main_actions)
iconMain.setImageResource(R.drawable.ic_slash_main_actions)
}
SlashItem.Main.Alignment -> {
textMain.setText(R.string.slash_widget_main_alignment)
iconMain.setImageResource(R.drawable.ic_slash_main_alignment)
}
SlashItem.Main.Background -> {
textMain.setText(R.string.slash_widget_main_background)
iconMain.setImageResource(R.drawable.ic_slash_main_rectangle)
}
SlashItem.Main.Color -> {
textMain.setText(R.string.slash_widget_main_color)
iconMain.setImageResource(R.drawable.ic_slash_main_color)
}
SlashItem.Main.Media -> {
textMain.setText(R.string.slash_widget_main_media)
iconMain.setImageResource(R.drawable.ic_slash_main_media)
}
SlashItem.Main.Objects -> {
textMain.setText(R.string.slash_widget_main_objects)
iconMain.setImageResource(R.drawable.ic_slash_main_objects)
}
SlashItem.Main.Other -> {
textMain.setText(R.string.slash_widget_main_other)
iconMain.setImageResource(R.drawable.ic_slash_main_other)
}
SlashItem.Main.Relations -> {
textMain.setText(R.string.slash_widget_main_relations)
iconMain.setImageResource(R.drawable.ic_slash_main_relations)
}
SlashItem.Main.Style -> {
textMain.setText(R.string.slash_widget_main_style)
iconMain.setImageResource(R.drawable.ic_slash_main_style)
}
}
}
}

View file

@ -0,0 +1,40 @@
package com.anytypeio.anytype.core_ui.features.page.slash.holders
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.presentation.page.editor.slash.SlashItem
import kotlinx.android.synthetic.main.item_slash_widget_style.view.*
class MediaMenuHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind(item: SlashItem.Media) = with(itemView) {
when (item) {
SlashItem.Media.Bookmark -> {
tvTitle.setText(R.string.slash_widget_media_bookmark)
tvSubtitle.setText(R.string.slash_widget_media_bookmark_subtitle)
ivIcon.setImageResource(R.drawable.ic_slash_media_bookmark)
}
SlashItem.Media.Code -> {
tvTitle.setText(R.string.slash_widget_media_code)
tvSubtitle.setText(R.string.slash_widget_media_code_subtitle)
ivIcon.setImageResource(R.drawable.ic_slash_media_code)
}
SlashItem.Media.File -> {
tvTitle.setText(R.string.slash_widget_media_file)
tvSubtitle.setText(R.string.slash_widget_media_file_subtitle)
ivIcon.setImageResource(R.drawable.ic_slash_media_file)
}
SlashItem.Media.Picture -> {
tvTitle.setText(R.string.slash_widget_media_picture)
tvSubtitle.setText(R.string.slash_widget_media_picture_subtitle)
ivIcon.setImageResource(R.drawable.ic_slash_media_picture)
}
SlashItem.Media.Video -> {
tvTitle.setText(R.string.slash_widget_media_video)
tvSubtitle.setText(R.string.slash_widget_media_video_subtitle)
ivIcon.setImageResource(R.drawable.ic_slash_media_video)
}
}
}
}

View file

@ -0,0 +1,19 @@
package com.anytypeio.anytype.core_ui.features.page.slash.holders
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import com.anytypeio.anytype.presentation.page.editor.slash.SlashItem
import kotlinx.android.synthetic.main.item_slash_widget_object_type.view.*
class ObjectTypeMenuHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind(item: SlashItem.ObjectType) = with(itemView) {
ivIcon.setIcon(
emoji = item.emoji,
image = null,
name = item.name
)
tvTitle.text = item.name
tvSubtitle.text = item.description
}
}

View file

@ -2,11 +2,96 @@ package com.anytypeio.anytype.core_ui.features.page.slash.holders
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_utils.ext.gone
import com.anytypeio.anytype.core_utils.ext.visible
import com.anytypeio.anytype.presentation.page.editor.slash.SlashItem
import kotlinx.android.synthetic.main.item_slash_widget_style.view.*
class StyleMenuHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind(item: SlashItem.Style) {
fun bind(item: SlashItem.Style) = with(itemView) {
when (item) {
is SlashItem.Style.Markup.Bold -> {
tvTitle.setText(R.string.slash_widget_style_bold)
ivIcon.setImageResource(R.drawable.ic_slash_style_bold)
tvSubtitle.gone()
}
is SlashItem.Style.Markup.Breakthrough -> {
tvTitle.setText(R.string.slash_widget_style_breakthrough)
ivIcon.setImageResource(R.drawable.ic_slash_style_breakthrough)
tvSubtitle.gone()
}
is SlashItem.Style.Markup.Code -> {
tvTitle.setText(R.string.slash_widget_style_code)
ivIcon.setImageResource(R.drawable.ic_slash_style_code)
tvSubtitle.gone()
}
is SlashItem.Style.Markup.Italic -> {
tvTitle.setText(R.string.slash_widget_style_italic)
ivIcon.setImageResource(R.drawable.ic_slash_style_italic)
tvSubtitle.gone()
}
is SlashItem.Style.Type.Bulleted -> {
tvTitle.setText(R.string.slash_widget_style_bulleted)
tvSubtitle.visible()
tvSubtitle.setText(R.string.slash_widget_style_bulleted_subtitle)
ivIcon.setImageResource(R.drawable.ic_slash_style_bulleted)
}
is SlashItem.Style.Type.Callout -> {
tvTitle.setText(R.string.slash_widget_style_callout)
tvSubtitle.visible()
tvSubtitle.setText(R.string.slash_widget_style_callout_subtitle)
ivIcon.setImageResource(R.drawable.ic_slash_style_callout)
}
is SlashItem.Style.Type.Checkbox -> {
tvTitle.setText(R.string.slash_widget_style_checkbox)
tvSubtitle.visible()
tvSubtitle.setText(R.string.slash_widget_style_checkbox_subtitle)
ivIcon.setImageResource(R.drawable.ic_slash_style_checkbox)
}
is SlashItem.Style.Type.Heading -> {
tvTitle.setText(R.string.slash_widget_style_heading)
tvSubtitle.visible()
tvSubtitle.setText(R.string.slash_widget_style_heading_subtitle)
ivIcon.setImageResource(R.drawable.ic_slash_style_heading)
}
is SlashItem.Style.Type.Highlighted -> {
tvTitle.setText(R.string.slash_widget_style_highlighted)
tvSubtitle.visible()
tvSubtitle.setText(R.string.slash_widget_style_highlighted_subtitle)
ivIcon.setImageResource(R.drawable.ic_slash_style_highlighted)
}
is SlashItem.Style.Type.Numbered -> {
tvTitle.setText(R.string.slash_widget_style_numbered)
tvSubtitle.visible()
tvSubtitle.setText(R.string.slash_widget_style_numbered_subtitle)
ivIcon.setImageResource(R.drawable.ic_slash_style_numbered)
}
is SlashItem.Style.Type.Subheading -> {
tvTitle.setText(R.string.slash_widget_style_subheading)
tvSubtitle.visible()
tvSubtitle.setText(R.string.slash_widget_style_subheading_subtitle)
ivIcon.setImageResource(R.drawable.ic_slash_style_subheading)
}
is SlashItem.Style.Type.Text -> {
tvTitle.setText(R.string.slash_widget_style_text)
tvSubtitle.visible()
tvSubtitle.setText(R.string.slash_widget_style_text_subtitle)
ivIcon.setImageResource(R.drawable.ic_slash_style_text)
}
is SlashItem.Style.Type.Title -> {
tvTitle.setText(R.string.slash_widget_style_title)
tvSubtitle.visible()
tvSubtitle.setText(R.string.slash_widget_style_title_subtitle)
ivIcon.setImageResource(R.drawable.ic_slash_style_title)
}
is SlashItem.Style.Type.Toggle -> {
tvTitle.setText(R.string.slash_widget_style_toggle)
tvSubtitle.visible()
tvSubtitle.setText(R.string.slash_widget_style_toggle_subtitle)
ivIcon.setImageResource(R.drawable.ic_slash_style_toggle)
}
}
}
}

View file

@ -0,0 +1,36 @@
package com.anytypeio.anytype.core_ui.features.page.slash.holders
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_utils.ext.invisible
import com.anytypeio.anytype.core_utils.ext.visible
import com.anytypeio.anytype.presentation.page.editor.slash.SlashItem
import kotlinx.android.synthetic.main.item_slash_widget_subheader.view.*
class SubheaderMenuHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind(item: SlashItem.Subheader) = with(itemView) {
val text = when (item) {
SlashItem.Subheader.Style -> {
flBack.invisible()
R.string.slash_widget_main_style
}
SlashItem.Subheader.StyleWithBack -> {
flBack.visible()
R.string.slash_widget_main_style
}
SlashItem.Subheader.Media -> {
flBack.invisible()
R.string.slash_widget_main_media
}
SlashItem.Subheader.MediaWithBack -> {
flBack.visible()
R.string.slash_widget_main_media
}
SlashItem.Subheader.ObjectType -> R.string.slash_widget_main_objects_subheader
}
subheader.setText(text)
}
}

View file

@ -177,8 +177,9 @@ class DocumentRelationAdapter(
else -> R.layout.item_relation_list_relation_default
}
}
is RelationListViewModel.Model.Section.Featured -> R.layout.item_relation_list_section
is RelationListViewModel.Model.Section.Other -> R.layout.item_relation_list_section
RelationListViewModel.Model.Section.Featured -> R.layout.item_relation_list_section
RelationListViewModel.Model.Section.Other -> R.layout.item_relation_list_section
RelationListViewModel.Model.Section.NoSection -> R.layout.item_relation_list_section
}
fun update(update: List<RelationListViewModel.Model>) {
@ -198,6 +199,9 @@ class DocumentRelationAdapter(
RelationListViewModel.Model.Section.Other -> {
itemView.tvSectionName.setText(R.string.other_relations)
}
RelationListViewModel.Model.Section.NoSection -> {
itemView.tvSectionName.setText(R.string.slash_widget_main_relations)
}
}
}
}

View file

@ -1,6 +1,5 @@
package com.anytypeio.anytype.core_ui.tools
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.tools.SlashTextWatcher.Companion.NO_SLASH_POSITION
import com.anytypeio.anytype.core_ui.tools.SlashTextWatcher.Companion.SLASH_CHAR
import com.anytypeio.anytype.presentation.page.editor.slash.SlashItem
@ -13,6 +12,27 @@ object SlashHelper {
fun isSlashDeleted(start: Int, slashPosition: Int): Boolean =
slashPosition != NO_SLASH_POSITION && start <= slashPosition
/**
* Replace char sequence range in [filter] with start index [replacementStart]
* by new [replacement] char sequence
* @property before See TextWatcher.onTextChanged property before
*/
fun getUpdatedFilter(
replacement: CharSequence,
replacementStart: Int,
before: Int,
filter: String
): String = try {
val range = replacementStart until replacementStart + before
if (range.first > filter.length) {
filter
} else {
filter.replaceRange(range = range, replacement = replacement)
}
} catch (e: Exception) {
filter
}
/**
* return subsequence from [startIndex] to end of sequence with limit [takeNumber]
*/
@ -22,238 +42,20 @@ object SlashHelper {
takeNumber: Int
): CharSequence = s.subSequence(startIndex = startIndex, endIndex = s.length).take(takeNumber)
fun getSlashItems(filter: String): List<SlashItem> {
if (filter.isEmpty()) {
return mainList
} else return listOf<SlashItem>()
fun filterSlashItems(
filter: String,
viewType: Int
): List<SlashItem> {
return emptyList()
// when (viewType) {
// HOLDER_PARAGRAPH -> {
// val styleItems = styleTypeList.filter {
// it.javaClass.simpleName.contains(filter, ignoreCase = true)
// }
//
// return styleSubheader + styleItems
// }
// else -> TODO()
// }
}
val mainList = listOf<SlashItem>(
SlashItem.Main.Style(
title = R.string.slash_widget_main_style,
icon = R.drawable.ic_slash_main_style
),
SlashItem.Main.Media(
title = R.string.slash_widget_main_media,
icon = R.drawable.ic_slash_main_media
),
SlashItem.Main.Objects(
title = R.string.slash_widget_main_objects,
icon = R.drawable.ic_slash_main_objects
),
SlashItem.Main.Relations(
title = R.string.slash_widget_main_relations,
icon = R.drawable.ic_slash_main_relations
),
SlashItem.Main.Other(
title = R.string.slash_widget_main_other,
icon = R.drawable.ic_slash_main_other
),
SlashItem.Main.Actions(
title = R.string.slash_widget_main_actions,
icon = R.drawable.ic_slash_main_actions
),
SlashItem.Main.Alignment(
title = R.string.slash_widget_main_alignment,
icon = R.drawable.ic_slash_main_alignment
),
SlashItem.Main.Color(
title = R.string.slash_widget_main_color,
icon = R.drawable.ic_slash_main_color
),
SlashItem.Main.Background(
title = R.string.slash_widget_main_background,
icon = R.drawable.ic_slash_main_rectangle
)
)
val styleList = listOf<SlashItem>(
SlashItem.Style.Type.Text(
title = R.string.slash_widget_style_text,
subtitle = R.string.slash_widget_style_text_subtitle,
icon = R.drawable.ic_slash_style_text,
category = SlashItem.CATEGORY_STYLE
),
SlashItem.Style.Type.Title(
title = R.string.slash_widget_style_title,
subtitle = R.string.slash_widget_style_title_subtitle,
icon = R.drawable.ic_slash_style_title,
category = SlashItem.CATEGORY_STYLE
),
SlashItem.Style.Type.Heading(
title = R.string.slash_widget_style_heading,
subtitle = R.string.slash_widget_style_heading_subtitle,
icon = R.drawable.ic_slash_style_heading,
category = SlashItem.CATEGORY_STYLE
),
SlashItem.Style.Type.Subheading(
title = R.string.slash_widget_style_subheading,
subtitle = R.string.slash_widget_style_subheading_subtitle,
icon = R.drawable.ic_slash_style_subheading,
category = SlashItem.CATEGORY_STYLE
),
SlashItem.Style.Type.Highlighted(
title = R.string.slash_widget_style_highlighted,
subtitle = R.string.slash_widget_style_highlighted_subtitle,
icon = R.drawable.ic_slash_style_highlighted,
category = SlashItem.CATEGORY_STYLE
),
SlashItem.Style.Type.Callout(
title = R.string.slash_widget_style_callout,
subtitle = R.string.slash_widget_style_callout_subtitle,
icon = R.drawable.ic_slash_style_callout,
category = SlashItem.CATEGORY_STYLE
),
SlashItem.Style.Type.Checkbox(
title = R.string.slash_widget_style_checkbox,
subtitle = R.string.slash_widget_style_checkbox_subtitle,
icon = R.drawable.ic_slash_style_checkbox,
category = SlashItem.CATEGORY_STYLE
),
SlashItem.Style.Type.Bulleted(
title = R.string.slash_widget_style_bulleted,
subtitle = R.string.slash_widget_style_bulleted_subtitle,
icon = R.drawable.ic_slash_style_bulleted,
category = SlashItem.CATEGORY_STYLE
),
SlashItem.Style.Type.Numbered(
title = R.string.slash_widget_style_numbered,
subtitle = R.string.slash_widget_style_numbered_subtitle,
icon = R.drawable.ic_slash_style_numbered,
category = SlashItem.CATEGORY_STYLE
),
SlashItem.Style.Type.Toggle(
title = R.string.slash_widget_style_toggle,
subtitle = R.string.slash_widget_style_toggle_subtitle,
icon = R.drawable.ic_slash_style_toggle,
category = SlashItem.CATEGORY_STYLE
),
SlashItem.Style.Markup.Bold(
title = R.string.slash_widget_style_bold,
icon = R.drawable.ic_slash_style_bold,
category = SlashItem.CATEGORY_STYLE
),
SlashItem.Style.Markup.Italic(
title = R.string.slash_widget_style_italic,
icon = R.drawable.ic_slash_style_italic,
category = SlashItem.CATEGORY_STYLE
),
SlashItem.Style.Markup.Breakthrough(
title = R.string.slash_widget_style_breakthrough,
icon = R.drawable.ic_slash_style_breakthrough,
category = SlashItem.CATEGORY_STYLE
),
SlashItem.Style.Markup.Code(
title = R.string.slash_widget_style_code,
icon = R.drawable.ic_slash_style_code,
category = SlashItem.CATEGORY_STYLE
),
SlashItem.Style.Markup.Link(
title = R.string.slash_widget_style_link,
icon = R.drawable.ic_slash_style_link,
category = SlashItem.CATEGORY_STYLE
)
)
val mediaList = listOf<SlashItem>(
SlashItem.Media.File(
title = R.string.slash_widget_media_file,
subtitle = R.string.slash_widget_media_file_subtitle,
icon = R.drawable.ic_slash_media_file,
category = SlashItem.CATEGORY_MEDIA
),
SlashItem.Media.Picture(
title = R.string.slash_widget_media_picture,
subtitle = R.string.slash_widget_media_picture_subtitle,
icon = R.drawable.ic_slash_media_picture,
category = SlashItem.CATEGORY_MEDIA
),
SlashItem.Media.Video(
title = R.string.slash_widget_media_video,
subtitle = R.string.slash_widget_media_video_subtitle,
icon = R.drawable.ic_slash_media_video,
category = SlashItem.CATEGORY_MEDIA
),
SlashItem.Media.Bookmark(
title = R.string.slash_widget_media_bookmark,
subtitle = R.string.slash_widget_media_bookmark_subtitle,
icon = R.drawable.ic_slash_media_bookmark,
category = SlashItem.CATEGORY_MEDIA
),
SlashItem.Media.Code(
title = R.string.slash_widget_media_code,
subtitle = R.string.slash_widget_media_code_subtitle,
icon = R.drawable.ic_slash_media_code,
category = SlashItem.CATEGORY_MEDIA
)
)
val otherList = listOf<SlashItem>(
SlashItem.Other.Line(
title = R.string.slash_widget_other_line,
icon = R.drawable.ic_slash_other_line,
category = SlashItem.CATEGORY_OTHER
),
SlashItem.Other.Dots(
title = R.string.slash_widget_other_dots,
icon = R.drawable.ic_slash_other_dots,
category = SlashItem.CATEGORY_OTHER
)
)
val actionsList = listOf<SlashItem>(
SlashItem.Actions.Delete(
title = R.string.slash_widget_actions_delete,
icon = R.drawable.ic_slash_actions_delete,
category = SlashItem.CATEGORY_ACTIONS
),
SlashItem.Actions.Duplicate(
title = R.string.slash_widget_actions_duplicate,
icon = R.drawable.ic_slash_actions_duplicate,
category = SlashItem.CATEGORY_ACTIONS
),
SlashItem.Actions.Copy(
title = R.string.slash_widget_actions_copy,
icon = R.drawable.ic_slash_actions_copy,
category = SlashItem.CATEGORY_ACTIONS
),
SlashItem.Actions.Paste(
title = R.string.slash_widget_actions_paste,
icon = R.drawable.ic_slash_actions_paste,
category = SlashItem.CATEGORY_ACTIONS
),
SlashItem.Actions.Move(
title = R.string.slash_widget_actions_move,
icon = R.drawable.ic_slash_actions_move,
category = SlashItem.CATEGORY_ACTIONS
),
SlashItem.Actions.MoveTo(
title = R.string.slash_widget_actions_moveto,
icon = R.drawable.ic_slash_actions_move_to,
category = SlashItem.CATEGORY_ACTIONS
),
SlashItem.Actions.CleanStyle(
title = R.string.slash_widget_actions_clean_style,
icon = R.drawable.ic_slash_actions_clean_style,
category = SlashItem.CATEGORY_ACTIONS
)
)
val alignmentList = listOf<SlashItem>(
SlashItem.Alignment.Left(
title = R.string.slash_widget_align_left,
icon = R.drawable.ic_slash_align_left,
category = SlashItem.CATEGORY_ALIGNMENT
),
SlashItem.Alignment.Center(
title = R.string.slash_widget_align_center,
icon = R.drawable.ic_slash_align_center,
category = SlashItem.CATEGORY_ALIGNMENT
),
SlashItem.Alignment.Right(
title = R.string.slash_widget_align_right,
icon = R.drawable.ic_slash_align_right,
category = SlashItem.CATEGORY_ALIGNMENT
)
)
}

View file

@ -28,7 +28,8 @@ class SlashTextWatcher(
proceedWithFilter(
text = s,
start = start,
count = count
count = count,
before = before
)
}
@ -49,13 +50,17 @@ class SlashTextWatcher(
}
}
private fun proceedWithFilter(text: CharSequence, start: Int, count: Int) {
private fun proceedWithFilter(text: CharSequence, start: Int, count: Int, before: Int) {
if (isSlashCharVisible()) {
if (isStartPositionBeforeSlash(start = start, slashPos = slashCharPosition)) {
stopSlashWatcher()
} else {
filter += text.subSequence(start, start + count)
Timber.d("Send Filter event:$filter")
filter = SlashHelper.getUpdatedFilter(
filter = filter,
replacement = text.subSequence(startIndex = start, endIndex = start + count),
replacementStart = start - slashCharPosition,
before = before
)
onSlashEvent(SlashTextWatcherState.Filter(filter))
}
}

View file

@ -51,8 +51,12 @@ class ObjectIconWidget @JvmOverloads constructor(
val hasEmojiCircleBackground =
attrs.getBoolean(R.styleable.ObjectIconWidget_hasEmojiCircleBackground, false)
val hasEmojiRoundedBackground =
attrs.getBoolean(R.styleable.ObjectIconWidget_hasEmojiRoundedBackground, false)
val hasEmojiRounded12Background =
attrs.getBoolean(R.styleable.ObjectIconWidget_hasEmojiRounded12Background, false)
val hasEmojiRounded8Background =
attrs.getBoolean(R.styleable.ObjectIconWidget_hasEmojiRounded8Background, false)
val hasInitialRounded8Background =
attrs.getBoolean(R.styleable.ObjectIconWidget_hasInitialRounded8Background, false)
ivEmoji.updateLayoutParams<LayoutParams> {
this.height = emojiSize
@ -73,14 +77,22 @@ class ObjectIconWidget @JvmOverloads constructor(
emojiContainer.setBackgroundResource(R.drawable.circle_object_icon_emoji_background)
}
if (hasEmojiRoundedBackground) {
if (hasEmojiRounded12Background) {
emojiContainer.setBackgroundResource(R.drawable.rectangle_object_icon_emoji_background)
}
if (!hasEmojiCircleBackground && !hasEmojiRoundedBackground) {
if (hasEmojiRounded8Background) {
emojiContainer.setBackgroundResource(R.drawable.rectangle_object_icon_emoji_background_8)
}
if (!hasEmojiCircleBackground && !hasEmojiRounded12Background && !hasEmojiRounded8Background) {
emojiContainer.background = null
}
if (hasInitialRounded8Background) {
initialContainer.setBackgroundResource(R.drawable.rectangle_avatar_initial_background_8)
}
val initialTextSize =
attrs.getDimensionPixelSize(R.styleable.ObjectIconWidget_initialTextSize, 0)
if (initialTextSize > 0) initial.setTextSize(

View file

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="21dp"
android:height="14dp"
android:viewportWidth="21"
android:viewportHeight="14">
<path
android:pathData="M18.0557,1H6.4387C6.1288,1 5.8363,1.1438 5.647,1.3892L1.3186,7L5.647,12.6108C5.8363,12.8562 6.1288,13 6.4387,13H18.0557C18.6079,13 19.0557,12.5523 19.0557,12V2C19.0557,1.4477 18.6079,1 18.0557,1ZM0.0557,7L4.8552,13.2216C5.2339,13.7125 5.8188,14 6.4387,14H18.0557C19.1602,14 20.0557,13.1046 20.0557,12V2C20.0557,0.8954 19.1602,0 18.0557,0H6.4387C5.8188,0 5.2339,0.2875 4.8552,0.7784L0.0557,7Z"
android:fillColor="#ACA996"
android:fillType="evenOdd"/>
<path
android:pathData="M8.6465,3.6465C8.8417,3.4512 9.1583,3.4512 9.3535,3.6465L12,6.2929L14.6464,3.6465C14.8417,3.4512 15.1583,3.4512 15.3536,3.6465C15.5488,3.8417 15.5488,4.1583 15.3536,4.3535L12.7071,7L15.3536,9.6465C15.5488,9.8417 15.5488,10.1583 15.3536,10.3536C15.1583,10.5488 14.8417,10.5488 14.6464,10.3536L12,7.7071L9.3535,10.3536C9.1583,10.5488 8.8417,10.5488 8.6465,10.3536C8.4512,10.1583 8.4512,9.8417 8.6465,9.6465L11.2929,7L8.6465,4.3535C8.4512,4.1583 8.4512,3.8417 8.6465,3.6465Z"
android:fillColor="#ACA996"
android:fillType="evenOdd"/>
</vector>

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">
<corners android:radius="@dimen/dp_8" />
<solid android:color="#F3F2EC" />
</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">
<corners android:radius="@dimen/dp_8" />
<solid android:color="#F3F2EC" />
</shape>

View file

@ -13,7 +13,7 @@
app:emojiSize="28dp"
app:imageSize="@dimen/dp_48"
app:initialTextSize="28sp"
app:hasEmojiRoundedBackground="true"
app:hasEmojiRounded12Background="true"
android:layout_gravity="center_vertical"
tools:background="@drawable/circle_solid_default" />

View file

@ -41,7 +41,7 @@
app:emojiSize="28dp"
app:imageSize="@dimen/dp_48"
app:initialTextSize="28sp"
app:hasEmojiRoundedBackground="true"
app:hasEmojiRounded12Background="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

View file

@ -28,7 +28,7 @@
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
app:emojiSize="28dp"
app:hasEmojiRoundedBackground="true"
app:hasEmojiRounded12Background="true"
app:imageSize="@dimen/dp_48"
app:initialTextSize="28sp"
app:layout_constraintBottom_toBottomOf="parent"

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<com.anytypeio.anytype.core_ui.widgets.ObjectIconWidget
android:id="@+id/ivIcon"
android:layout_width="40dp"
android:layout_height="40dp"
app:emojiSize="24dp"
app:hasEmojiRounded8Background="true"
app:hasInitialRounded8Background="true"
app:imageSize="40dp"
app:initialTextSize="22sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvTitle"
style="@style/SlashWidgetStyleItemTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/ivIcon"
app:layout_constraintTop_toTopOf="parent"
tools:text="File" />
<TextView
android:id="@+id/tvSubtitle"
style="@style/SlashWidgetStyleItemSubTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginBottom="8dp"
android:maxLines="1"
android:singleLine="true"
android:ellipsize="end"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/ivIcon"
app:layout_constraintTop_toBottomOf="@id/tvTitle"
tools:text="Just start writing with a plain text" />
<View
android:id="@+id/view"
android:layout_width="0dp"
android:layout_height="0.5dp"
android:layout_marginStart="52dp"
android:background="@drawable/divider_relations"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,6 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent">
<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">
<ImageView
android:id="@+id/ivIcon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:scaleType="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_slash_style_heading" />
<TextView
android:id="@+id/tvTitle"
style="@style/SlashWidgetStyleItemTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
app:layout_constraintBottom_toTopOf="@+id/tvSubtitle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/ivIcon"
app:layout_constraintTop_toTopOf="@+id/ivIcon"
tools:text="Text" />
<TextView
android:id="@+id/tvSubtitle"
style="@style/SlashWidgetStyleItemSubTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
app:layout_constraintBottom_toBottomOf="@+id/ivIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/ivIcon"
app:layout_constraintTop_toBottomOf="@+id/tvTitle"
tools:text="Just start writing with a plain text" />
<View
android:id="@+id/view"
android:layout_width="0dp"
android:layout_height="0.5dp"
android:layout_marginStart="52dp"
android:background="@drawable/divider_relations"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="40dp"
xmlns:tools="http://schemas.android.com/tools">
<TextView
android:id="@+id/subheader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
android:fontFamily="@font/inter_medium"
android:textColor="@color/black"
tools:text="Media"
android:textSize="@dimen/sp_13" />
<FrameLayout
android:id="@+id/flBack"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingStart="@dimen/dp_20"
android:layout_gravity="end"
android:clickable="true"
android:focusable="true">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:src="@drawable/ic_slash_widget_back"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="26dp"
android:fontFamily="@font/inter_regular"
android:textSize="13sp"
android:layout_gravity="center_vertical"
android:textColor="#929082"
android:text="@string/slash_widget_back"/>
</FrameLayout>
</FrameLayout>

View file

@ -19,9 +19,11 @@
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:id="@+id/rvSlash"
android:layout_width="0dp"
android:layout_height="0dp"
android:paddingStart="@dimen/dp_20"
android:paddingEnd="@dimen/dp_20"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@ -29,12 +31,4 @@
tools:itemCount="3"
tools:listitem="@layout/item_mention" />
<TextView
android:id="@+id/tvFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/recyclerView" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -9,7 +9,9 @@
<attr name="imageSize" format="dimension"/>
<attr name="checkboxSize" format="dimension"/>
<attr name="hasEmojiCircleBackground" format="boolean" />
<attr name="hasEmojiRoundedBackground" format="boolean" />
<attr name="hasEmojiRounded12Background" format="boolean" />
<attr name="hasEmojiRounded8Background" format="boolean" />
<attr name="hasInitialRounded8Background" format="boolean" />
<attr name="initialTextSize" format="dimension" />
</declare-styleable>

View file

@ -313,6 +313,7 @@
<string name="slash_widget_main_style">Style</string>
<string name="slash_widget_main_media">Media</string>
<string name="slash_widget_main_objects">Objects</string>
<string name="slash_widget_main_objects_subheader">Object types</string>
<string name="slash_widget_main_relations">Relations</string>
<string name="slash_widget_main_other">Other</string>
<string name="slash_widget_main_actions">Actions</string>
@ -367,6 +368,7 @@
<string name="slash_widget_actions_move">Move</string>
<string name="slash_widget_actions_moveto">Move to</string>
<string name="slash_widget_actions_clean_style">Clean style</string>
<string name="slash_widget_back">Back</string>
<string name="slash_widget_align_right">Right</string>
<string name="slash_widget_align_center">Center</string>

View file

@ -645,6 +645,20 @@
<item name="android:maxLines">1</item>
</style>
<style name="SlashWidgetStyleItemTitle">
<item name="android:fontFamily">@font/inter_regular</item>
<item name="android:textSize">15sp</item>
<item name="android:textColor">@color/black</item>
<item name="android:layout_gravity">center_vertical|start</item>
</style>
<style name="SlashWidgetStyleItemSubTitle">
<item name="android:fontFamily">@font/inter_regular</item>
<item name="android:textSize">13sp</item>
<item name="android:textColor">#929082</item>
<item name="android:layout_gravity">center_vertical|start</item>
</style>
<style name="StyleToolbarTextStyleStyle">
<item name="android:background">@drawable/ic_style_toolbar_text_style_background_selector</item>
<item name="android:paddingStart">10dp</item>

View file

@ -19,7 +19,7 @@ ext {
androidx_security_crypto_version = '1.0.0-rc01'
appcompat_version = '1.2.0'
constraintLayout_version = '2.0.4'
recyclerview_version = '1.2.0-rc01'
recyclerview_version = '1.2.0'
cardview_version = '1.0.0'
material_version = '1.1.0-beta02'
fragment_version = "1.3.2"

View file

@ -1,5 +1,6 @@
package com.anytypeio.anytype
import anytype.SmartBlockType
import anytype.model.Block
import com.anytypeio.anytype.common.MockDataFactory
import com.anytypeio.anytype.core_models.Event
@ -36,7 +37,8 @@ class MiddlewareEventChannelTest {
val msg = anytype.Event.Object.Show(
rootId = context,
blocks = emptyList()
blocks = emptyList(),
type = SmartBlockType.Page
)
val message = anytype.Event.Message(objectShow = msg)
@ -100,7 +102,8 @@ class MiddlewareEventChannelTest {
val msg = anytype.Event.Object.Show(
rootId = context,
blocks = emptyList()
blocks = emptyList(),
type = SmartBlockType.Page
)
val message = anytype.Event.Message(objectShow = msg)

View file

@ -14,6 +14,7 @@ import com.anytypeio.anytype.presentation.page.editor.control.ControlPanelState.
import com.anytypeio.anytype.presentation.page.editor.control.ControlPanelState.Toolbar
import com.anytypeio.anytype.presentation.page.editor.mention.Mention
import com.anytypeio.anytype.presentation.page.editor.model.Alignment
import com.anytypeio.anytype.presentation.page.editor.slash.SlashCommand
import com.anytypeio.anytype.presentation.page.editor.styling.StylingMode
import com.anytypeio.anytype.presentation.page.editor.styling.getStyleConfig
import kotlinx.coroutines.CoroutineScope
@ -184,8 +185,15 @@ sealed class ControlPanelMachine {
}
sealed class Slash : Event() {
data class OnStart(val cursorCoordinate: Int, val slashFrom: Int) : Slash()
data class OnFilter(val filter: String) : Slash()
data class OnStart(
val cursorCoordinate: Int,
val slashFrom: Int
) : Slash()
data class Update(
val command: SlashCommand
) : Slash()
object OnStop : Slash()
}
@ -603,6 +611,7 @@ sealed class ControlPanelMachine {
isVisible = true,
from = event.slashFrom,
filter = "",
command = null,
cursorCoordinate = event.cursorCoordinate,
updateList = false,
items = emptyList()
@ -621,11 +630,10 @@ sealed class ControlPanelMachine {
)
)
}
is Event.Slash.OnFilter -> {
is Event.Slash.Update -> {
state.copy(
slashWidget = state.slashWidget.copy(
filter = event.filter,
updateList = false
command = event.command
)
)
}

View file

@ -36,6 +36,7 @@ import com.anytypeio.anytype.domain.base.Result
import com.anytypeio.anytype.domain.block.interactor.RemoveLinkMark
import com.anytypeio.anytype.domain.block.interactor.UpdateLinkMarks
import com.anytypeio.anytype.domain.block.interactor.UpdateText
import com.anytypeio.anytype.domain.block.interactor.sets.GetObjectTypes
import com.anytypeio.anytype.domain.cover.RemoveDocCover
import com.anytypeio.anytype.domain.cover.SetDocCoverImage
import com.anytypeio.anytype.domain.editor.Editor
@ -77,8 +78,7 @@ import com.anytypeio.anytype.presentation.page.editor.sam.ScrollAndMoveTargetDes
import com.anytypeio.anytype.presentation.page.editor.sam.ScrollAndMoveTargetDescriptor.Companion.INNER_RANGE
import com.anytypeio.anytype.presentation.page.editor.sam.ScrollAndMoveTargetDescriptor.Companion.START_RANGE
import com.anytypeio.anytype.presentation.page.editor.search.SearchInDocEvent
import com.anytypeio.anytype.presentation.page.editor.slash.SlashEvent
import com.anytypeio.anytype.presentation.page.editor.slash.SlashItem
import com.anytypeio.anytype.presentation.page.editor.slash.*
import com.anytypeio.anytype.presentation.page.editor.styling.StylingEvent
import com.anytypeio.anytype.presentation.page.editor.styling.StylingMode
import com.anytypeio.anytype.presentation.page.model.TextUpdate
@ -88,6 +88,8 @@ import com.anytypeio.anytype.presentation.page.search.search
import com.anytypeio.anytype.presentation.page.selection.SelectionStateHolder
import com.anytypeio.anytype.presentation.page.toggle.ToggleStateHolder
import com.anytypeio.anytype.presentation.relations.DocumentRelationView
import com.anytypeio.anytype.presentation.relations.RelationListViewModel
import com.anytypeio.anytype.presentation.relations.views
import com.anytypeio.anytype.presentation.util.Dispatcher
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel
@ -120,7 +122,8 @@ class PageViewModel(
private val analytics: Analytics,
private val dispatcher: Dispatcher<Payload>,
private val detailModificationManager: DetailModificationManager,
private val updateDetail: UpdateDetail
private val updateDetail: UpdateDetail,
private val getObjectTypes: GetObjectTypes
) : ViewStateViewModel<ViewState>(),
SupportNavigation<EventWrapper<AppNavigation.Command>>,
SupportCommand<Command>,
@ -3277,33 +3280,6 @@ class PageViewModel(
navigation.postValue(EventWrapper(AppNavigation.Command.OpenPageSearch))
}
fun onSlashItemClicked(item: SlashItem) {
}
fun onSlashEvent(event: SlashEvent) {
when (event) {
is SlashEvent.Filter -> {
controlPanelInteractor.onEvent(
ControlPanelMachine.Event.Slash.OnFilter(
filter = event.filter.toString()
)
)
}
is SlashEvent.Start -> {
controlPanelInteractor.onEvent(
ControlPanelMachine.Event.Slash.OnStart(
cursorCoordinate = event.cursorCoordinate,
slashFrom = event.slashStart
)
)
}
SlashEvent.Stop -> {
controlPanelInteractor.onEvent(ControlPanelMachine.Event.Slash.OnStop)
}
}
}
fun onMentionEvent(mentionEvent: MentionEvent) {
when (mentionEvent) {
is MentionEvent.MentionSuggestText -> {
@ -3569,4 +3545,158 @@ class PageViewModel(
}
enum class Session { IDLE, OPEN, ERROR }
//region SLASH WIDGET
fun onSlashItemClicked(item: SlashItem) {
when (item) {
is SlashItem.Main.Style -> {
val items = listOf(SlashItem.Subheader.StyleWithBack) + SlashExtensions.getSlashItems()
onSlashCommand(SlashCommand.ShowStyleItems(items))
}
is SlashItem.Main.Media -> {
val items = listOf(SlashItem.Subheader.MediaWithBack) + SlashExtensions.getMediaItems()
onSlashCommand(SlashCommand.ShowMediaItems(items))
}
is SlashItem.Main.Relations -> {
val relations = orchestrator.stores.relations.current()
val details = orchestrator.stores.details.current()
val detail = details.details[context]
val values = detail?.map ?: emptyMap()
val update = relations.views(
details = details,
values = values,
urlBuilder = urlBuilder
).map { RelationListViewModel.Model.Item(it) }
onSlashCommand(
SlashCommand.ShowRelations(
relations = listOf(RelationListViewModel.Model.Section.NoSection) + update
)
)
}
is SlashItem.Main.Objects -> {
viewModelScope.launch {
getObjectTypes.invoke(Unit).proceed(
failure = {
Timber.e(it, "Error while getting object types")
},
success = { objectTypes ->
val items = listOf(SlashItem.Subheader.ObjectType) + objectTypes.toView()
onSlashCommand(
SlashCommand.ShowObjectTypes(items)
)
}
)
}
}
is SlashItem.Style.Type -> {
onSlashStyleTypeItemClicked(item)
}
is SlashItem.Media -> {
onSlashMediaItemClicked(item)
}
is SlashItem.ObjectType -> {
}
else -> {
Timber.d("PRESSED ON SLAH ITEM : $item")
}
}
}
private fun onSlashMediaItemClicked(item: SlashItem.Media) {
when (item) {
SlashItem.Media.Bookmark -> {
controlPanelInteractor.onEvent(ControlPanelMachine.Event.Slash.OnStop)
onAddBookmarkBlockClicked()
proceedWithClearingFocus()
}
SlashItem.Media.Code -> {
onAddTextBlockClicked(style = Content.Text.Style.CODE_SNIPPET)
}
SlashItem.Media.File -> {
onAddFileBlockClicked(Content.File.Type.FILE)
proceedWithClearingFocus()
}
SlashItem.Media.Picture -> {
onAddFileBlockClicked(Content.File.Type.IMAGE)
proceedWithClearingFocus()
}
SlashItem.Media.Video -> {
onAddFileBlockClicked(Content.File.Type.VIDEO)
proceedWithClearingFocus()
}
}
}
private fun onSlashStyleTypeItemClicked(item: SlashItem.Style.Type) {
val target = _focus.value
if (target != null) {
viewModelScope.launch {
orchestrator.stores.focus.update(Editor.Focus(
id = target,
cursor = Editor.Cursor.End
))
}
val uiBlock = item.convertToUiBlock()
controlPanelInteractor.onEvent(ControlPanelMachine.Event.Slash.OnStop)
onTurnIntoBlockClicked(
target = target,
uiBlock = uiBlock
)
} else {
Timber.e("Error while style item clicked, target is null")
}
}
private fun onSlashCommand(command: SlashCommand) {
controlPanelInteractor.onEvent(
ControlPanelMachine.Event.Slash.Update(
command = command
)
)
}
fun onSlashBackClicked() {
val items = SlashExtensions.getSlashMainItems()
controlPanelInteractor.onEvent(
ControlPanelMachine.Event.Slash.Update(
command = SlashCommand.ShowMainItems(items)
)
)
}
fun onSlashEvent(event: SlashEvent) {
when (event) {
is SlashEvent.Filter -> {
if (event.filter.length == 1 && event.filter[0] == '/') {
val items = SlashExtensions.getSlashMainItems()
controlPanelInteractor.onEvent(
ControlPanelMachine.Event.Slash.Update(
command = SlashCommand.ShowMainItems(items)
)
)
} else {
controlPanelInteractor.onEvent(
ControlPanelMachine.Event.Slash.Update(
command = SlashCommand.FilterItems(
filter = event.filter.toString(),
viewType = event.viewType
)
)
)
}
}
is SlashEvent.Start -> {
controlPanelInteractor.onEvent(
ControlPanelMachine.Event.Slash.OnStart(
cursorCoordinate = event.cursorCoordinate,
slashFrom = event.slashStart
)
)
}
SlashEvent.Stop -> {
controlPanelInteractor.onEvent(ControlPanelMachine.Event.Slash.OnStop)
}
}
}
//endregion
}

View file

@ -9,6 +9,7 @@ import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.domain.`object`.UpdateDetail
import com.anytypeio.anytype.domain.block.interactor.RemoveLinkMark
import com.anytypeio.anytype.domain.block.interactor.UpdateLinkMarks
import com.anytypeio.anytype.domain.block.interactor.sets.GetObjectTypes
import com.anytypeio.anytype.domain.cover.RemoveDocCover
import com.anytypeio.anytype.domain.cover.SetDocCoverImage
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
@ -44,7 +45,8 @@ open class PageViewModelFactory(
private val analytics: Analytics,
private val dispatcher: Dispatcher<Payload>,
private val detailModificationManager: DetailModificationManager,
private val updateDetail: UpdateDetail
private val updateDetail: UpdateDetail,
private val getObjectTypes: GetObjectTypes
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
@ -71,7 +73,8 @@ open class PageViewModelFactory(
analytics = analytics,
dispatcher = dispatcher,
detailModificationManager = detailModificationManager,
updateDetail = updateDetail
updateDetail = updateDetail,
getObjectTypes = getObjectTypes
) as T
}
}

View file

@ -3,6 +3,8 @@ package com.anytypeio.anytype.presentation.page.editor.control
import com.anytypeio.anytype.presentation.page.editor.Markup
import com.anytypeio.anytype.presentation.page.editor.mention.Mention
import com.anytypeio.anytype.presentation.page.editor.model.Alignment
import com.anytypeio.anytype.presentation.page.editor.slash.SlashCommand
import com.anytypeio.anytype.presentation.page.editor.slash.SlashItem
import com.anytypeio.anytype.presentation.page.editor.styling.StyleConfig
import com.anytypeio.anytype.presentation.page.editor.styling.StylingMode
@ -170,7 +172,8 @@ data class ControlPanelState(
val filter: String? = null,
val cursorCoordinate: Int? = null,
val updateList: Boolean = false,
val items: List<String> = emptyList()
val items: List<String> = emptyList(),
val command: SlashCommand? = null
): Toolbar() {
companion object {
fun reset(): SlashWidget = SlashWidget(
@ -179,7 +182,8 @@ data class ControlPanelState(
from = null,
cursorCoordinate = null,
items = emptyList(),
updateList = false
updateList = false,
command = null
)
}
}

View file

@ -1,7 +1,7 @@
package com.anytypeio.anytype.presentation.page.editor.slash
sealed class SlashEvent {
data class Filter(val filter: CharSequence) : SlashEvent()
data class Filter(val filter: CharSequence, val viewType: Int) : SlashEvent()
data class Start(val cursorCoordinate: Int, val slashStart: Int) : SlashEvent()
object Stop : SlashEvent()
}

View file

@ -0,0 +1,67 @@
package com.anytypeio.anytype.presentation.page.editor.slash
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.presentation.page.editor.model.BlockView
import com.anytypeio.anytype.presentation.page.editor.model.UiBlock
fun List<ObjectType>.toView(): List<SlashItem.ObjectType> = map { oType ->
SlashItem.ObjectType(
url = oType.url,
name = oType.name,
emoji = oType.emoji,
description = oType.description
)
}
fun SlashItem.Style.Type.convertToUiBlock() = when (this) {
SlashItem.Style.Type.Bulleted -> UiBlock.BULLETED
SlashItem.Style.Type.Callout -> TODO()
SlashItem.Style.Type.Checkbox -> UiBlock.CHECKBOX
SlashItem.Style.Type.Heading -> UiBlock.HEADER_TWO
SlashItem.Style.Type.Highlighted -> UiBlock.HIGHLIGHTED
SlashItem.Style.Type.Numbered -> UiBlock.NUMBERED
SlashItem.Style.Type.Subheading -> UiBlock.HEADER_THREE
SlashItem.Style.Type.Text -> UiBlock.TEXT
SlashItem.Style.Type.Title -> UiBlock.HEADER_ONE
SlashItem.Style.Type.Toggle -> UiBlock.TOGGLE
}
object SlashExtensions {
fun getSlashMainItems() = listOf(
SlashItem.Main.Style,
SlashItem.Main.Media,
SlashItem.Main.Objects,
SlashItem.Main.Relations,
SlashItem.Main.Other,
SlashItem.Main.Actions,
SlashItem.Main.Alignment,
SlashItem.Main.Color,
SlashItem.Main.Background,
)
fun getSlashItems() = listOf(
SlashItem.Style.Type.Text,
SlashItem.Style.Type.Title,
SlashItem.Style.Type.Heading,
SlashItem.Style.Type.Subheading,
SlashItem.Style.Type.Highlighted,
SlashItem.Style.Type.Callout,
SlashItem.Style.Type.Checkbox,
SlashItem.Style.Type.Bulleted,
SlashItem.Style.Type.Numbered,
SlashItem.Style.Type.Toggle,
SlashItem.Style.Markup.Bold,
SlashItem.Style.Markup.Italic,
SlashItem.Style.Markup.Breakthrough,
SlashItem.Style.Markup.Code
)
fun getMediaItems() = listOf(
SlashItem.Media.File,
SlashItem.Media.Picture,
SlashItem.Media.Video,
SlashItem.Media.Bookmark,
SlashItem.Media.Code
)
}

View file

@ -1,309 +1,120 @@
package com.anytypeio.anytype.presentation.page.editor.slash
import com.anytypeio.anytype.core_models.Relation
import com.anytypeio.anytype.core_models.Url
import com.anytypeio.anytype.presentation.relations.RelationListViewModel
sealed class SlashCommand {
data class FilterItems(val filter: String, val viewType: Int) : SlashCommand()
data class ShowMainItems(val items: List<SlashItem>) : SlashCommand()
data class ShowStyleItems(val items: List<SlashItem>) : SlashCommand()
data class ShowMediaItems(val items: List<SlashItem>): SlashCommand()
object ShowOtherItems : SlashCommand()
data class ShowRelations(val relations: List<RelationListViewModel.Model>): SlashCommand()
data class ShowObjectTypes(val items: List<SlashItem>): SlashCommand()
}
sealed class SlashItem {
interface Title {
val title: Int
//region SUB HEADER
sealed class Subheader: SlashItem(){
object Style: Subheader()
object StyleWithBack: Subheader()
object Media: Subheader()
object MediaWithBack: Subheader()
object ObjectType: Subheader()
}
//endregion
interface Subtitle {
val subtitle: Int
//region MAIN
sealed class Main : SlashItem() {
object Style: Main()
object Media: Main()
object Objects: Main()
object Relations: Main()
object Other: Main()
object Actions: Main()
object Alignment: Main()
object Color: Main()
object Background: Main()
}
//endregion
interface Icon {
val icon: Int
}
interface SlashCategory {
val category: Int
}
//---------- MAIN -----------------
sealed class Main : SlashItem(), Title, Icon {
data class Style(
override val title: Int,
override val icon: Int
) : Main()
data class Media(
override val title: Int,
override val icon: Int
) : Main()
data class Objects(
override val title: Int,
override val icon: Int
) : Main()
data class Relations(
override val title: Int,
override val icon: Int
) : Main()
data class Other(
override val title: Int,
override val icon: Int
) : Main()
data class Actions(
override val title: Int,
override val icon: Int
) : Main()
data class Alignment(
override val title: Int,
override val icon: Int
) : Main()
data class Color(
override val title: Int,
override val icon: Int
) : Main()
data class Background(
override val title: Int,
override val icon: Int
) : Main()
}
//---------- STYLE -----------------
//region STYLE
sealed class Style : SlashItem() {
sealed class Type : Style(), SlashCategory, Title, Subtitle, Icon {
data class Text(
override val title: Int,
override val subtitle: Int,
override val icon: Int,
override val category: Int
) : Type()
data class Title(
override val title: Int,
override val subtitle: Int,
override val icon: Int,
override val category: Int
) : Type()
data class Heading(
override val title: Int,
override val subtitle: Int,
override val icon: Int,
override val category: Int
) : Type()
data class Subheading(
override val title: Int,
override val subtitle: Int,
override val icon: Int,
override val category: Int
) : Type()
data class Highlighted(
override val title: Int,
override val subtitle: Int,
override val icon: Int,
override val category: Int
) : Type()
data class Callout(
override val title: Int,
override val subtitle: Int,
override val icon: Int,
override val category: Int
) : Type()
data class Checkbox(
override val title: Int,
override val subtitle: Int,
override val icon: Int,
override val category: Int
) : Type()
data class Numbered(
override val title: Int,
override val subtitle: Int,
override val icon: Int,
override val category: Int
) : Type()
data class Toggle(
override val title: Int,
override val subtitle: Int,
override val icon: Int,
override val category: Int
) : Type()
data class Bulleted(
override val title: Int,
override val subtitle: Int,
override val icon: Int,
override val category: Int
) : Type()
sealed class Type : Style() {
object Text: Type()
object Title: Type()
object Heading: Type()
object Subheading: Type()
object Highlighted: Type()
object Callout: Type()
object Checkbox: Type()
object Numbered: Type()
object Toggle: Type()
object Bulleted: Type()
}
sealed class Markup : Style(), SlashCategory, Title, Icon {
data class Bold(
override val title: Int,
override val icon: Int,
override val category: Int
) : Markup()
data class Italic(
override val title: Int,
override val icon: Int,
override val category: Int
) : Markup()
data class Breakthrough(
override val title: Int,
override val icon: Int,
override val category: Int
) : Markup()
data class Code(
override val title: Int,
override val icon: Int,
override val category: Int
) : Markup()
data class Link(
override val title: Int,
override val icon: Int,
override val category: Int
) : Markup()
sealed class Markup : Style() {
object Bold: Markup()
object Italic : Markup()
object Breakthrough: Markup()
object Code: Markup()
}
}
//endregion
//---------- MEDIA -----------------
sealed class Media : SlashItem(), SlashCategory, Title, Subtitle, Icon {
data class File(
override val title: Int,
override val subtitle: Int,
override val icon: Int,
override val category: Int
) : Media()
data class Picture(
override val title: Int,
override val subtitle: Int,
override val icon: Int,
override val category: Int
) : Media()
data class Video(
override val title: Int,
override val subtitle: Int,
override val icon: Int,
override val category: Int
) : Media()
data class Bookmark(
override val title: Int,
override val subtitle: Int,
override val icon: Int,
override val category: Int
) : Media()
data class Code(
override val title: Int,
override val subtitle: Int,
override val icon: Int,
override val category: Int
) : Media()
//region MEDIA
sealed class Media : SlashItem() {
object File: Media()
object Picture: Media()
object Video: Media()
object Bookmark: Media()
object Code: Media()
}
//endregion
//---------- OTHER -----------------
sealed class Other : SlashItem(), SlashCategory, Title, Icon {
data class Line(
override val title: Int,
override val icon: Int,
override val category: Int
) : Other()
//region OBJECT TYPE
data class ObjectType(
val url: Url,
val name: String,
val emoji: String,
val description: String?
) : SlashItem()
//endregion
data class Dots(
override val title: Int,
override val icon: Int,
override val category: Int
) : Other()
//region RELATION
sealed class SlashRelation {
object New: SlashRelation()
data class Items(val relations: List<Relation>): SlashRelation()
}
//endregion
//---------- ACTIONS -----------------
sealed class Actions : SlashItem(), SlashCategory, Title, Icon {
data class Delete(
override val title: Int,
override val icon: Int,
override val category: Int
) : Actions()
data class Duplicate(
override val title: Int,
override val icon: Int,
override val category: Int
) : Actions()
data class Copy(
override val title: Int,
override val icon: Int,
override val category: Int
) : Actions()
data class Paste(
override val title: Int,
override val icon: Int,
override val category: Int
) : Actions()
data class Move(
override val title: Int,
override val icon: Int,
override val category: Int
) : Actions()
data class MoveTo(
override val title: Int,
override val icon: Int,
override val category: Int
) : Actions()
data class CleanStyle(
override val title: Int,
override val icon: Int,
override val category: Int
) : Actions()
//region OTHER
sealed class Other : SlashItem() {
object Line: Other()
object Dots: Other()
}
//endregion
//---------- ALIGNMENT -----------------
sealed class Alignment : SlashItem(), SlashCategory, Title, Icon {
data class Left(
override val title: Int,
override val icon: Int,
override val category: Int
) : Alignment()
data class Center(
override val title: Int,
override val icon: Int,
override val category: Int
) : Alignment()
data class Right(
override val title: Int,
override val icon: Int,
override val category: Int
) : Alignment()
//region ACTIONS
sealed class Actions : SlashItem() {
object Delete: Actions()
object Duplicate: Actions()
object Copy: Actions()
object Paste: Actions()
object Move: Actions()
object MoveTo: Actions()
object CleanStyle: Actions()
}
//endregion
companion object Category {
const val CATEGORY_MAIN = 1
const val CATEGORY_STYLE = 2
const val CATEGORY_MEDIA = 3
const val CATEGORY_OBJECT_TYPES = 4
const val CATEGORY_RELATIONS = 5
const val CATEGORY_OTHER = 6
const val CATEGORY_ACTIONS = 7
const val CATEGORY_ALIGNMENT = 8
const val CATEGORY_COLOR = 9
const val CATEGORY_BACKGROUND = 10
const val CATEGORY_HEADER = 11
//region ALIGNMENT
sealed class Alignment : SlashItem(){
object Left: Alignment()
object Center: Alignment()
object Right: Alignment()
}
//endregion
}

View file

@ -199,7 +199,6 @@ class RelationListViewModel(
fun onRelationTextValueChanged(
ctx: Id,
value: Any?,
objectId: Id,
relationId: Id
) {
viewModelScope.launch {
@ -232,6 +231,10 @@ class RelationListViewModel(
object Other : Section() {
override val identifier: String get() = "Section_Other"
}
object NoSection : Section() {
override val identifier: String get() = "No_Section"
}
}
data class Item(val view: DocumentRelationView) : Model() {

View file

@ -11,6 +11,7 @@ import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.domain.base.Result
import com.anytypeio.anytype.domain.block.UpdateDivider
import com.anytypeio.anytype.domain.block.interactor.*
import com.anytypeio.anytype.domain.block.interactor.sets.GetObjectTypes
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.clipboard.Copy
import com.anytypeio.anytype.domain.clipboard.Paste
@ -203,6 +204,9 @@ open class PageViewModelTest {
@Mock
lateinit var coverImageHashProvider: CoverImageHashProvider
@Mock
lateinit var getObjectTypes: GetObjectTypes
@Mock
lateinit var repo: BlockRepository
@ -3982,7 +3986,8 @@ open class PageViewModelTest {
setDocCoverImage = setDocCoverImage,
removeDocCover = removeDocCover,
detailModificationManager = InternalDetailModificationManager(storage.details),
updateDetail = updateDetail
updateDetail = updateDetail,
getObjectTypes = getObjectTypes
)
}

View file

@ -9,6 +9,7 @@ import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.domain.base.Result
import com.anytypeio.anytype.domain.block.UpdateDivider
import com.anytypeio.anytype.domain.block.interactor.*
import com.anytypeio.anytype.domain.block.interactor.sets.GetObjectTypes
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.clipboard.Copy
import com.anytypeio.anytype.domain.clipboard.Paste
@ -173,6 +174,9 @@ open class EditorPresentationTestSetup {
@Mock
lateinit var repo: BlockRepository
@Mock
lateinit var getObjectTypes: GetObjectTypes
@Mock
lateinit var coverImageHashProvider: CoverImageHashProvider
@ -252,7 +256,8 @@ open class EditorPresentationTestSetup {
removeDocCover = removeDocCover,
setDocCoverImage = setDocCoverImage,
detailModificationManager = InternalDetailModificationManager(storage.details),
updateDetail = updateDetail
updateDetail = updateDetail,
getObjectTypes = getObjectTypes
)
}