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

DROID-449 App | Tech | Add debug tree menu (#2730)

DROID-449 App | Tech | Add debug tree menu
This commit is contained in:
Mikhail 2022-11-30 23:17:39 +03:00 committed by GitHub
parent 548f3b0039
commit e2ac0e6076
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 598 additions and 359 deletions

View file

@ -16,7 +16,7 @@ import com.anytypeio.anytype.core_models.Event
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_models.Position
import com.anytypeio.anytype.core_ui.widgets.text.TextInputWidget
import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.domain.base.Resultat
import com.anytypeio.anytype.domain.block.interactor.CreateBlock
import com.anytypeio.anytype.domain.block.interactor.UpdateTextStyle
import com.anytypeio.anytype.features.editor.base.EditorTestSetup
@ -24,7 +24,6 @@ import com.anytypeio.anytype.features.editor.base.TestEditorFragment
import com.anytypeio.anytype.presentation.MockBlockContentFactory.StubTextContent
import com.anytypeio.anytype.presentation.MockBlockFactory
import com.anytypeio.anytype.presentation.editor.EditorViewModel
import com.anytypeio.anytype.test_utils.ValueClassAnswer
import com.anytypeio.anytype.test_utils.utils.TestUtils
import com.anytypeio.anytype.ui.editor.EditorFragment
import com.anytypeio.anytype.utils.CoroutinesTestRule
@ -33,7 +32,6 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.stub
import org.mockito.kotlin.times
@ -183,7 +181,7 @@ class CreateBlockTesting : EditorTestSetup() {
// Check results
verifyBlocking(createBlock, times(1)) { invoke(params) }
verifyBlocking(createBlock, times(1)) { asFlow(params) }
Espresso.onView(
TestUtils.withRecyclerView(R.id.recycler).atPositionOnView(0, targetViewId)
@ -326,7 +324,7 @@ class CreateBlockTesting : EditorTestSetup() {
createBlock.stub {
onBlocking {
execute(params)
} doAnswer ValueClassAnswer(
} doReturn Resultat.success(
Pair(new.id, Payload(context = root, events = events))
)
}

View file

@ -18,6 +18,7 @@ import com.anytypeio.anytype.core_models.SmartBlockType
import com.anytypeio.anytype.core_models.ext.content
import com.anytypeio.anytype.core_ui.widgets.text.TextInputWidget
import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.domain.base.Resultat
import com.anytypeio.anytype.domain.block.interactor.CreateBlock
import com.anytypeio.anytype.domain.block.interactor.UpdateTextStyle
import com.anytypeio.anytype.features.editor.base.EditorTestSetup
@ -25,7 +26,6 @@ import com.anytypeio.anytype.features.editor.base.TestEditorFragment
import com.anytypeio.anytype.presentation.MockBlockContentFactory.StubTextContent
import com.anytypeio.anytype.presentation.MockBlockFactory
import com.anytypeio.anytype.presentation.editor.EditorViewModel
import com.anytypeio.anytype.test_utils.ValueClassAnswer
import com.anytypeio.anytype.test_utils.utils.TestUtils
import com.anytypeio.anytype.ui.editor.EditorFragment
import com.bartoszlipinski.disableanimationsrule.DisableAnimationsRule
@ -367,7 +367,7 @@ class ListBlockTesting : EditorTestSetup() {
createBlock.stub {
onBlocking {
execute(params)
} doAnswer ValueClassAnswer(
} doReturn Resultat.success(
Pair(new.id, Payload(context = root, events = events))
)
}

View file

@ -12,12 +12,12 @@ import com.anytypeio.anytype.core_models.Event
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.domain.base.Result
import com.anytypeio.anytype.domain.base.Resultat
import com.anytypeio.anytype.features.editor.base.EditorTestSetup
import com.anytypeio.anytype.features.editor.base.TestEditorFragment
import com.anytypeio.anytype.presentation.MockBlockContentFactory.StubTextContent
import com.anytypeio.anytype.presentation.editor.EditorViewModel
import com.anytypeio.anytype.test_utils.MockDataFactory
import com.anytypeio.anytype.test_utils.ValueClassAnswer
import com.anytypeio.anytype.test_utils.utils.checkHasText
import com.anytypeio.anytype.test_utils.utils.onItemView
import com.anytypeio.anytype.test_utils.utils.rVMatcher
@ -114,7 +114,7 @@ class MentionUpdateTesting : EditorTestSetup() {
stubInterceptThreadStatus()
stubUpdateText()
openPage.stub {
onBlocking { execute(any()) } doAnswer ValueClassAnswer(
onBlocking { execute(any()) } doReturn Resultat.success(
Result.Success(
Payload(
context = root,

View file

@ -20,6 +20,7 @@ import com.anytypeio.anytype.domain.`object`.UpdateDetail
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.domain.base.Result
import com.anytypeio.anytype.domain.base.Resultat
import com.anytypeio.anytype.domain.block.UpdateDivider
import com.anytypeio.anytype.domain.block.interactor.ClearBlockContent
import com.anytypeio.anytype.domain.block.interactor.ClearBlockStyle
@ -103,9 +104,8 @@ import com.anytypeio.anytype.presentation.editor.toggle.ToggleStateHolder
import com.anytypeio.anytype.presentation.search.ObjectSearchConstants
import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.presentation.util.downloader.MiddlewareShareDownloader
import com.anytypeio.anytype.presentation.util.downloader.DocumentFileShareDownloader
import com.anytypeio.anytype.test_utils.MockDataFactory
import com.anytypeio.anytype.test_utils.ValueClassAnswer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.test.StandardTestDispatcher
@ -140,24 +140,31 @@ open class EditorTestSetup {
lateinit var copyFileToCacheDirectory: CopyFileToCacheDirectory
@Mock
lateinit var middlewareShareDownloader: MiddlewareShareDownloader
lateinit var documentFileShareDownloader: DocumentFileShareDownloader
@Mock
lateinit var openPage: OpenPage
@Mock
lateinit var closePage: CloseBlock
@Mock
lateinit var updateText: UpdateText
@Mock
lateinit var createBlock: CreateBlock
@Mock
lateinit var interceptEvents: InterceptEvents
@Mock
lateinit var updateCheckbox: UpdateCheckbox
@Mock
lateinit var unlinkBlocks: UnlinkBlocks
lateinit var getSearchObjects: SearchObjects
@Mock
lateinit var duplicateBlock: DuplicateBlock
@ -341,7 +348,7 @@ open class EditorTestSetup {
applyTemplate = applyTemplate
)
featureToggles = DefaultFeatureToggles()
featureToggles = mock<DefaultFeatureToggles>()
TestEditorFragment.testViewModelFactory = EditorViewModelFactory(
@ -375,7 +382,7 @@ open class EditorTestSetup {
duplicateBlock = duplicateBlock,
updateAlignment = updateAlignment,
downloadFile = downloadFile,
middlewareShareDownloader = middlewareShareDownloader,
documentFileShareDownloader = documentFileShareDownloader,
mergeBlocks = mergeBlocks,
updateTextColor = updateTextColor,
replaceBlock = replaceBlock,
@ -455,7 +462,7 @@ open class EditorTestSetup {
relations: List<Relation> = emptyList()
) {
openPage.stub {
onBlocking { execute(any()) } doAnswer ValueClassAnswer(
onBlocking { execute(any()) } doReturn Resultat.success(
Result.Success(
Payload(
context = root,
@ -479,7 +486,7 @@ open class EditorTestSetup {
events: List<Event.Command>
) {
createBlock.stub {
onBlocking { execute(params) } doAnswer ValueClassAnswer(
onBlocking { execute(params) } doReturn Resultat.success(
Pair(
MockDataFactory.randomUuid(),
Payload(context = root, events = events)

View file

@ -123,7 +123,8 @@ import com.anytypeio.anytype.presentation.relations.providers.ObjectValueProvide
import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory
import com.anytypeio.anytype.presentation.util.DefaultCopyFileToCacheDirectory
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.presentation.util.downloader.MiddlewareShareDownloader
import com.anytypeio.anytype.presentation.util.downloader.DebugTreeShareDownloader
import com.anytypeio.anytype.presentation.util.downloader.DocumentFileShareDownloader
import com.anytypeio.anytype.presentation.util.downloader.UriFileProvider
import com.anytypeio.anytype.providers.DefaultCoverImageHashProvider
import com.anytypeio.anytype.providers.DefaultUriFileProvider
@ -409,7 +410,7 @@ object EditorSessionModule {
setRelationKey: SetRelationKey,
analytics: Analytics,
updateBlocksMark: UpdateBlocksMark,
middlewareShareDownloader: MiddlewareShareDownloader,
documentFileShareDownloader: DocumentFileShareDownloader,
clearBlockContent: ClearBlockContent,
clearBlockStyle: ClearBlockStyle
): Orchestrator = Orchestrator(
@ -431,7 +432,7 @@ object EditorSessionModule {
updateDivider = updateDivider,
memory = memory,
downloadFile = downloadFile,
middlewareShareDownloader = middlewareShareDownloader,
documentFileShareDownloader = documentFileShareDownloader,
turnIntoDocument = turnIntoDocument,
textInteractor = Interactor.TextInteractor(
proxies = proxer,
@ -1034,17 +1035,25 @@ object EditorUseCaseModule {
@JvmStatic
@Provides
@PerScreen
fun providesMiddlewareShareDownloader(
fun providesDocumentFileShareDownloader(
repo: BlockRepository,
context: Context,
fileProvider: UriFileProvider
): MiddlewareShareDownloader = MiddlewareShareDownloader(
): DocumentFileShareDownloader = DocumentFileShareDownloader(
repo = repo,
context = context.applicationContext,
uriFileProvider = fileProvider
)
@JvmStatic
@Provides
@PerScreen
fun providesDebugTreeShareDownloader(
repo: BlockRepository,
context: Context,
fileProvider: UriFileProvider
): DebugTreeShareDownloader = DebugTreeShareDownloader(
repo = repo,
dispatchers = AppCoroutineDispatchers(
io = Dispatchers.IO,
computation = Dispatchers.Default,
main = Dispatchers.Main
),
context = context.applicationContext,
uriFileProvider = fileProvider
)

View file

@ -3,6 +3,7 @@ package com.anytypeio.anytype.di.feature
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerDialog
import com.anytypeio.anytype.core_utils.tools.FeatureToggles
import com.anytypeio.anytype.domain.`object`.DuplicateObject
import com.anytypeio.anytype.domain.auth.repo.AuthRepository
import com.anytypeio.anytype.domain.block.interactor.CreateBlock
@ -23,6 +24,7 @@ import com.anytypeio.anytype.presentation.objects.menu.ObjectMenuViewModel
import com.anytypeio.anytype.presentation.objects.menu.ObjectSetMenuViewModel
import com.anytypeio.anytype.presentation.sets.ObjectSet
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.presentation.util.downloader.DebugTreeShareDownloader
import com.anytypeio.anytype.ui.editor.sheets.ObjectMenuFragment
import com.anytypeio.anytype.ui.sets.ObjectSetMenuFragment
import dagger.Module
@ -92,6 +94,7 @@ object ObjectMenuModule {
fun provideViewModelFactory(
setObjectIsArchived: SetObjectIsArchived,
duplicateObject: DuplicateObject,
debugTreeShareDownloader: DebugTreeShareDownloader,
addToFavorite: AddToFavorite,
removeFromFavorite: RemoveFromFavorite,
addBackLinkToObject: AddBackLinkToObject,
@ -100,10 +103,12 @@ object ObjectMenuModule {
analytics: Analytics,
dispatcher: Dispatcher<Payload>,
updateFields: UpdateFields,
featureToggles: FeatureToggles,
delegator: Delegator<Action>
): ObjectMenuViewModel.Factory = ObjectMenuViewModel.Factory(
setObjectIsArchived = setObjectIsArchived,
duplicateObject = duplicateObject,
debugTreeShareDownloader = debugTreeShareDownloader,
addToFavorite = addToFavorite,
removeFromFavorite = removeFromFavorite,
addBackLinkToObject = addBackLinkToObject,
@ -113,14 +118,15 @@ object ObjectMenuModule {
dispatcher = dispatcher,
updateFields = updateFields,
delegator = delegator,
menuOptionsProvider = createMenuOptionsProvider(storage)
menuOptionsProvider = createMenuOptionsProvider(storage, featureToggles)
)
@JvmStatic
private fun createMenuOptionsProvider(storage: Editor.Storage) =
private fun createMenuOptionsProvider(storage: Editor.Storage, featureToggles: FeatureToggles) =
ObjectMenuOptionsProviderImpl(
details = storage.details.stream().map { it.details },
restrictions = storage.objectRestrictions.stream()
restrictions = storage.objectRestrictions.stream(),
featureToggles = featureToggles
)
}
@ -140,6 +146,7 @@ object ObjectSetMenuModule {
urlBuilder: UrlBuilder,
analytics: Analytics,
state: StateFlow<ObjectSet>,
featureToggles: FeatureToggles,
dispatcher: Dispatcher<Payload>
): ObjectSetMenuViewModel.Factory = ObjectSetMenuViewModel.Factory(
setObjectIsArchived = setObjectIsArchived,
@ -152,7 +159,7 @@ object ObjectSetMenuModule {
analytics = analytics,
state = state,
dispatcher = dispatcher,
menuOptionsProvider = createMenuOptionsProvider(state)
menuOptionsProvider = createMenuOptionsProvider(state, featureToggles)
)
@JvmStatic
@ -173,9 +180,13 @@ object ObjectSetMenuModule {
)
@JvmStatic
private fun createMenuOptionsProvider(state: StateFlow<ObjectSet>) =
private fun createMenuOptionsProvider(
state: StateFlow<ObjectSet>,
featureToggles: FeatureToggles
) =
ObjectMenuOptionsProviderImpl(
details = state.map { it.details },
restrictions = state.map { it.objectRestrictions }
restrictions = state.map { it.objectRestrictions },
featureToggles = featureToggles
)
}

View file

@ -5,16 +5,16 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_ui.features.objects.ObjectActionAdapter
import com.anytypeio.anytype.core_ui.layout.SpacingItemDecoration
import com.anytypeio.anytype.core_ui.reactive.clicks
import com.anytypeio.anytype.core_ui.reactive.click
import com.anytypeio.anytype.core_ui.reactive.proceed
import com.anytypeio.anytype.core_utils.ext.arg
import com.anytypeio.anytype.core_utils.ext.subscribe
import com.anytypeio.anytype.core_utils.ext.shareFile
import com.anytypeio.anytype.core_utils.ext.toast
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetFragment
import com.anytypeio.anytype.core_utils.ui.showActionableSnackBar
@ -29,10 +29,9 @@ import com.anytypeio.anytype.ui.editor.modals.IconPickerFragmentBase
import com.anytypeio.anytype.ui.moving.MoveToFragment
import com.anytypeio.anytype.ui.moving.OnMoveToAction
import com.anytypeio.anytype.ui.relations.RelationListFragment
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
abstract class ObjectMenuBaseFragment : BaseBottomSheetFragment<FragmentObjectMenuBinding>(),
abstract class ObjectMenuBaseFragment :
BaseBottomSheetFragment<FragmentObjectMenuBinding>(),
OnMoveToAction {
protected val ctx get() = arg<Id>(CTX_KEY)
@ -53,30 +52,12 @@ abstract class ObjectMenuBaseFragment : BaseBottomSheetFragment<FragmentObjectMe
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.optionHistory
.clicks()
.onEach { vm.onHistoryClicked() }
.launchIn(lifecycleScope)
binding.optionLayout
.clicks()
.onEach { vm.onLayoutClicked(ctx) }
.launchIn(lifecycleScope)
binding.optionIcon
.clicks()
.onEach { vm.onIconClicked(ctx) }
.launchIn(lifecycleScope)
binding.optionRelations
.clicks()
.onEach { vm.onRelationsClicked() }
.launchIn(lifecycleScope)
binding.optionCover
.clicks()
.onEach { vm.onCoverClicked(ctx) }
.launchIn(lifecycleScope)
click(binding.objectDiagnostics) { vm.onDiagnosticsClicked(ctx) }
click(binding.optionHistory) { vm.onHistoryClicked() }
click(binding.optionLayout) { vm.onLayoutClicked(ctx) }
click(binding.optionIcon) { vm.onIconClicked(ctx) }
click(binding.optionRelations) { vm.onRelationsClicked() }
click(binding.optionCover) { vm.onCoverClicked(ctx) }
binding.rvActions.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
@ -91,13 +72,12 @@ abstract class ObjectMenuBaseFragment : BaseBottomSheetFragment<FragmentObjectMe
}
override fun onStart() {
with(lifecycleScope) {
jobs += subscribe(vm.actions) { actionAdapter.submitList(it) }
jobs += subscribe(vm.toasts) { toast(it) }
jobs += subscribe(vm.isDismissed) { isDismissed -> if (isDismissed) dismiss() }
jobs += subscribe(vm.commands) { command -> execute(command) }
jobs += subscribe(vm.options) { options -> renderOptions(options) }
}
proceed(vm.actions) { actionAdapter.submitList(it) }
proceed(vm.toasts) { toast(it) }
proceed(vm.isDismissed) { isDismissed -> if (isDismissed) dismiss() }
proceed(vm.commands) { command -> execute(command) }
proceed(vm.options) { options -> renderOptions(options) }
super.onStart()
vm.onStart(
ctx = ctx,
@ -110,11 +90,13 @@ abstract class ObjectMenuBaseFragment : BaseBottomSheetFragment<FragmentObjectMe
// TODO refactor to recycler view
private fun renderOptions(options: ObjectMenuOptionsProvider.Options) {
val iconVisibility = if (options.hasIcon) View.VISIBLE else View.GONE
val coverVisibility = if (options.hasCover) View.VISIBLE else View.GONE
val layoutVisibility = if (options.hasLayout) View.VISIBLE else View.GONE
val relationsVisibility = if (options.hasRelations) View.VISIBLE else View.GONE
val historyVisibility = if (options.hasHistory) View.VISIBLE else View.GONE
val iconVisibility = options.hasIcon.toVisibility()
val coverVisibility = options.hasCover.toVisibility()
val layoutVisibility = options.hasLayout.toVisibility()
val relationsVisibility = options.hasRelations.toVisibility()
val historyVisibility = options.hasHistory.toVisibility()
val objectDiagnosticsVisibility = options.hasDiagnosticsVisibility.toVisibility()
binding.optionIcon.visibility = iconVisibility
binding.optionCover.visibility = coverVisibility
binding.optionLayout.visibility = layoutVisibility
@ -125,86 +107,104 @@ abstract class ObjectMenuBaseFragment : BaseBottomSheetFragment<FragmentObjectMe
binding.layoutDivider.visibility = layoutVisibility
binding.relationsDivider.visibility = relationsVisibility
binding.historyDivider.visibility = historyVisibility
binding.objectDiagnostics.visibility = objectDiagnosticsVisibility
binding.objectDiagnosticsDivider.visibility = objectDiagnosticsVisibility
}
private fun execute(command: ObjectMenuViewModelBase.Command) {
when (command) {
ObjectMenuViewModelBase.Command.OpenObjectCover -> {
findNavController().navigate(
R.id.objectCoverScreen,
bundleOf(SelectCoverObjectFragment.CTX_KEY to ctx)
)
}
ObjectMenuViewModelBase.Command.OpenObjectIcons -> {
findNavController().navigate(
R.id.objectIconPickerScreen,
bundleOf(
IconPickerFragmentBase.ARG_CONTEXT_ID_KEY to ctx,
)
)
}
ObjectMenuViewModelBase.Command.OpenObjectLayout -> {
val fr = ObjectLayoutFragment.new(ctx)
fr.show(childFragmentManager, null)
}
ObjectMenuViewModelBase.Command.OpenObjectRelations -> {
findNavController().navigate(
R.id.objectRelationListScreen,
bundleOf(
RelationListFragment.ARG_CTX to ctx,
RelationListFragment.ARG_TARGET to null,
RelationListFragment.ARG_LOCKED to isLocked,
RelationListFragment.ARG_MODE to RelationListFragment.MODE_LIST
)
)
}
ObjectMenuViewModelBase.Command.OpenSetCover -> {
findNavController().navigate(
R.id.objectSetCoverScreen,
bundleOf(SelectCoverObjectSetFragment.CTX_KEY to ctx)
)
}
ObjectMenuViewModelBase.Command.OpenSetIcons -> {
findNavController().navigate(
R.id.objectSetIconPickerScreen,
bundleOf(
IconPickerFragmentBase.ARG_CONTEXT_ID_KEY to ctx,
)
)
}
ObjectMenuViewModelBase.Command.OpenSetLayout -> {
toast(COMING_SOON_MSG)
}
ObjectMenuViewModelBase.Command.OpenSetRelations -> {
toast(COMING_SOON_MSG)
}
ObjectMenuViewModelBase.Command.OpenLinkToChooser -> {
val fr = MoveToFragment.new(
ctx = ctx,
blocks = emptyList(),
restorePosition = null,
restoreBlock = null,
title = getString(R.string.link_to)
)
fr.show(childFragmentManager, null)
}
is ObjectMenuViewModelBase.Command.OpenSnackbar -> {
binding.root.postDelayed({
dialog?.window
?.decorView
?.showActionableSnackBar(
command.currentObjectName,
command.targetObjectName,
command.icon,
binding.anchor
) {
vm.proceedWithOpeningPage(command.id)
}
}, 300L)
}
ObjectMenuViewModelBase.Command.OpenObjectCover -> openObjectCover()
ObjectMenuViewModelBase.Command.OpenObjectIcons -> openObjectIcons()
ObjectMenuViewModelBase.Command.OpenObjectLayout -> openObjectLayout()
ObjectMenuViewModelBase.Command.OpenObjectRelations -> openObjectRelations()
ObjectMenuViewModelBase.Command.OpenSetCover -> openSetCover()
ObjectMenuViewModelBase.Command.OpenSetIcons -> openSetIcons()
ObjectMenuViewModelBase.Command.OpenSetLayout -> toast(COMING_SOON_MSG)
ObjectMenuViewModelBase.Command.OpenSetRelations -> toast(COMING_SOON_MSG)
ObjectMenuViewModelBase.Command.OpenLinkToChooser -> openLinkChooser(command)
is ObjectMenuViewModelBase.Command.OpenSnackbar -> openSnackbar(command)
is ObjectMenuViewModelBase.Command.ShareDebugTree -> shareFile(command.uri)
}
}
private fun openObjectCover() {
findNavController().navigate(
R.id.objectCoverScreen,
bundleOf(SelectCoverObjectFragment.CTX_KEY to ctx)
)
}
private fun openObjectIcons() {
findNavController().navigate(
R.id.objectIconPickerScreen,
bundleOf(
IconPickerFragmentBase.ARG_CONTEXT_ID_KEY to ctx,
)
)
}
private fun openObjectLayout() {
val fr = ObjectLayoutFragment.new(ctx)
fr.show(childFragmentManager, null)
}
private fun openObjectRelations() {
findNavController().navigate(
R.id.objectRelationListScreen,
bundleOf(
RelationListFragment.ARG_CTX to ctx,
RelationListFragment.ARG_TARGET to null,
RelationListFragment.ARG_LOCKED to isLocked,
RelationListFragment.ARG_MODE to RelationListFragment.MODE_LIST
)
)
}
private fun openSetCover() {
findNavController().navigate(
R.id.objectSetCoverScreen,
bundleOf(SelectCoverObjectSetFragment.CTX_KEY to ctx)
)
}
private fun openSetIcons() {
findNavController().navigate(
R.id.objectSetIconPickerScreen,
bundleOf(
IconPickerFragmentBase.ARG_CONTEXT_ID_KEY to ctx,
)
)
}
private fun openLinkChooser(command: ObjectMenuViewModelBase.Command) {
val fr = MoveToFragment.new(
ctx = ctx,
blocks = emptyList(),
restorePosition = null,
restoreBlock = null,
title = getString(R.string.link_to)
)
fr.show(childFragmentManager, null)
}
private fun openSnackbar(command: ObjectMenuViewModelBase.Command.OpenSnackbar) {
binding.root.postDelayed({
dialog?.window
?.decorView
?.showActionableSnackBar(
command.currentObjectName,
command.targetObjectName,
command.icon,
binding.anchor
) {
vm.proceedWithOpeningPage(command.id)
}
}, 300L)
}
override fun onMoveTo(
target: Id,
blocks: List<Id>,
@ -243,4 +243,6 @@ abstract class ObjectMenuBaseFragment : BaseBottomSheetFragment<FragmentObjectMe
fun onLayoutClicked()
fun onUndoRedoClicked()
}
}
}
private fun Boolean.toVisibility() = if (this) View.VISIBLE else View.GONE

View file

@ -1,8 +1,5 @@
package com.anytypeio.anytype.ui.settings
import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@ -17,8 +14,8 @@ import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.anytypeio.anytype.R
import com.anytypeio.anytype.analytics.base.EventsDictionary
import com.anytypeio.anytype.core_utils.ext.shareFile
import com.anytypeio.anytype.core_utils.ext.subscribe
import com.anytypeio.anytype.core_utils.ext.toast
import com.anytypeio.anytype.core_utils.tools.FeatureToggles
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment
import com.anytypeio.anytype.di.common.componentManager
@ -27,7 +24,6 @@ import com.anytypeio.anytype.ui.dashboard.ClearCacheAlertFragment
import com.anytypeio.anytype.ui.profile.KeychainPhraseDialog
import com.anytypeio.anytype.ui_settings.account.AccountAndDataScreen
import com.anytypeio.anytype.ui_settings.account.AccountAndDataViewModel
import timber.log.Timber
import javax.inject.Inject
class AccountAndDataFragment : BaseBottomSheetComposeFragment() {
@ -80,24 +76,7 @@ class AccountAndDataFragment : BaseBottomSheetComposeFragment() {
}
}
private fun shareFile(uri: Uri) {
try {
val shareIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
putExtra(Intent.EXTRA_STREAM, uri)
type = requireContext().contentResolver.getType(uri)
}
startActivity(shareIntent)
} catch (e: Exception) {
if (e is ActivityNotFoundException) {
toast("No application found to open the selected file")
} else {
toast("Could not open file: ${e.message}")
}
Timber.e(e, "Error while opening file")
}
}
private fun proceedWithClearFileCacheWarning() {
vm.onClearCacheButtonClicked()

View file

@ -1,5 +1,6 @@
package com.anytypeio.anytype.ui.settings
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@ -13,11 +14,13 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_utils.tools.FeatureToggles
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment
import com.anytypeio.anytype.di.common.componentManager
import com.anytypeio.anytype.presentation.settings.MainSettingsViewModel
import com.anytypeio.anytype.presentation.settings.MainSettingsViewModel.Event
import com.anytypeio.anytype.presentation.settings.MainSettingsViewModel.Command
import com.anytypeio.anytype.ui.settings.system.SettingsActivity
import com.anytypeio.anytype.ui_settings.main.MainSettingScreen
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
@ -28,6 +31,9 @@ class MainSettingFragment : BaseBottomSheetComposeFragment() {
@Inject
lateinit var factory: MainSettingsViewModel.Factory
@Inject
lateinit var featureToggles: FeatureToggles
private val vm by viewModels<MainSettingsViewModel> { factory }
private val onAccountAndDataClicked = {
@ -46,6 +52,10 @@ class MainSettingFragment : BaseBottomSheetComposeFragment() {
vm.onOptionClicked(Event.OnAppearanceClicked)
}
private val onDebugClicked = {
vm.onOptionClicked(Event.OnDebugClicked)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -59,7 +69,9 @@ class MainSettingFragment : BaseBottomSheetComposeFragment() {
onAccountAndDataClicked = onAccountAndDataClicked,
onAboutAppClicked = onAboutAppClicked,
onAppearanceClicked = onAppearanceClicked,
onPersonalizationClicked = onPersonalizationClicked
onDebugClicked = onDebugClicked,
onPersonalizationClicked = onPersonalizationClicked,
showDebugMenu = featureToggles.isDebug
)
}
}
@ -89,12 +101,16 @@ class MainSettingFragment : BaseBottomSheetComposeFragment() {
Command.OpenPersonalizationScreen -> {
findNavController().navigate(R.id.actionOpenPersonalizationScreen)
}
Command.OpenDebugScreen -> {
startActivity(Intent(requireActivity(), SettingsActivity::class.java))
}
}
}
override fun injectDependencies() {
componentManager().mainSettingsComponent.get().inject(this)
}
override fun releaseDependencies() {
componentManager().mainSettingsComponent.release()
}

View file

@ -137,6 +137,29 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/optionHistory" />
<com.anytypeio.anytype.core_ui.widgets.ObjectMenuItemWidget
android:id="@+id/objectDiagnostics"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/default_ripple"
app:icon="@drawable/ic_object_menu_diagnostics"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/historyDivider"
app:subtitle="@string/object_debug"
app:title="@string/object_diagnostics" />
<View
android:id="@+id/objectDiagnosticsDivider"
android:layout_width="0dp"
android:layout_height="0.5dp"
android:layout_marginTop="21dp"
android:background="@color/shape_primary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/objectDiagnostics" />
<FrameLayout
android:id="@+id/rvContainer"
android:layout_width="match_parent"
@ -144,7 +167,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/historyDivider">
app:layout_constraintTop_toBottomOf="@+id/objectDiagnosticsDivider">
<androidx.recyclerview.widget.RecyclerView
android:layout_gravity="center_vertical"

View file

@ -233,6 +233,8 @@ Do the computation of an expensive paragraph of text on a background thread:
<string name="relations_description">List of related objects</string>
<string name="history_description">All version of object</string>
<string name="history">History</string>
<string name="object_debug">Object Debug</string>
<string name="object_diagnostics">Diagnostics</string>
<string name="provide_name">Provide name for new object</string>
<string name="wait_a_bit_message">Please give us a moment. Were almost there…</string>
<string name="soon">Soon</string>

View file

@ -53,6 +53,7 @@ dependencies {
implementation applicationDependencies.kotlin
implementation applicationDependencies.coroutinesAndroid
implementation applicationDependencies.androidxCore
implementation applicationDependencies.lifecycleRuntime
implementation applicationDependencies.design
implementation applicationDependencies.recyclerView

View file

@ -1,16 +1,27 @@
package com.anytypeio.anytype.core_ui.reactive
import android.os.Looper
import android.os.Parcelable
import android.text.Editable
import android.text.TextWatcher
import android.view.MotionEvent
import android.view.View
import android.widget.EditText
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.anytypeio.anytype.core_utils.ext.throttleFirst
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetFragment
import com.anytypeio.anytype.core_utils.ui.BaseFragment
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
fun View.clicks(): Flow<Unit> = callbackFlow {
checkMainThread()
@ -42,20 +53,21 @@ fun EditText.focusChanges(): Flow<Boolean> = callbackFlow {
}.conflate()
fun View.touches(handled: (MotionEvent) -> Boolean = { true }): Flow<MotionEvent> = callbackFlow<MotionEvent> {
checkMainThread()
val listener = View.OnTouchListener { _, event ->
performClick()
if (handled(event)) {
trySend(event)
true
} else {
false
fun View.touches(handled: (MotionEvent) -> Boolean = { true }): Flow<MotionEvent> =
callbackFlow<MotionEvent> {
checkMainThread()
val listener = View.OnTouchListener { _, event ->
performClick()
if (handled(event)) {
trySend(event)
true
} else {
false
}
}
}
setOnTouchListener(listener)
awaitClose { setOnTouchListener(null) }
}.conflate()
setOnTouchListener(listener)
awaitClose { setOnTouchListener(null) }
}.conflate()
fun EditText.afterTextChanges(): Flow<CharSequence> = callbackFlow<CharSequence> {
checkMainThread()
@ -95,4 +107,32 @@ fun TextView.editorActionEvents(
fun checkMainThread() = check(Looper.myLooper() == Looper.getMainLooper()) {
"Expected to be called on the main thread but was " + Thread.currentThread().name
}
fun BaseFragment<*>.click(
view: View,
action: () -> Unit
) {
jobs += view.clicks()
.throttleFirst()
.onEach { action() }
.launchIn(lifecycleScope)
}
fun BaseBottomSheetFragment<*>.click(
view: View,
action: () -> Unit
) {
jobs += view.clicks()
.throttleFirst()
.onEach { action() }
.launchIn(lifecycleScope)
}
fun <T> BaseFragment<*>.proceed(flow: Flow<T>, body: suspend (T) -> Unit) {
jobs += flow.onEach { body(it) }.launchIn(lifecycleScope)
}
fun <T> BaseBottomSheetFragment<*>.proceed(flow: Flow<T>, body: suspend (T) -> Unit) {
jobs += flow.onEach { body(it) }.launchIn(lifecycleScope)
}

View file

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="44dp"
android:height="44dp"
android:viewportWidth="44"
android:viewportHeight="44">
<path
android:pathData="M22,0L22,0A22,22 0,0 1,44 22L44,22A22,22 0,0 1,22 44L22,44A22,22 0,0 1,0 22L0,22A22,22 0,0 1,22 0z"
android:strokeAlpha="0.2"
android:fillColor="@color/turquoise"
android:fillAlpha="0.2"/>
<path
android:pathData="M24.76,34C24.517,34 24.306,33.823 24.247,33.569L19.37,12.889L17.189,22.14C17.129,22.394 16.917,22.571 16.675,22.571L10.53,22.571C10.237,22.571 10,22.315 10,22C10,21.684 10.237,21.428 10.53,21.428H16.263L18.857,10.431C18.917,10.177 19.128,10 19.37,10C19.612,10 19.824,10.177 19.883,10.431L24.76,31.111L26.941,21.86C27.001,21.606 27.212,21.429 27.454,21.429H33.47C33.763,21.429 34,21.685 34,22C34,22.316 33.763,22.572 33.47,22.572L27.866,22.572L25.273,33.569C25.213,33.823 25.002,34 24.76,34H24.76Z"
android:strokeWidth="0.6"
android:fillColor="@color/turquoise"
android:strokeColor="@color/turquoise"/>
</vector>

View file

@ -21,6 +21,8 @@
<color name="chapter_yellow">#ECD91B</color>
<color name="default_toolbar_option_stroke_color">#DFDDD0</color>
<color name="turquoise">#0FC8BA</color>
<color name="toolbar_block_turn_into_toggle_selected">#2C2B27</color>
<color name="toolbar_block_turn_into_toggle_default">#ACA996</color>

View file

@ -2,6 +2,7 @@ package com.anytypeio.anytype.core_utils.ext
import android.Manifest
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
@ -353,4 +354,23 @@ fun NavController.safeNavigate(
if (currentDestinationId == currentDestination?.id) {
navigate(id, args)
}
}
fun Fragment.shareFile(uri: Uri) {
try {
val shareIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
putExtra(Intent.EXTRA_STREAM, uri)
type = requireContext().contentResolver.getType(uri)
}
startActivity(shareIntent)
} catch (e: Exception) {
if (e is ActivityNotFoundException) {
toast("No application found to open the selected file")
} else {
toast("Could not open file: ${e.message}")
}
Timber.e(e, "Error while opening file")
}
}

View file

@ -21,7 +21,7 @@ abstract class BaseBottomSheetFragment<T : ViewBinding>(
val sheet: FrameLayout? get() = dialog?.findViewById(BOTTOM_SHEET_ID)
protected val jobs = mutableListOf<Job>()
val jobs = mutableListOf<Job>()
override fun onCreateView(
inflater: LayoutInflater,

View file

@ -25,7 +25,7 @@ abstract class BaseFragment<T : ViewBinding>(
val binding: T get() = _binding!!
val hasBinding get() = _binding != null
protected val jobs = mutableListOf<Job>()
val jobs = mutableListOf<Job>()
abstract fun injectDependencies()
abstract fun releaseDependencies()

View file

@ -581,7 +581,7 @@ class MiddlewareServiceImplementation @Inject constructor(
}
override fun debugTree(request: Rpc.Debug.Tree.Request): Rpc.Debug.Tree.Response {
val encoded = Service.debugSync(Rpc.Debug.Tree.Request.ADAPTER.encode(request))
val encoded = Service.debugTree(Rpc.Debug.Tree.Request.ADAPTER.encode(request))
val response = Rpc.Debug.Tree.Response.ADAPTER.decode(encoded)
val error = response.error
if (error != null && error.code != Rpc.Debug.Tree.Response.Error.Code.NULL) {

View file

@ -51,6 +51,7 @@ import com.anytypeio.anytype.presentation.extension.sendAnalyticsReorderBlockEve
import com.anytypeio.anytype.presentation.extension.sendAnalyticsSplitBlockEvent
import com.anytypeio.anytype.presentation.extension.sendAnalyticsUndoEvent
import com.anytypeio.anytype.presentation.extension.sendAnalyticsUploadMediaEvent
import com.anytypeio.anytype.presentation.util.downloader.DocumentFileShareDownloader
import com.anytypeio.anytype.presentation.util.downloader.MiddlewareShareDownloader
import timber.log.Timber
@ -68,7 +69,7 @@ class Orchestrator(
private val turnIntoStyle: TurnIntoStyle,
private val updateCheckbox: UpdateCheckbox,
private val downloadFile: DownloadFile,
private val middlewareShareDownloader: MiddlewareShareDownloader,
private val documentFileShareDownloader: DocumentFileShareDownloader,
val updateText: UpdateText,
private val updateAlignment: UpdateAlignment,
private val uploadBlock: UploadBlock,
@ -442,7 +443,7 @@ class Orchestrator(
)
}
is Intent.Media.ShareFile -> {
middlewareShareDownloader.execute(
documentFileShareDownloader.execute(
params = MiddlewareShareDownloader.Params(
hash = intent.hash,
name = intent.name

View file

@ -10,6 +10,7 @@ interface ObjectMenuOptionsProvider {
val hasCover: Boolean,
val hasLayout: Boolean,
val hasRelations: Boolean,
val hasDiagnosticsVisibility: Boolean,
) {
val hasHistory: Boolean = false
companion object {
@ -18,6 +19,7 @@ interface ObjectMenuOptionsProvider {
hasCover = true,
hasLayout = true,
hasRelations = true,
hasDiagnosticsVisibility = true
)
}
}

View file

@ -5,6 +5,7 @@ import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.ObjectWrapper
import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction
import com.anytypeio.anytype.core_utils.tools.FeatureToggles
import com.anytypeio.anytype.presentation.objects.menu.ObjectMenuOptionsProvider.Options
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@ -15,6 +16,7 @@ import timber.log.Timber
class ObjectMenuOptionsProviderImpl(
private val details: Flow<Map<Id, Block.Fields>>,
private val restrictions: Flow<List<ObjectRestriction>>,
private val featureToggles: FeatureToggles,
) : ObjectMenuOptionsProvider {
private fun observeLayout(ctx: Id): Flow<ObjectType.Layout?> = details
@ -58,12 +60,14 @@ class ObjectMenuOptionsProviderImpl(
hasIcon = hasIcon,
hasCover = hasCover,
hasLayout = hasLayout,
hasDiagnosticsVisibility = featureToggles.isDebug,
)
ObjectType.Layout.TODO -> Options(
hasIcon = false,
hasCover = hasCover,
hasLayout = hasLayout,
hasRelations = true,
hasDiagnosticsVisibility = featureToggles.isDebug,
)
ObjectType.Layout.NOTE -> Options(
@ -71,6 +75,7 @@ class ObjectMenuOptionsProviderImpl(
hasCover = false,
hasLayout = hasLayout,
hasRelations = true,
hasDiagnosticsVisibility = featureToggles.isDebug,
)
}
} else {
@ -79,6 +84,7 @@ class ObjectMenuOptionsProviderImpl(
hasIcon = hasIcon,
hasCover = hasCover,
hasLayout = hasLayout,
hasDiagnosticsVisibility = featureToggles.isDebug,
)
}
return options

View file

@ -11,6 +11,7 @@ import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction
import com.anytypeio.anytype.domain.`object`.DuplicateObject
import com.anytypeio.anytype.domain.base.fold
import com.anytypeio.anytype.domain.block.interactor.UpdateFields
import com.anytypeio.anytype.domain.dashboard.interactor.AddToFavorite
import com.anytypeio.anytype.domain.dashboard.interactor.RemoveFromFavorite
@ -22,6 +23,8 @@ import com.anytypeio.anytype.presentation.common.Delegator
import com.anytypeio.anytype.presentation.editor.Editor
import com.anytypeio.anytype.presentation.objects.ObjectAction
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.presentation.util.downloader.DebugTreeShareDownloader
import com.anytypeio.anytype.presentation.util.downloader.MiddlewareShareDownloader
import kotlinx.coroutines.launch
import timber.log.Timber
@ -35,6 +38,7 @@ class ObjectMenuViewModel(
dispatcher: Dispatcher<Payload>,
menuOptionsProvider: ObjectMenuOptionsProvider,
duplicateObject: DuplicateObject,
private val debugTreeShareDownloader: DebugTreeShareDownloader,
private val storage: Editor.Storage,
private val analytics: Analytics,
private val updateFields: UpdateFields
@ -88,6 +92,30 @@ class ObjectMenuViewModel(
add(ObjectAction.SEARCH_ON_PAGE)
}
override fun onDiagnosticsClicked(ctx: Id) {
jobs += viewModelScope.launch {
debugTreeShareDownloader.stream(
MiddlewareShareDownloader.Params(hash = ctx, name = "$ctx.zip")
).collect { result ->
result.fold(
onSuccess = { uri ->
commands.emit(Command.ShareDebugTree(uri))
},
onLoading = {
sendToast(
"Do not go away from this menu and don't turn the screen off. " +
"Tree diagnostic is started to collect."
)
},
onFailure = {
sendToast("Error while collecting tree diagnostics")
Timber.e(it, "Error while adding link from object to object")
}
)
}
}
}
override fun onIconClicked(ctx: Id) {
viewModelScope.launch {
if (objectRestrictions.contains(ObjectRestriction.DETAILS)) {
@ -255,6 +283,7 @@ class ObjectMenuViewModel(
class Factory(
private val setObjectIsArchived: SetObjectIsArchived,
private val duplicateObject: DuplicateObject,
private val debugTreeShareDownloader: DebugTreeShareDownloader,
private val addToFavorite: AddToFavorite,
private val removeFromFavorite: RemoveFromFavorite,
private val addBackLinkToObject: AddBackLinkToObject,
@ -270,6 +299,7 @@ class ObjectMenuViewModel(
return ObjectMenuViewModel(
setObjectIsArchived = setObjectIsArchived,
duplicateObject = duplicateObject,
debugTreeShareDownloader = debugTreeShareDownloader,
addToFavorite = addToFavorite,
removeFromFavorite = removeFromFavorite,
addBackLinkToObject = addBackLinkToObject,

View file

@ -1,5 +1,6 @@
package com.anytypeio.anytype.presentation.objects.menu
import android.net.Uri
import androidx.lifecycle.viewModelScope
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.core_models.Id
@ -42,7 +43,7 @@ abstract class ObjectMenuViewModelBase(
private val duplicateObject: DuplicateObject
) : BaseViewModel() {
private val jobs = mutableListOf<Job>()
protected val jobs = mutableListOf<Job>()
val isDismissed = MutableStateFlow(false)
val isObjectArchived = MutableStateFlow(false)
val commands = MutableSharedFlow<Command>(replay = 0)
@ -54,6 +55,7 @@ abstract class ObjectMenuViewModelBase(
hasCover = false,
hasLayout = false,
hasRelations = false,
hasDiagnosticsVisibility = false
)
)
val options: Flow<ObjectMenuOptionsProvider.Options> = _options
@ -100,7 +102,7 @@ abstract class ObjectMenuViewModelBase(
): List<ObjectAction>
protected fun proceedWithRemovingFromFavorites(ctx: Id) {
viewModelScope.launch {
jobs += viewModelScope.launch {
removeFromFavorite(
RemoveFromFavorite.Params(
target = ctx
@ -119,7 +121,7 @@ abstract class ObjectMenuViewModelBase(
}
protected fun proceedWithAddingToFavorites(ctx: Id) {
viewModelScope.launch {
jobs += viewModelScope.launch {
addToFavorite(
AddToFavorite.Params(
target = ctx
@ -138,7 +140,7 @@ abstract class ObjectMenuViewModelBase(
}
fun proceedWithUpdatingArchivedStatus(ctx: Id, isArchived: Boolean) {
viewModelScope.launch {
jobs += viewModelScope.launch {
setObjectIsArchived(
SetObjectIsArchived.Params(
context = ctx,
@ -169,11 +171,14 @@ abstract class ObjectMenuViewModelBase(
).fold(
onSuccess = { obj ->
sendAnalyticsObjectLinkToEvent(analytics)
commands.emit(Command.OpenSnackbar(
id = addTo,
currentObjectName = fromName,
targetObjectName = obj.getProperName(),
icon = ObjectIcon.from(obj, obj.layout, urlBuilder)))
commands.emit(
Command.OpenSnackbar(
id = addTo,
currentObjectName = fromName,
targetObjectName = obj.getProperName(),
icon = ObjectIcon.from(obj, obj.layout, urlBuilder)
)
)
},
onFailure = {
Timber.e(it, "Error while adding link from object to object")
@ -207,6 +212,10 @@ abstract class ObjectMenuViewModelBase(
}
}
open fun onDiagnosticsClicked(ctx: Id) {
throw IllegalStateException("You should not call diagnostics for sets")
}
sealed class Command {
object OpenObjectIcons : Command()
object OpenSetIcons : Command()
@ -217,7 +226,8 @@ abstract class ObjectMenuViewModelBase(
object OpenObjectRelations : Command()
object OpenSetRelations : Command()
object OpenLinkToChooser : Command()
class OpenSnackbar(
data class ShareDebugTree(val uri: Uri) : Command()
data class OpenSnackbar(
val id: Id,
val currentObjectName: String?,
val targetObjectName: String?,

View file

@ -38,6 +38,7 @@ class MainSettingsViewModel(
Event.OnAccountAndDataClicked -> commands.emit(Command.OpenAccountAndDataScreen)
Event.OnAppearanceClicked -> commands.emit(Command.OpenAppearanceScreen)
Event.OnPersonalizationClicked -> commands.emit(Command.OpenPersonalizationScreen)
Event.OnDebugClicked -> commands.emit(Command.OpenDebugScreen)
}
}
@ -67,6 +68,7 @@ class MainSettingsViewModel(
eventName = EventsDictionary.personalisationSettingsShow
)
}
Event.OnDebugClicked -> {}
}
}
@ -83,15 +85,17 @@ class MainSettingsViewModel(
sealed class Event {
object OnAboutClicked : Event()
object OnAppearanceClicked: Event()
object OnAccountAndDataClicked: Event()
object OnPersonalizationClicked: Event()
object OnAppearanceClicked : Event()
object OnAccountAndDataClicked : Event()
object OnPersonalizationClicked : Event()
object OnDebugClicked : Event()
}
sealed class Command {
object OpenAboutScreen : Command()
object OpenAppearanceScreen: Command()
object OpenAccountAndDataScreen: Command()
object OpenPersonalizationScreen: Command()
object OpenAppearanceScreen : Command()
object OpenAccountAndDataScreen : Command()
object OpenPersonalizationScreen : Command()
object OpenDebugScreen : Command()
}
}

View file

@ -0,0 +1,14 @@
package com.anytypeio.anytype.presentation.util.downloader
import android.content.Context
import com.anytypeio.anytype.domain.block.repo.BlockRepository
class DebugTreeShareDownloader(
private val repo: BlockRepository,
context: Context,
uriFileProvider: UriFileProvider
) : MiddlewareShareDownloader(context, uriFileProvider) {
override suspend fun downloadFile(hash: String, path: String) =
repo.debugTree(objectId = hash, path = path)
}

View file

@ -0,0 +1,16 @@
package com.anytypeio.anytype.presentation.util.downloader
import android.content.Context
import com.anytypeio.anytype.core_models.Command
import com.anytypeio.anytype.domain.block.repo.BlockRepository
class DocumentFileShareDownloader(
private val repo: BlockRepository,
context: Context,
uriFileProvider: UriFileProvider
) : MiddlewareShareDownloader(context, uriFileProvider) {
override suspend fun downloadFile(hash: String, path: String) = repo.downloadFile(
Command.DownloadFile(hash = hash, path = path)
)
}

View file

@ -2,17 +2,11 @@ package com.anytypeio.anytype.presentation.util.downloader
import android.content.Context
import android.net.Uri
import com.anytypeio.anytype.core_models.Command
import com.anytypeio.anytype.core_models.Hash
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.base.ResultInteractor
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import kotlinx.coroutines.withContext
import java.io.File
class MiddlewareShareDownloader(
private val repo: BlockRepository,
private val dispatchers: AppCoroutineDispatchers,
abstract class MiddlewareShareDownloader(
private val context: Context,
private val uriFileProvider: UriFileProvider
) : ResultInteractor<MiddlewareShareDownloader.Params, Uri>() {
@ -22,7 +16,9 @@ class MiddlewareShareDownloader(
val name: String
)
override suspend fun doWork(params: Params) = withContext(dispatchers.io) {
abstract suspend fun downloadFile(hash: String, path: String): String
override suspend fun doWork(params: Params): Uri {
val cacheDir = context.cacheDir
require(cacheDir != null) { "Impossible to cache files!" }
@ -38,17 +34,11 @@ class MiddlewareShareDownloader(
if (tempDir.exists()) tempDir.deleteRecursively()
tempDir.mkdirs()
val tempResult = File(
repo.downloadFile(
Command.DownloadFile(
hash = params.hash,
path = tempFileFolderPath
)
)
)
val tempResult = File(downloadFile(params.hash, tempFileFolderPath))
tempResult.renameTo(resultFile)
}
uriFileProvider.getUriForFile(resultFile)
return uriFileProvider.getUriForFile(resultFile)
}
}
}

View file

@ -13,6 +13,7 @@ import com.anytypeio.anytype.core_models.ext.getChildrenIdsList
import com.anytypeio.anytype.domain.`object`.ObjectTypesProvider
import com.anytypeio.anytype.domain.auth.interactor.GetProfile
import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.domain.base.Resultat
import com.anytypeio.anytype.domain.block.interactor.Move
import com.anytypeio.anytype.domain.config.DebugSettings
import com.anytypeio.anytype.domain.config.Gateway
@ -35,7 +36,6 @@ import com.anytypeio.anytype.presentation.MockBlockFactory.link
import com.anytypeio.anytype.presentation.navigation.AppNavigation
import com.anytypeio.anytype.presentation.util.CoroutinesTestRule
import com.anytypeio.anytype.test_utils.MockDataFactory
import com.anytypeio.anytype.test_utils.ValueClassAnswer
import com.jraska.livedata.test
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
@ -372,7 +372,7 @@ class HomeDashboardViewModelTest {
private fun givenDelegateId(id: String) {
createNewObject.stub {
onBlocking { execute(Unit) } doAnswer ValueClassAnswer(id)
onBlocking { execute(Unit) } doReturn Resultat.success(id)
}
}

View file

@ -25,6 +25,7 @@ import com.anytypeio.anytype.domain.`object`.ConvertObjectToSet
import com.anytypeio.anytype.domain.`object`.UpdateDetail
import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.domain.base.Result
import com.anytypeio.anytype.domain.base.Resultat
import com.anytypeio.anytype.domain.block.UpdateDivider
import com.anytypeio.anytype.domain.block.interactor.ClearBlockContent
import com.anytypeio.anytype.domain.block.interactor.ClearBlockStyle
@ -117,9 +118,9 @@ import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory
import com.anytypeio.anytype.presentation.util.CoroutinesTestRule
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.presentation.util.TXT
import com.anytypeio.anytype.presentation.util.downloader.DocumentFileShareDownloader
import com.anytypeio.anytype.presentation.util.downloader.MiddlewareShareDownloader
import com.anytypeio.anytype.test_utils.MockDataFactory
import com.anytypeio.anytype.test_utils.ValueClassAnswer
import com.jraska.livedata.test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
@ -233,7 +234,7 @@ open class EditorViewModelTest {
lateinit var downloadFile: DownloadFile
@Mock
lateinit var middlewareShareDownloader: MiddlewareShareDownloader
lateinit var documentFileShareDownloader: DocumentFileShareDownloader
@Mock
lateinit var uploadBlock: UploadBlock
@ -2665,7 +2666,7 @@ open class EditorViewModelTest {
vm.startSharingFile(id = file.id)
runTest {
verify(middlewareShareDownloader, times(1)).execute(
verify(documentFileShareDownloader, times(1)).execute(
params = eq(
MiddlewareShareDownloader.Params(
name = file.content<Block.Content.File>().name.orEmpty(),
@ -3743,7 +3744,7 @@ open class EditorViewModelTest {
objectRestrictions: List<ObjectRestriction> = emptyList()
) {
openPage.stub {
onBlocking { execute(any()) } doAnswer ValueClassAnswer(
onBlocking { execute(any()) } doReturn Resultat.success(
Result.Success(
Payload(
context = root,
@ -3768,7 +3769,7 @@ open class EditorViewModelTest {
) {
closePage.stub {
onBlocking { execute(any()) } doAnswer ValueClassAnswer(Unit)
onBlocking { execute(any()) } doReturn Resultat.success(Unit)
}
exception?.let {
@ -3797,7 +3798,7 @@ open class EditorViewModelTest {
events: List<Event> = emptyList()
) {
openPage.stub {
onBlocking { execute(any()) } doAnswer ValueClassAnswer(
onBlocking { execute(any()) } doReturn Resultat.success(
Result.Success(
Payload(
context = context,
@ -3857,7 +3858,7 @@ open class EditorViewModelTest {
private fun stubCreateBlock(root: String) {
createBlock.stub {
onBlocking { execute(any()) } doAnswer ValueClassAnswer(
onBlocking { execute(any()) } doReturn Resultat.success(
Pair(
MockDataFactory.randomString(), Payload(
context = root,
@ -3881,8 +3882,8 @@ open class EditorViewModelTest {
}
private fun givenSharedFile() {
middlewareShareDownloader.stub {
onBlocking { execute(any()) } doAnswer ValueClassAnswer(Uri.EMPTY)
documentFileShareDownloader.stub {
onBlocking { execute(any()) } doReturn Resultat.success(Uri.EMPTY)
}
}
@ -3967,7 +3968,7 @@ open class EditorViewModelTest {
updateTextColor = updateTextColor,
duplicateBlock = duplicateBlock,
downloadFile = downloadFile,
middlewareShareDownloader = middlewareShareDownloader,
documentFileShareDownloader = documentFileShareDownloader,
undo = undo,
redo = redo,
updateText = updateText,
@ -4523,7 +4524,7 @@ open class EditorViewModelTest {
private fun givenDelegateId(id: String) {
createNewObject.stub {
onBlocking { execute(Unit) } doAnswer ValueClassAnswer(id)
onBlocking { execute(Unit) } doReturn Resultat.success(id)
}
}
}

View file

@ -10,6 +10,7 @@ import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_models.ext.content
import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.domain.base.Result
import com.anytypeio.anytype.domain.base.Resultat
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
import com.anytypeio.anytype.domain.icon.DocumentEmojiIconProvider
import com.anytypeio.anytype.domain.page.CreateNewDocument
@ -23,7 +24,6 @@ import com.anytypeio.anytype.presentation.editor.render.parseThemeBackgroundColo
import com.anytypeio.anytype.presentation.util.CoroutinesTestRule
import com.anytypeio.anytype.presentation.util.TXT
import com.anytypeio.anytype.test_utils.MockDataFactory
import com.anytypeio.anytype.test_utils.ValueClassAnswer
import com.jraska.livedata.test
import kotlinx.coroutines.runBlocking
import net.lachlanmckee.timberjunit.TimberTestRule
@ -875,29 +875,28 @@ class EditorMentionTest : EditorPresentationTestSetup() {
val params = InterceptEvents.Params(context = root)
openPage.stub {
onBlocking { execute(any()) } doAnswer
ValueClassAnswer(
Result.Success(
Payload(
onBlocking { execute(any()) } doReturn Resultat.success(
Result.Success(
Payload(
context = root,
events = listOf(
Event.Command.ShowObject(
context = root,
events = listOf(
Event.Command.ShowObject(
context = root,
root = root,
details = Block.Details(),
relations = emptyList(),
blocks = document,
objectRestrictions = emptyList()
),
Event.Command.Details.Amend(
context = root,
target = mentionTarget,
details = mapOf(Block.Fields.NAME_KEY to "Foob")
)
)
root = root,
details = Block.Details(),
relations = emptyList(),
blocks = document,
objectRestrictions = emptyList()
),
Event.Command.Details.Amend(
context = root,
target = mentionTarget,
details = mapOf(Block.Fields.NAME_KEY to "Foob")
)
)
)
)
)
}
stubInterceptEvents()
stubSearchObjects()
@ -1023,29 +1022,28 @@ class EditorMentionTest : EditorPresentationTestSetup() {
val params = InterceptEvents.Params(context = root)
openPage.stub {
onBlocking { execute(any()) } doAnswer
ValueClassAnswer(
Result.Success(
Payload(
onBlocking { execute(any()) } doReturn Resultat.success(
Result.Success(
Payload(
context = root,
events = listOf(
Event.Command.ShowObject(
context = root,
events = listOf(
Event.Command.ShowObject(
context = root,
root = root,
details = Block.Details(),
relations = emptyList(),
blocks = document,
objectRestrictions = emptyList()
),
Event.Command.Details.Amend(
context = root,
target = mentionTarget,
details = mapOf(Block.Fields.NAME_KEY to "")
)
)
root = root,
details = Block.Details(),
relations = emptyList(),
blocks = document,
objectRestrictions = emptyList()
),
Event.Command.Details.Amend(
context = root,
target = mentionTarget,
details = mapOf(Block.Fields.NAME_KEY to "")
)
)
)
)
)
}
stubInterceptEvents()
stubSearchObjects()

View file

@ -14,6 +14,7 @@ import com.anytypeio.anytype.domain.`object`.ObjectTypesProvider
import com.anytypeio.anytype.domain.`object`.UpdateDetail
import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.domain.base.Result
import com.anytypeio.anytype.domain.base.Resultat
import com.anytypeio.anytype.domain.block.UpdateDivider
import com.anytypeio.anytype.domain.block.interactor.ClearBlockContent
import com.anytypeio.anytype.domain.block.interactor.ClearBlockStyle
@ -99,9 +100,8 @@ import com.anytypeio.anytype.presentation.editor.template.EditorTemplateDelegate
import com.anytypeio.anytype.presentation.editor.toggle.ToggleStateHolder
import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.presentation.util.downloader.MiddlewareShareDownloader
import com.anytypeio.anytype.presentation.util.downloader.DocumentFileShareDownloader
import com.anytypeio.anytype.test_utils.MockDataFactory
import com.anytypeio.anytype.test_utils.ValueClassAnswer
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flowOf
@ -183,7 +183,7 @@ open class EditorPresentationTestSetup {
lateinit var downloadFile: DownloadFile
@Mock
lateinit var middlewareShareDownloader: MiddlewareShareDownloader
lateinit var documentFileShareDownloader: DocumentFileShareDownloader
@Mock
lateinit var uploadBlock: UploadBlock
@ -296,22 +296,31 @@ open class EditorPresentationTestSetup {
@Mock
lateinit var createTableColumn: CreateTableColumn
@Mock
lateinit var createTableRow: CreateTableRow
@Mock
lateinit var deleteTableColumn: DeleteTableColumn
@Mock
lateinit var deleteTableRow: DeleteTableRow
@Mock
lateinit var duplicateTableRow: DuplicateTableRow
@Mock
lateinit var duplicateTableColumn: DuplicateTableColumn
@Mock
lateinit var fillTableColumn: FillTableColumn
@Mock
lateinit var moveTableRow: MoveTableRow
@Mock
lateinit var moveTableColumn: MoveTableColumn
@Mock
lateinit var setTableRowHeader: SetTableRowHeader
@ -347,7 +356,7 @@ open class EditorPresentationTestSetup {
updateTextColor = updateTextColor,
duplicateBlock = duplicateBlock,
downloadFile = downloadFile,
middlewareShareDownloader = middlewareShareDownloader,
documentFileShareDownloader = documentFileShareDownloader,
undo = undo,
redo = redo,
updateText = updateText,
@ -445,25 +454,24 @@ open class EditorPresentationTestSetup {
relationLinks: List<RelationLink> = emptyList()
) {
openPage.stub {
onBlocking { execute(any()) } doAnswer
ValueClassAnswer(
Result.Success(
Payload(
onBlocking { execute(any()) } doReturn Resultat.success(
Result.Success(
Payload(
context = root,
events = listOf(
Event.Command.ShowObject(
context = root,
events = listOf(
Event.Command.ShowObject(
context = root,
root = root,
details = details,
relations = relations,
relationLinks = relationLinks,
blocks = document,
objectRestrictions = objectRestrictions
)
)
root = root,
details = details,
relations = relations,
relationLinks = relationLinks,
blocks = document,
objectRestrictions = objectRestrictions
)
)
)
)
)
}
}
@ -547,15 +555,14 @@ open class EditorPresentationTestSetup {
fun stubCreateBlock(root: String) {
createBlock.stub {
onBlocking { execute(any()) } doAnswer
ValueClassAnswer(
Pair(
MockDataFactory.randomString(), Payload(
context = root,
events = listOf()
)
)
onBlocking { execute(any()) } doReturn Resultat.success(
Pair(
MockDataFactory.randomString(), Payload(
context = root,
events = listOf()
)
)
)
}
}
@ -620,7 +627,7 @@ open class EditorPresentationTestSetup {
fun stubClosePage() {
closePage.stub {
onBlocking { execute(any()) } doAnswer ValueClassAnswer(Unit)
onBlocking { execute(any()) } doReturn Resultat.success(Unit)
}
}
@ -671,7 +678,7 @@ open class EditorPresentationTestSetup {
}
}
fun stubSearchObjects(objects : List<ObjectWrapper.Basic> = emptyList()) {
fun stubSearchObjects(objects: List<ObjectWrapper.Basic> = emptyList()) {
searchObjects.stub {
onBlocking { invoke(any()) } doReturn Either.Right(objects)
}
@ -679,7 +686,7 @@ open class EditorPresentationTestSetup {
fun stubGetDefaultObjectType(type: String? = null, name: String? = null) {
getDefaultEditorType.stub {
onBlocking { execute(Unit) } doAnswer ValueClassAnswer(
onBlocking { execute(Unit) } doReturn Resultat.success(
GetDefaultEditorType.Response(
type,
name

View file

@ -5,12 +5,12 @@ import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_models.Position
import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.domain.base.Resultat
import com.anytypeio.anytype.domain.block.interactor.CreateBlock
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
import com.anytypeio.anytype.domain.page.CreateDocument
import com.anytypeio.anytype.presentation.util.CoroutinesTestRule
import com.anytypeio.anytype.test_utils.MockDataFactory
import com.anytypeio.anytype.test_utils.ValueClassAnswer
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@ -541,16 +541,15 @@ class EditorTitleAddBlockTest : EditorPresentationTestSetup() {
params: CreateBlock.Params
) {
createBlock.stub {
onBlocking { execute(params) } doAnswer
ValueClassAnswer(
Pair(
MockDataFactory.randomUuid(),
Payload(
context = root,
events = emptyList()
)
)
onBlocking { execute(params) } doReturn Resultat.success(
Pair(
MockDataFactory.randomUuid(),
Payload(
context = root,
events = emptyList()
)
)
)
}
}

View file

@ -10,6 +10,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.mockito.kotlin.mock
import kotlin.test.assertEquals
@ExperimentalCoroutinesApi
@ -18,7 +19,7 @@ class ObjectMenuOptionsProviderImplTest {
private val objectId: String = "objectId"
private val details = MutableStateFlow<Map<Id, Fields>>(mapOf())
private val restrictions = MutableStateFlow<List<ObjectRestriction>>(emptyList())
private val provider = ObjectMenuOptionsProviderImpl(details, restrictions)
private val provider = ObjectMenuOptionsProviderImpl(details, restrictions, mock())
@Test
fun `when layout note - options are layout, relations, history`() {
@ -30,6 +31,7 @@ class ObjectMenuOptionsProviderImplTest {
hasCover = false,
hasLayout = true,
hasRelations = true,
hasDiagnosticsVisibility = false
)
assertOptions(
@ -47,6 +49,7 @@ class ObjectMenuOptionsProviderImplTest {
hasCover = true,
hasLayout = true,
hasRelations = true,
hasDiagnosticsVisibility = false
)
assertOptions(
@ -61,7 +64,7 @@ class ObjectMenuOptionsProviderImplTest {
)
assertOptions(
expected = ObjectMenuOptionsProvider.Options.ALL
expected = ObjectMenuOptionsProvider.Options.ALL.copy(hasDiagnosticsVisibility = false)
)
}
@ -73,7 +76,7 @@ class ObjectMenuOptionsProviderImplTest {
)
assertOptions(
expected = ObjectMenuOptionsProvider.Options.ALL
expected = ObjectMenuOptionsProvider.Options.ALL.copy(hasDiagnosticsVisibility = false)
)
}
@ -85,7 +88,10 @@ class ObjectMenuOptionsProviderImplTest {
restrictions.value = listOf(ObjectRestriction.LAYOUT_CHANGE)
assertOptions(
expected = ObjectMenuOptionsProvider.Options.ALL.copy(hasLayout = false)
expected = ObjectMenuOptionsProvider.Options.ALL.copy(
hasLayout = false,
hasDiagnosticsVisibility = false
)
)
}
@ -102,6 +108,7 @@ class ObjectMenuOptionsProviderImplTest {
hasCover = false,
hasLayout = false,
hasRelations = true,
hasDiagnosticsVisibility = false
)
)
}

View file

@ -15,6 +15,7 @@ import com.anytypeio.anytype.core_models.SearchResult
import com.anytypeio.anytype.core_models.StubRelationObject
import com.anytypeio.anytype.core_models.StubTitle
import com.anytypeio.anytype.core_models.ext.content
import com.anytypeio.anytype.domain.base.Resultat
import com.anytypeio.anytype.presentation.navigation.AppNavigation
import com.anytypeio.anytype.presentation.objects.SupportedLayouts
import com.anytypeio.anytype.presentation.relations.ObjectSetConfig
@ -24,7 +25,6 @@ import com.anytypeio.anytype.presentation.sets.ObjectSetViewModel
import com.anytypeio.anytype.presentation.sets.model.Viewer
import com.anytypeio.anytype.presentation.util.CoroutinesTestRule
import com.anytypeio.anytype.test_utils.MockDataFactory
import com.anytypeio.anytype.test_utils.ValueClassAnswer
import com.jraska.livedata.test
import kotlinx.coroutines.test.runTest
import org.junit.After
@ -33,6 +33,7 @@ import org.junit.Rule
import org.junit.Test
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.stub
import org.mockito.kotlin.times
import org.mockito.kotlin.verifyBlocking
@ -596,7 +597,7 @@ class ObjectSetNavigationTest : ObjectSetViewModelTestSetup() {
private fun givenDelegateId(id: String) {
createNewObject.stub {
onBlocking { execute(Unit) } doAnswer ValueClassAnswer(id)
onBlocking { execute(Unit) } doReturn Resultat.success(id)
}
}
}

View file

@ -20,6 +20,7 @@ import com.anytypeio.anytype.domain.`object`.UpdateDetail
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.domain.base.Result
import com.anytypeio.anytype.domain.base.Resultat
import com.anytypeio.anytype.domain.block.interactor.UpdateText
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.config.Gateway
@ -52,7 +53,6 @@ import com.anytypeio.anytype.presentation.sets.ObjectSetSession
import com.anytypeio.anytype.presentation.sets.ObjectSetViewModel
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.test_utils.MockDataFactory
import com.anytypeio.anytype.test_utils.ValueClassAnswer
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flowOf
@ -244,7 +244,7 @@ open class ObjectSetViewModelTestSetup {
fun stubCloseBlock() {
closeBlock.stub {
onBlocking { execute(any()) } doAnswer ValueClassAnswer(Unit)
onBlocking { execute(any()) } doReturn Resultat.success(Unit)
}
}

View file

@ -10,6 +10,7 @@ import com.anytypeio.anytype.domain.auth.interactor.LaunchWallet
import com.anytypeio.anytype.domain.auth.model.AuthStatus
import com.anytypeio.anytype.domain.auth.repo.AuthRepository
import com.anytypeio.anytype.domain.base.Either
import com.anytypeio.anytype.domain.base.Resultat
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.launch.GetDefaultEditorType
import com.anytypeio.anytype.domain.launch.SetDefaultEditorType
@ -19,7 +20,6 @@ import com.anytypeio.anytype.domain.search.ObjectTypesSubscriptionManager
import com.anytypeio.anytype.domain.search.RelationsSubscriptionManager
import com.anytypeio.anytype.presentation.util.CoroutinesTestRule
import com.anytypeio.anytype.test_utils.MockDataFactory
import com.anytypeio.anytype.test_utils.ValueClassAnswer
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.Before
@ -31,6 +31,7 @@ import org.mockito.MockitoAnnotations
import org.mockito.kotlin.any
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.doThrow
import org.mockito.kotlin.eq
import org.mockito.kotlin.stub
import org.mockito.kotlin.times
@ -143,7 +144,7 @@ class SplashViewModelTest {
stubLaunchAccount()
stubGetLastOpenedObject()
getDefaultEditorType.stub {
onBlocking { execute(Unit) } doAnswer ValueClassAnswer (Exception("error"))
onBlocking { execute(Unit) } doThrow Exception("error")
}
initViewModel()
@ -231,35 +232,37 @@ class SplashViewModelTest {
}
@Test
fun `should fallback to default object type if default object type contains deprecated prefix id`() = runTest {
stubCheckAuthStatus(Either.Right(AuthStatus.AUTHORIZED))
stubLaunchWallet()
stubLaunchAccount()
stubGetLastOpenedObject()
stubGetDefaultObjectType(type = ObjectTypeIds.MARKETPLACE_OBJECT_TYPE_PREFIX + MockDataFactory.randomUuid())
fun `should fallback to default object type if default object type contains deprecated prefix id`() =
runTest {
stubCheckAuthStatus(Either.Right(AuthStatus.AUTHORIZED))
stubLaunchWallet()
stubLaunchAccount()
stubGetLastOpenedObject()
stubGetDefaultObjectType(type = ObjectTypeIds.MARKETPLACE_OBJECT_TYPE_PREFIX + MockDataFactory.randomUuid())
initViewModel()
initViewModel()
verify(setDefaultEditorType, times(1)).invoke(
SetDefaultEditorType.Params(
SplashViewModel.DEFAULT_TYPE_FIRST_INSTALL.first,
SplashViewModel.DEFAULT_TYPE_FIRST_INSTALL.second
verify(setDefaultEditorType, times(1)).invoke(
SetDefaultEditorType.Params(
SplashViewModel.DEFAULT_TYPE_FIRST_INSTALL.first,
SplashViewModel.DEFAULT_TYPE_FIRST_INSTALL.second
)
)
)
}
}
@Test
fun `should not fallback to default object type if default object type does not contain deprecated prefix id`() = runTest {
stubCheckAuthStatus(Either.Right(AuthStatus.AUTHORIZED))
stubLaunchWallet()
stubLaunchAccount()
stubGetLastOpenedObject()
stubGetDefaultObjectType(type = ObjectTypeIds.DEFAULT_OBJECT_TYPE_PREFIX + MockDataFactory.randomUuid())
fun `should not fallback to default object type if default object type does not contain deprecated prefix id`() =
runTest {
stubCheckAuthStatus(Either.Right(AuthStatus.AUTHORIZED))
stubLaunchWallet()
stubLaunchAccount()
stubGetLastOpenedObject()
stubGetDefaultObjectType(type = ObjectTypeIds.DEFAULT_OBJECT_TYPE_PREFIX + MockDataFactory.randomUuid())
initViewModel()
initViewModel()
verifyNoInteractions(setDefaultEditorType)
}
verifyNoInteractions(setDefaultEditorType)
}
//Todo can't mock Amplitude
// @Test
@ -361,7 +364,7 @@ class SplashViewModelTest {
private fun stubGetDefaultObjectType(type: String? = null, name: String? = null) {
getDefaultEditorType.stub {
onBlocking { execute(Unit) } doAnswer ValueClassAnswer(
onBlocking { execute(Unit) } doReturn Resultat.success(
GetDefaultEditorType.Response(
type,
name

View file

@ -1,8 +0,0 @@
package com.anytypeio.anytype.test_utils
import org.mockito.invocation.InvocationOnMock
import org.mockito.stubbing.Answer
class ValueClassAnswer(private val value: Any) : Answer<Any> {
override fun answer(invocation: InvocationOnMock?): Any = value
}

View file

@ -18,8 +18,10 @@ import com.anytypeio.anytype.ui_settings.R
fun MainSettingScreen(
onAccountAndDataClicked: () -> Unit,
onAboutAppClicked: () -> Unit,
onDebugClicked: () -> Unit,
onPersonalizationClicked: () -> Unit,
onAppearanceClicked: () -> Unit
onAppearanceClicked: () -> Unit,
showDebugMenu: Boolean
) {
Column {
Box(
@ -51,6 +53,14 @@ fun MainSettingScreen(
onClick = onAboutAppClicked
)
Divider()
if (showDebugMenu) {
Option(
image = R.drawable.ic_debug,
text = stringResource(R.string.debug),
onClick = onDebugClicked
)
Divider()
}
Box(modifier = Modifier.height(16.dp))
}
}

View file

@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="28"
android:viewportHeight="28">
<path
android:fillColor="@color/chapter_yellow"
android:pathData="M6,0L22,0A6,6 0,0 1,28 6L28,22A6,6 0,0 1,22 28L6,28A6,6 0,0 1,0 22L0,6A6,6 0,0 1,6 0z" />
<path
android:fillColor="@color/white"
android:pathData="M14,4C13,10 10,13 4,14H14V4Z" />
<path
android:fillColor="@color/white"
android:pathData="M14,24C13,18 10,15 4,14H14V24Z" />
<path
android:fillColor="@color/white"
android:pathData="M14,4C15,10 18,13 24,14H14V4Z" />
<path
android:fillColor="@color/white"
android:pathData="M14,24C15,18 18,15 24,14H14V24Z" />
</vector>

View file

@ -11,6 +11,7 @@
<string name="recovery_phrase">Recovery phrase</string>
<string name="clear_file_cache">Clear file cache</string>
<string name="debug_sync_report">Debug Sync Report</string>
<string name="debug">Debug</string>
<string name="account">Account</string>
<string name="reset_account">Reset account</string>
<string name="delete_account">Delete account</string>