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

Add navigation bar on pages and enable page archiving (#330)

This commit is contained in:
Evgenii Kozlov 2020-03-29 13:09:08 +02:00 committed by GitHub
parent 94fe66555a
commit 12a43599db
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 561 additions and 27 deletions

View file

@ -5,6 +5,11 @@
### New features 🚀
* Undo/redo changes in document (unstable) (#284)
* User can archive documents (#293)
### Design 🔳
* Added navigation bar with title and icon for pages (#293)
### Fixes & tech 🚒
@ -12,6 +17,7 @@
* Added `blockUndo` command (#284)
* Added `blockRedo` command (#284)
* Added `blockSetPageIsArchived` command (#293)
## Version 0.0.24

View file

@ -63,7 +63,8 @@ class PageModule {
urlBuilder: UrlBuilder,
downloadFile: DownloadFile,
renderer: DefaultBlockViewRenderer,
counter: Counter
counter: Counter,
archiveDocument: ArchiveDocument
): PageViewModelFactory = PageViewModelFactory(
openPage = openPage,
closePage = closePage,
@ -88,7 +89,8 @@ class PageModule {
renderer = renderer,
counter = counter,
undo = undo,
redo = redo
redo = redo,
archiveDocument = archiveDocument
)
@Provides
@ -269,4 +271,12 @@ class PageModule {
): Redo = Redo(
repo = repo
)
@Provides
@PerScreen
fun provideArchiveDocumentUseCase(
repo: BlockRepository
): ArchiveDocument = ArchiveDocument(
repo = repo
)
}

View file

@ -107,8 +107,9 @@ class HomeDashboardFragment : ViewStateFragment<State>(R.layout.fragment_desktop
fab.visible()
state.dashboard?.let { dashboard ->
lifecycleScope.launch {
val result =
withContext(Dispatchers.IO) { dashboard.toView(emojifier = emojifier) }
val result = withContext(Dispatchers.IO) {
dashboard.toView(emojifier = emojifier)
}
dashboardAdapter.update(result)
}
}

View file

@ -22,8 +22,11 @@ import com.agileburo.anytype.R
import com.agileburo.anytype.core_ui.extensions.invisible
import com.agileburo.anytype.core_ui.extensions.visible
import com.agileburo.anytype.core_ui.features.page.BlockAdapter
import com.agileburo.anytype.core_ui.features.page.BlockView
import com.agileburo.anytype.core_ui.menu.DocumentPopUpMenu
import com.agileburo.anytype.core_ui.reactive.clicks
import com.agileburo.anytype.core_ui.state.ControlPanelState
import com.agileburo.anytype.core_ui.tools.FirstItemInvisibilityDetector
import com.agileburo.anytype.core_ui.tools.OutsideClickDetector
import com.agileburo.anytype.core_ui.widgets.toolbar.ActionToolbarWidget.ActionConfig.ACTION_DELETE
import com.agileburo.anytype.core_ui.widgets.toolbar.ActionToolbarWidget.ActionConfig.ACTION_DUPLICATE
@ -126,6 +129,18 @@ open class PageFragment : NavigationFragment(R.layout.fragment_page),
)
}
private val titleVisibilityDetector by lazy {
FirstItemInvisibilityDetector { isVisible ->
if (isVisible) {
title.invisible()
emoji.invisible()
} else {
title.visible()
emoji.visible()
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
@ -258,6 +273,7 @@ open class PageFragment : NavigationFragment(R.layout.fragment_page),
layoutManager = LinearLayoutManager(requireContext())
setHasFixedSize(true)
adapter = pageAdapter
addOnScrollListener(titleVisibilityDetector)
}
toolbar
@ -342,6 +358,16 @@ open class PageFragment : NavigationFragment(R.layout.fragment_page),
.launchIn(lifecycleScope)
fab.clicks().onEach { vm.onPlusButtonPressed() }.launchIn(lifecycleScope)
menu.clicks().onEach { showToolbarMenu() }.launchIn(lifecycleScope)
backButton.clicks().onEach { vm.onBackButtonPressed() }.launchIn(lifecycleScope)
}
private fun showToolbarMenu() {
DocumentPopUpMenu(
requireContext(),
menu,
vm::onArchiveThisPageClicked
).show()
}
private fun handleTextColorClick(click: ColorToolbarWidget.Click.OnTextColorClicked) =
@ -527,6 +553,7 @@ open class PageFragment : NavigationFragment(R.layout.fragment_page),
when (state) {
is PageViewModel.ViewState.Success -> {
pageAdapter.updateWithDiffUtil(state.blocks)
resetDocumentTitle(state)
}
is PageViewModel.ViewState.OpenLinkScreen -> {
SetLinkFragment.newInstance(
@ -541,6 +568,15 @@ open class PageFragment : NavigationFragment(R.layout.fragment_page),
}
}
private fun resetDocumentTitle(state: PageViewModel.ViewState.Success) {
state.blocks.firstOrNull { view ->
view is BlockView.Title
}?.let { view ->
title.text = (view as BlockView.Title).text
emoji.text = view.emoji
}
}
private fun render(state: ControlPanelState) {
Timber.d("Rendering new control panel state:\n$state")
markupToolbar.setState(state.markupToolbar)

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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:id="@+id/root"
android:background="@drawable/background_desktop"
@ -14,21 +15,71 @@
android:focusable="true"
android:focusableInTouchMode="true" />
<FrameLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/sheet"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/rectangle_debug_background"
android:background="@color/white"
app:layout_behavior="@string/bottom_sheet_behavior">
<ImageView
android:contentDescription="@string/content_description_back_button_icon"
android:id="@+id/backButton"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="16dp"
android:layout_marginTop="12dp"
android:src="@drawable/ic_back"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:visibility="invisible"
android:textColor="@color/emoji_color"
android:id="@+id/emoji"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
app:layout_constraintBottom_toBottomOf="@+id/backButton"
app:layout_constraintStart_toEndOf="@+id/backButton"
app:layout_constraintTop_toTopOf="@+id/backButton"
tools:text="🌗" />
<TextView
android:visibility="invisible"
android:textColor="#6C6A5F"
android:textSize="15sp"
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
app:layout_constraintBottom_toBottomOf="@+id/emoji"
app:layout_constraintStart_toEndOf="@+id/emoji"
app:layout_constraintTop_toTopOf="@+id/emoji"
tools:text="Title" />
<ImageView
android:contentDescription="@string/content_description_menu_icon"
android:id="@+id/menu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_marginEnd="16dp"
android:src="@drawable/ic_more"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:layout_height="match_parent"
android:layout_marginTop="16dp"
android:id="@+id/recycler"
android:layout_width="match_parent"
android:paddingBottom="48dp" />
android:layout_height="match_parent"
android:layout_marginTop="48dp"
android:paddingBottom="48dp"
app:layout_constraintTop_toTopOf="parent" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:id="@+id/container"

View file

@ -81,7 +81,7 @@ Do the computation of an expensive paragraph of text on a background thread:
<string name="greet">Hi, %1$s!</string>
<string name="organize_everything">Organize everything</string>
<string name="setting_up_the_wallet">Setting up the wallet...</string>
<string name="setting_up_the_wallet">Setting up the wallet</string>
<string name="sign_clock"></string>
<string name="back">Back</string>
<string name="create_a_new_profile">Create a new profile</string>
@ -105,5 +105,6 @@ Do the computation of an expensive paragraph of text on a background thread:
<string name="page_icon_picker_remove_text">Remove</string>
<string name="create_bookmark">Create bookmark</string>
<string name="hint_paste_or_type_a_url">Paste or type a URL</string>
<string name="content_description_menu_icon">Menu icon</string>
</resources>

View file

@ -0,0 +1,21 @@
package com.agileburo.anytype.core_ui.menu
import android.content.Context
import android.view.View
import android.widget.PopupMenu
import com.agileburo.anytype.core_ui.R
class DocumentPopUpMenu(
context: Context,
view: View,
onArchiveClicked: () -> Unit
) : PopupMenu(context, view) {
init {
menuInflater.inflate(R.menu.menu_page, menu)
setOnMenuItemClickListener {
onArchiveClicked()
true
}
}
}

View file

@ -0,0 +1,24 @@
package com.agileburo.anytype.core_ui.tools
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlin.properties.Delegates
class FirstItemInvisibilityDetector(
onVisibilityChanged: (Boolean) -> Unit
) : RecyclerView.OnScrollListener() {
private var visible: Boolean by Delegates.observable(true) { _, old, new ->
if (new != old) onVisibilityChanged(new)
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (recyclerView.layoutManager is LinearLayoutManager) {
val mng = (recyclerView.layoutManager as LinearLayoutManager)
visible = mng.findFirstVisibleItemPosition() == 0
} else {
throw IllegalStateException("This detector support only LinearLayoutManager")
}
}
}

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="@string/archive" />
</menu>

View file

@ -135,5 +135,6 @@
<string name="block_with_a_picture">Block with a picture</string>
<string name="tap_here_to_insert_a_bookmark_url">Tap here to insert a bookmark url</string>
<string name="add_a_web_bookmark">Add a web bookmark</string>
<string name="archive">Archive</string>
</resources>

View file

@ -406,6 +406,11 @@ fun Command.Redo.toEntity() = CommandEntity.Redo(
context = context
)
fun Command.ArchiveDocument.toEntity() = CommandEntity.ArchiveDocument(
context = context,
target = target
)
fun Position.toEntity(): PositionEntity {
return PositionEntity.valueOf(name)
}

View file

@ -5,6 +5,11 @@ package com.agileburo.anytype.data.auth.model
*/
class CommandEntity {
class ArchiveDocument(
val context: String,
val target: String
)
class UpdateText(
val contextId: String,
val blockId: String,

View file

@ -83,4 +83,8 @@ class BlockDataRepository(
override suspend fun undo(command: Command.Undo) = factory.remote.undo(command.toEntity())
override suspend fun redo(command: Command.Redo) = factory.remote.redo(command.toEntity())
override suspend fun archiveDocument(
command: Command.ArchiveDocument
) = factory.remote.archiveDocument(command.toEntity())
}

View file

@ -27,4 +27,5 @@ interface BlockDataStore {
suspend fun setupBookmark(command: CommandEntity.SetupBookmark)
suspend fun undo(command: CommandEntity.Undo)
suspend fun redo(command: CommandEntity.Redo)
suspend fun archiveDocument(command: CommandEntity.ArchiveDocument)
}

View file

@ -27,4 +27,5 @@ interface BlockRemote {
suspend fun setupBookmark(command: CommandEntity.SetupBookmark)
suspend fun undo(command: CommandEntity.Undo)
suspend fun redo(command: CommandEntity.Redo)
suspend fun archiveDocument(command: CommandEntity.ArchiveDocument)
}

View file

@ -76,4 +76,8 @@ class BlockRemoteDataStore(private val remote: BlockRemote) : BlockDataStore {
override suspend fun undo(command: CommandEntity.Undo) = remote.undo(command)
override suspend fun redo(command: CommandEntity.Redo) = remote.redo(command)
override suspend fun archiveDocument(
command: CommandEntity.ArchiveDocument
) = remote.archiveDocument(command)
}

View file

@ -30,12 +30,14 @@ data class Block(
val name: String by default
val icon: String? by default
val isArchived: Boolean? by default
fun hasName() = map.containsKey(NAME_KEY)
companion object {
fun empty(): Fields = Fields(emptyMap())
const val NAME_KEY = "name"
const val IS_ARCHIVED_KEY = "isArchived"
}
}

View file

@ -4,6 +4,16 @@ import com.agileburo.anytype.domain.common.Id
sealed class Command {
/**
* Command for archiving a document
* @property context id of the context
* @property target id of the target (document we want to close)
*/
class ArchiveDocument(
val context: Id,
val target: Id
)
/**
* @property contextId context id
* @property blockId target block id

View file

@ -9,6 +9,8 @@ interface BlockRepository {
suspend fun duplicate(command: Command.Duplicate): Id
suspend fun unlink(command: Command.Unlink)
suspend fun archiveDocument(command: Command.ArchiveDocument)
/**
* Creates a new block.
* @return id of the created block.

View file

@ -0,0 +1,38 @@
package com.agileburo.anytype.domain.page
import com.agileburo.anytype.domain.base.BaseUseCase
import com.agileburo.anytype.domain.base.Either
import com.agileburo.anytype.domain.block.model.Command
import com.agileburo.anytype.domain.block.repo.BlockRepository
import com.agileburo.anytype.domain.common.Id
/**
* Use-case for archiving a document
*/
class ArchiveDocument(
private val repo: BlockRepository
) : BaseUseCase<Unit, ArchiveDocument.Params>() {
override suspend fun run(params: Params) = try {
repo.archiveDocument(
command = Command.ArchiveDocument(
context = params.context,
target = params.target
)
).let {
Either.Right(it)
}
} catch (t: Throwable) {
Either.Left(t)
}
/**
* Params for archiving a document
* @property context id of the context
* @property target id of the target (document we want to close)
*/
data class Params(
val context: Id,
val target: Id
)
}

View file

@ -25,7 +25,7 @@ open class ClosePage(
/**
* @property id page's id
*/
class Params(val id: String) {
data class Params(val id: String) {
companion object {
fun reference() = Params(id = MainConfig.REFERENCE_PAGE_ID)
}

View file

@ -0,0 +1,33 @@
package com.agileburo.anytype.domain.block.model
import org.junit.Test
import kotlin.test.assertNull
import kotlin.test.assertTrue
class FieldsTest {
@Test
fun `should return null if isArchived-key is not present`() {
val fields = Block.Fields(emptyMap())
assertNull(actual = fields.isArchived)
}
@Test
fun `should return value for isArchived property`() {
Block.Fields(
map = mapOf(
Block.Fields.IS_ARCHIVED_KEY to false
)
).apply {
assertTrue { isArchived == false }
}
Block.Fields(
map = mapOf(
Block.Fields.IS_ARCHIVED_KEY to true
)
).apply {
assertTrue { isArchived == true }
}
}
}

View file

@ -100,6 +100,7 @@ fun Struct.fields(): BlockEntity.Fields = BlockEntity.Fields().also { result ->
result.map[key] = when (val case = value.kindCase) {
Value.KindCase.NUMBER_VALUE -> value.numberValue
Value.KindCase.STRING_VALUE -> value.stringValue
Value.KindCase.BOOL_VALUE -> value.boolValue
else -> throw IllegalStateException("$case is not supported.")
}
}
@ -209,6 +210,7 @@ fun Block.link(): BlockEntity.Content.Link = BlockEntity.Content.Link(
result.map[key] = when (val case = value.kindCase) {
Value.KindCase.NUMBER_VALUE -> value.numberValue
Value.KindCase.STRING_VALUE -> value.stringValue
Value.KindCase.BOOL_VALUE -> value.boolValue
else -> throw IllegalStateException("$case is not supported.")
}
}

View file

@ -98,4 +98,8 @@ class BlockMiddleware(
override suspend fun undo(command: CommandEntity.Undo) = middleware.undo(command)
override suspend fun redo(command: CommandEntity.Redo) = middleware.redo(command)
override suspend fun archiveDocument(
command: CommandEntity.ArchiveDocument
) = middleware.archiveDocument(command)
}

View file

@ -422,4 +422,17 @@ public class Middleware {
service.blockRedo(request);
}
public void archiveDocument(CommandEntity.ArchiveDocument command) throws Exception {
Block.Set.Page.IsArchived.Request request = Block.Set.Page.IsArchived.Request
.newBuilder()
.setContextId(command.getContext())
.setBlockId(command.getTarget())
.setIsArchived(true)
.build();
Timber.d("Archiving document with the following request:\n%s", request.toString());
service.blockSetPageIsArchived(request);
}
}

View file

@ -306,4 +306,15 @@ public class DefaultMiddlewareService implements MiddlewareService {
return response;
}
}
@Override
public Block.Set.Page.IsArchived.Response blockSetPageIsArchived(Block.Set.Page.IsArchived.Request request) throws Exception {
byte[] encoded = Lib.blockSetPageIsArchived(request.toByteArray());
Block.Set.Page.IsArchived.Response response = Block.Set.Page.IsArchived.Response.parseFrom(encoded);
if (response.getError() != null && response.getError().getCode() != Block.Set.Page.IsArchived.Response.Error.Code.NULL) {
throw new Exception(response.getError().getDescription());
} else {
return response;
}
}
}

View file

@ -64,4 +64,6 @@ public interface MiddlewareService {
Block.Undo.Response blockUndo(Block.Undo.Request request) throws Exception;
Block.Redo.Response blockRedo(Block.Redo.Request request) throws Exception;
Block.Set.Page.IsArchived.Response blockSetPageIsArchived(Block.Set.Page.IsArchived.Request request) throws Exception;
}

View file

@ -6,7 +6,6 @@ import com.agileburo.anytype.data.auth.repo.AuthCache
import com.agileburo.anytype.persistence.db.AnytypeDatabase
import com.agileburo.anytype.persistence.mapper.toEntity
import com.agileburo.anytype.persistence.mapper.toTable
import timber.log.Timber
class DefaultAuthCache(
private val db: AnytypeDatabase,
@ -32,7 +31,6 @@ class DefaultAuthCache(
}
override suspend fun saveMnemonic(mnemonic: String) {
Timber.d("Saving mnemonic: $mnemonic")
prefs.edit().putString(MNEMONIC_KEY, mnemonic).apply()
}

View file

@ -50,7 +50,7 @@ class KeychainLoginViewModel(
fnR = { proceedWithSavingMnemonic(chain) },
fnL = {
state.postValue(ViewState.Error(it.localizedMessage))
Timber.e(it, "Error while recovering wallet with the following mnemonic: $chain")
Timber.e(it, "Error while recovering wallet")
}
)
}
@ -67,7 +67,7 @@ class KeychainLoginViewModel(
fnR = {
navigation.postValue(EventWrapper(AppNavigation.Command.SelectAccountScreen))
},
fnL = { Timber.e(it, "Error while saving mnemonic: $mnemonic") }
fnL = { Timber.e(it, "Error while saving mnemonic") }
)
}
}

View file

@ -134,16 +134,23 @@ suspend fun HomeDashboard.toView(
when (val content = model.content) {
is Block.Content.Link -> {
if (content.type == Block.Content.Link.Type.PAGE) {
DashboardView.Document(
id = content.target,
title = if (content.fields.hasName()) content.fields.name else defaultTitle,
emoji = content.fields.icon?.let { name ->
if (name.isNotEmpty())
emojifier.fromShortName(name).unicode
if (content.fields.isArchived != true) {
DashboardView.Document(
id = content.target,
title = if (content.fields.hasName())
content.fields.name.ifEmpty { defaultTitle }
else
null
}
)
defaultTitle,
emoji = content.fields.icon?.let { name ->
if (name.isNotEmpty())
emojifier.fromShortName(name).unicode
else
null
}
)
} else {
null
}
} else {
null
}

View file

@ -45,6 +45,7 @@ class PageViewModel(
private val openPage: OpenPage,
private val closePage: ClosePage,
private val createPage: CreatePage,
private val archiveDocument: ArchiveDocument,
private val undo: Undo,
private val redo: Redo,
private val updateBlock: UpdateBlock,
@ -374,6 +375,10 @@ class PageViewModel(
proceedWithExiting()
}
fun onBackButtonPressed() {
proceedWithExiting()
}
fun onBottomSheetHidden() {
proceedWithExiting()
}
@ -934,6 +939,22 @@ class PageViewModel(
}
}
fun onArchiveThisPageClicked() {
archiveDocument.invoke(
scope = viewModelScope,
params = ArchiveDocument.Params(
context = context,
target = context
),
onResult = { result ->
result.either(
fnL = { Timber.e(it, "Error while archiving page") },
fnR = { proceedWithExiting() }
)
}
)
}
fun onAddBookmarkClicked() {
controlPanelInteractor.onEvent(ControlPanelMachine.Event.OnAddBlockToolbarOptionSelected)

View file

@ -17,6 +17,7 @@ open class PageViewModelFactory(
private val openPage: OpenPage,
private val closePage: ClosePage,
private val createPage: CreatePage,
private val archiveDocument: ArchiveDocument,
private val redo: Redo,
private val undo: Undo,
private val updateBlock: UpdateBlock,
@ -49,6 +50,7 @@ open class PageViewModelFactory(
redo = redo,
updateBlock = updateBlock,
createBlock = createBlock,
archiveDocument = archiveDocument,
interceptEvents = interceptEvents,
updateCheckbox = updateCheckbox,
duplicateBlock = duplicateBlock,

View file

@ -11,7 +11,6 @@ object MockDataFactory {
return randomUuid()
}
fun randomInt(): Int {
return ThreadLocalRandom.current().nextInt(0, 1000 + 1)
}

View file

@ -0,0 +1,77 @@
package com.agileburo.anytype.presentation.mapper
import MockDataFactory
import com.agileburo.anytype.domain.block.model.Block
import com.agileburo.anytype.domain.dashboard.model.HomeDashboard
import com.agileburo.anytype.domain.emoji.Emojifier
import com.agileburo.anytype.domain.ext.content
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import kotlin.test.assertTrue
class HomeDashboardViewMapperTest {
@Mock
lateinit var emojifier: Emojifier
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
}
@Test
fun `shoudl not map archived links`() {
val archived = Block(
id = MockDataFactory.randomUuid(),
content = Block.Content.Link(
type = Block.Content.Link.Type.PAGE,
fields = Block.Fields(
mapOf(
Block.Fields.IS_ARCHIVED_KEY to true
)
),
target = MockDataFactory.randomUuid()
),
children = emptyList(),
fields = Block.Fields.empty()
)
val active = Block(
id = MockDataFactory.randomUuid(),
content = Block.Content.Link(
type = Block.Content.Link.Type.PAGE,
fields = Block.Fields(
mapOf(
Block.Fields.IS_ARCHIVED_KEY to false
)
),
target = MockDataFactory.randomUuid()
),
children = emptyList(),
fields = Block.Fields.empty()
)
val dashboard = HomeDashboard(
id = MockDataFactory.randomUuid(),
blocks = listOf(archived, active),
children = listOf(archived.id, active.id),
fields = Block.Fields.empty(),
type = Block.Content.Dashboard.Type.MAIN_SCREEN
)
val result = runBlocking {
dashboard.toView(
emojifier = emojifier,
defaultTitle = MockDataFactory.randomString()
)
}
assertTrue {
result.size == 1 && result.first().id == active.content<Block.Content.Link>().target
}
}
}

View file

@ -110,6 +110,9 @@ class PageViewModelTest {
@Mock
lateinit var redo: Redo
@Mock
lateinit var archiveDocument: ArchiveDocument
private lateinit var vm: PageViewModel
@Before
@ -3253,6 +3256,140 @@ class PageViewModelTest {
)
}
@Test
fun `should start archiving document on on-archive-this-page-clicked event`() {
// SETUP
val root = MockDataFactory.randomUuid()
val title = MockBlockFactory.makeTitleBlock()
val page = listOf(
Block(
id = root,
fields = Block.Fields(
map = mapOf("icon" to "")
),
content = Block.Content.Page(
style = Block.Content.Page.Style.SET
),
children = listOf(title.id)
),
title
)
val flow: Flow<List<Event.Command>> = flow {
delay(100)
emit(
listOf(
Event.Command.ShowBlock(
rootId = root,
blocks = page,
context = root
)
)
)
}
stubObserveEvents(flow)
stubOpenPage()
buildViewModel()
vm.open(root)
coroutineTestRule.advanceTime(100)
// TESTING
vm.onArchiveThisPageClicked()
verify(archiveDocument, times(1)).invoke(
scope = any(),
params = eq(
ArchiveDocument.Params(
context = root,
target = root
)
),
onResult = any()
)
}
@Test
fun `should start closing page after succesful archive operation`() {
// SETUP
val root = MockDataFactory.randomUuid()
val title = MockBlockFactory.makeTitleBlock()
val page = listOf(
Block(
id = root,
fields = Block.Fields(
map = mapOf("icon" to "")
),
content = Block.Content.Page(
style = Block.Content.Page.Style.SET
),
children = listOf(title.id)
),
title
)
val flow: Flow<List<Event.Command>> = flow {
delay(100)
emit(
listOf(
Event.Command.ShowBlock(
rootId = root,
blocks = page,
context = root
)
)
)
}
stubObserveEvents(flow)
stubOpenPage()
buildViewModel()
vm.open(root)
coroutineTestRule.advanceTime(100)
archiveDocument.stub {
onBlocking { invoke(any(), any(), any()) } doAnswer { answer ->
answer.getArgument<(Either<Throwable, Unit>) -> Unit>(2)(Either.Right(Unit))
}
}
// TESTING
vm.onArchiveThisPageClicked()
verify(archiveDocument, times(1)).invoke(
scope = any(),
params = eq(
ArchiveDocument.Params(
context = root,
target = root
)
),
onResult = any()
)
verify(closePage, times(1)).invoke(
scope = any(),
params = eq(
ClosePage.Params(
id = root
)
),
onResult = any()
)
}
private fun simulateNormalPageOpeningFlow() {
val root = MockDataFactory.randomUuid()
@ -3339,7 +3476,8 @@ class PageViewModelTest {
urlBuilder = urlBuilder,
emojifier = emojifier,
toggleStateHolder = ToggleStateHolder.Default()
)
),
archiveDocument = archiveDocument
)
}
}