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:
parent
21cb51e789
commit
d84ab2319c
52 changed files with 2541 additions and 661 deletions
2
.github/workflows/check.yml
vendored
2
.github/workflows/check.yml
vendored
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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 = "/"
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
14
core-ui/src/main/res/drawable/ic_slash_widget_back.xml
Normal file
14
core-ui/src/main/res/drawable/ic_slash_widget_back.xml
Normal 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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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" />
|
||||
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
44
core-ui/src/main/res/layout/item_slash_widget_subheader.xml
Normal file
44
core-ui/src/main/res/layout/item_slash_widget_subheader.xml
Normal 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>
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue