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:
parent
94fe66555a
commit
12a43599db
35 changed files with 561 additions and 27 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
4
core-ui/src/main/res/menu/menu_page.xml
Normal file
4
core-ui/src/main/res/menu/menu_page.xml
Normal 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>
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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())
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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") }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -11,7 +11,6 @@ object MockDataFactory {
|
|||
return randomUuid()
|
||||
}
|
||||
|
||||
|
||||
fun randomInt(): Int {
|
||||
return ThreadLocalRandom.current().nextInt(0, 1000 + 1)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue