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

DROID-1564 Multispaces | Copying space settings to clipboard + Added warning dialog for space deletion (#472)

This commit is contained in:
Evgenii Kozlov 2023-10-26 16:48:02 +02:00 committed by GitHub
parent 43357af5e4
commit b7f1266508
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 215 additions and 58 deletions

View file

@ -97,7 +97,7 @@ import com.anytypeio.anytype.di.feature.spaces.DaggerSelectSpaceComponent
import com.anytypeio.anytype.di.feature.spaces.DaggerSpaceSettingsComponent
import com.anytypeio.anytype.di.feature.templates.DaggerTemplateBlankComponent
import com.anytypeio.anytype.di.feature.templates.DaggerTemplateSelectComponent
import com.anytypeio.anytype.di.feature.types.DaggerTypeCreationComponent
import com.anytypeio.anytype.di.feature.types.DaggerCreateObjectTypeComponent
import com.anytypeio.anytype.di.feature.types.DaggerTypeEditComponent
import com.anytypeio.anytype.di.feature.types.DaggerTypeIconPickComponent
import com.anytypeio.anytype.di.feature.update.DaggerMigrationErrorComponent
@ -868,8 +868,8 @@ class ComponentManager(
.build()
}
val typeCreationComponent = Component {
DaggerTypeCreationComponent
val createObjectTypeComponent = Component {
DaggerCreateObjectTypeComponent
.factory()
.create(findComponentDependencies())
}

View file

@ -11,33 +11,33 @@ import com.anytypeio.anytype.domain.types.CreateType
import com.anytypeio.anytype.domain.workspace.SpaceManager
import com.anytypeio.anytype.emojifier.data.Emoji
import com.anytypeio.anytype.emojifier.data.EmojiProvider
import com.anytypeio.anytype.presentation.types.TypeCreationViewModel
import com.anytypeio.anytype.ui.types.create.TypeCreationFragment
import com.anytypeio.anytype.presentation.types.CreateObjectTypeViewModel
import com.anytypeio.anytype.ui.types.create.CreateObjectTypeFragment
import dagger.Binds
import dagger.Component
import dagger.Module
import dagger.Provides
@Component(
dependencies = [TypeCreationDependencies::class],
dependencies = [CreateObjectTypeDependencies::class],
modules = [
TypeCreationModule::class,
TypeCreationModule.Declarations::class
CreateObjectTypeModule::class,
CreateObjectTypeModule.Declarations::class
]
)
@PerScreen
interface TypeCreationComponent {
interface CreateObjectTypeComponent {
@Component.Factory
interface Factory {
fun create(dependencies: TypeCreationDependencies): TypeCreationComponent
fun create(dependencies: CreateObjectTypeDependencies): CreateObjectTypeComponent
}
fun inject(fragment: TypeCreationFragment)
fun inject(fragment: CreateObjectTypeFragment)
}
@Module
object TypeCreationModule {
object CreateObjectTypeModule {
@Provides
@PerScreen
@ -57,13 +57,13 @@ object TypeCreationModule {
@PerScreen
@Binds
fun bindViewModelFactory(factory: TypeCreationViewModel.Factory): ViewModelProvider.Factory
fun bindViewModelFactory(factory: CreateObjectTypeViewModel.Factory): ViewModelProvider.Factory
}
}
interface TypeCreationDependencies : ComponentDependencies {
interface CreateObjectTypeDependencies : ComponentDependencies {
fun blockRepository(): BlockRepository
fun dispatchers(): AppCoroutineDispatchers
fun urlBuilder(): UrlBuilder

View file

@ -43,7 +43,7 @@ import com.anytypeio.anytype.di.feature.spaces.SpaceSettingsDependencies
import com.anytypeio.anytype.di.feature.templates.TemplateBlankDependencies
import com.anytypeio.anytype.di.feature.templates.TemplateSelectDependencies
import com.anytypeio.anytype.di.feature.templates.TemplateSubComponent
import com.anytypeio.anytype.di.feature.types.TypeCreationDependencies
import com.anytypeio.anytype.di.feature.types.CreateObjectTypeDependencies
import com.anytypeio.anytype.di.feature.types.TypeEditDependencies
import com.anytypeio.anytype.di.feature.types.TypeIconPickDependencies
import com.anytypeio.anytype.di.feature.update.MigrationErrorDependencies
@ -80,7 +80,7 @@ interface MainComponent :
LibraryDependencies,
HomeScreenDependencies,
CollectionDependencies,
TypeCreationDependencies,
CreateObjectTypeDependencies,
TypeIconPickDependencies,
TypeEditDependencies,
RelationCreateFromLibraryDependencies,
@ -161,7 +161,7 @@ private abstract class ComponentDependenciesModule private constructor() {
@Binds
@IntoMap
@ComponentDependenciesKey(TypeCreationDependencies::class)
@ComponentDependenciesKey(CreateObjectTypeDependencies::class)
abstract fun provideTypeCreationDependencies(component: MainComponent): ComponentDependencies
@Binds

View file

@ -31,13 +31,13 @@ import com.anytypeio.anytype.ui.relations.REQUEST_UNINSTALL_RELATION_ARG_NAME
import com.anytypeio.anytype.ui.relations.RelationCreateFromScratchForObjectFragment
import com.anytypeio.anytype.ui.relations.RelationEditFragment
import com.anytypeio.anytype.ui.settings.typography
import com.anytypeio.anytype.ui.types.create.CreateObjectTypeFragment
import com.anytypeio.anytype.ui.types.create.REQUEST_CREATE_OBJECT
import com.anytypeio.anytype.ui.types.create.TypeCreationFragment
import com.anytypeio.anytype.ui.types.edit.REQUEST_KEY_MODIFY_TYPE
import com.anytypeio.anytype.ui.types.edit.REQUEST_UNINSTALL_TYPE_ARG_ID
import com.anytypeio.anytype.ui.types.edit.REQUEST_UNINSTALL_TYPE_ARG_NAME
import com.anytypeio.anytype.ui.types.edit.REQUEST_KEY_UNINSTALL_TYPE
import com.anytypeio.anytype.ui.types.edit.REQUEST_UNINSTALL_TYPE_ARG_ICON
import com.anytypeio.anytype.ui.types.edit.REQUEST_UNINSTALL_TYPE_ARG_ID
import com.anytypeio.anytype.ui.types.edit.REQUEST_UNINSTALL_TYPE_ARG_NAME
import com.anytypeio.anytype.ui.types.edit.TypeEditFragment
import com.google.accompanist.pager.ExperimentalPagerApi
import javax.inject.Inject
@ -82,7 +82,7 @@ class LibraryFragment : BaseComposeFragment() {
findNavController().safeNavigate(
R.id.libraryFragment,
R.id.openTypeCreationScreen,
TypeCreationFragment.args(it.name)
CreateObjectTypeFragment.args(it.name)
)
}
is LibraryViewModel.Navigation.OpenTypeEditing -> {

View file

@ -27,20 +27,26 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.fragment.findNavController
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_ui.common.ComposeDialogView
import com.anytypeio.anytype.core_ui.extensions.throttledClick
import com.anytypeio.anytype.core_ui.foundation.Divider
import com.anytypeio.anytype.core_ui.foundation.Option
import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
import com.anytypeio.anytype.core_ui.views.ButtonSize
import com.anytypeio.anytype.core_ui.views.ButtonWarning
import com.anytypeio.anytype.core_ui.views.PreviewTitle2Regular
import com.anytypeio.anytype.core_ui.views.Title1
import com.anytypeio.anytype.core_utils.clipboard.copyPlainTextToClipboard
import com.anytypeio.anytype.core_utils.const.DateConst
import com.anytypeio.anytype.core_utils.ext.formatTimeInMillis
import com.anytypeio.anytype.core_utils.ext.toast
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment
import com.anytypeio.anytype.core_utils.ui.ViewState
import com.anytypeio.anytype.di.common.componentManager
import com.anytypeio.anytype.presentation.spaces.SpaceSettingsViewModel
import com.anytypeio.anytype.ui.settings.typography
import com.anytypeio.anytype.ui.spaces.DeleteSpaceWarning
import com.anytypeio.anytype.ui.spaces.Section
import com.anytypeio.anytype.ui.spaces.TypeOfSpace
import com.anytypeio.anytype.ui_settings.main.SpaceHeader
@ -61,15 +67,46 @@ class SpaceSettingsFragment : BaseBottomSheetComposeFragment() {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
MaterialTheme(typography = typography) {
SpaceSettingsScreen(onSpaceIconClick = {},
SpaceSettingsScreen(
onSpaceIconClick = {},
onNameSet = vm::onNameSet,
spaceData = vm.spaceViewState.collectAsStateWithLifecycle().value,
onDeleteSpaceClicked = vm::onDeleteSpaceClicked,
onDeleteSpaceClicked = throttledClick(
onClick = {
val dialog = DeleteSpaceWarning.new()
dialog.onDeletionAccepted = {
dialog.dismiss()
vm.onDeleteSpaceClicked()
}
dialog.show(childFragmentManager, null)
}
),
onFileStorageClick = {
findNavController().navigate(R.id.filesStorageScreen)
},
onPersonalizationClicked = {
findNavController().navigate(R.id.personalizationScreen)
},
onSpaceIdClicked = {
context.copyPlainTextToClipboard(
plainText = it,
label = "Space ID",
successToast = context.getString(R.string.space_id_copied_toast_msg)
)
},
onNetworkIdClicked = {
context.copyPlainTextToClipboard(
plainText = it,
label = "Network ID",
successToast = context.getString(R.string.network_id_copied_toast_msg)
)
},
onCreatedByClicked = {
context.copyPlainTextToClipboard(
plainText = it,
label = "Created-by ID",
successToast = context.getString(R.string.created_by_id_copied_toast_msg)
)
}
)
LaunchedEffect(Unit) { vm.toasts.collect { toast(it) } }
@ -105,6 +142,9 @@ fun SpaceSettingsScreen(
onDeleteSpaceClicked: () -> Unit,
onFileStorageClick: () -> Unit,
onPersonalizationClicked: () -> Unit,
onSpaceIdClicked: (Id) -> Unit,
onNetworkIdClicked: (Id) -> Unit,
onCreatedByClicked: (Id) -> Unit
) {
LazyColumn(
modifier = Modifier
@ -148,12 +188,20 @@ fun SpaceSettingsScreen(
onClick = throttledClick(onFileStorageClick)
)
}
item {
Divider(paddingStart = 60.dp)
}
item {
Option(image = R.drawable.ic_personalization,
text = stringResource(R.string.personalization),
onClick = throttledClick(onPersonalizationClicked)
)
}
item {
Divider(
paddingStart = 60.dp
)
}
item {
Section(title = stringResource(id = R.string.space_info))
}
@ -174,11 +222,14 @@ fun SpaceSettingsScreen(
Text(
text = spaceData.data.spaceId ?: stringResource(id = R.string.unknown),
style = PreviewTitle2Regular,
maxLines = 2,
color = colorResource(id = R.color.text_primary),
modifier = Modifier
.align(Alignment.BottomStart)
.padding(bottom = 12.dp, end = 50.dp),
maxLines = 2,
color = colorResource(id = R.color.text_primary)
.padding(bottom = 12.dp)
.noRippleClickable {
onSpaceIdClicked(spaceData.data.spaceId.orEmpty())
}
)
}
}
@ -200,11 +251,14 @@ fun SpaceSettingsScreen(
Text(
text = spaceData.data.createdBy ?: stringResource(id = R.string.unknown),
style = PreviewTitle2Regular,
maxLines = 1,
color = colorResource(id = R.color.text_primary),
modifier = Modifier
.align(Alignment.BottomStart)
.padding(bottom = 12.dp, end = 50.dp),
maxLines = 1,
color = colorResource(id = R.color.text_primary)
.padding(bottom = 12.dp, end = 50.dp)
.noRippleClickable {
onCreatedByClicked(spaceData.data.createdBy.orEmpty())
}
)
}
}
@ -223,8 +277,11 @@ fun SpaceSettingsScreen(
color = colorResource(id = R.color.text_primary)
)
if (spaceData is ViewState.Success) {
val formattedDate = spaceData.data.createdDateInMillis?.formatTimeInMillis(
DateConst.DEFAULT_DATE_FORMAT
) ?: stringResource(id = R.string.unknown)
Text(
text = spaceData.data.createdDate ?: stringResource(id = R.string.unknown),
text = formattedDate,
style = PreviewTitle2Regular,
modifier = Modifier
.align(Alignment.BottomStart)
@ -235,6 +292,35 @@ fun SpaceSettingsScreen(
}
}
}
item {
Box(
modifier = Modifier
.height(92.dp)
.padding(horizontal = 20.dp)
.fillMaxWidth()
) {
Text(
text = stringResource(id = R.string.network_id),
style = Title1,
modifier = Modifier.padding(top = 12.dp),
color = colorResource(id = R.color.text_primary)
)
if (spaceData is ViewState.Success) {
Text(
text = spaceData.data.network ?: stringResource(id = R.string.unknown),
style = PreviewTitle2Regular,
maxLines = 2,
color = colorResource(id = R.color.text_primary),
modifier = Modifier
.align(Alignment.BottomStart)
.padding(bottom = 12.dp, end = 50.dp)
.noRippleClickable {
onNetworkIdClicked(spaceData.data.network.orEmpty())
}
)
}
}
}
if (spaceData is ViewState.Success && spaceData.data.isDeletable) {
item {
Box(modifier = Modifier.height(78.dp)) {

View file

@ -0,0 +1,61 @@
package com.anytypeio.anytype.ui.spaces
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.material.MaterialTheme
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.res.stringResource
import androidx.core.os.bundleOf
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_ui.foundation.Warning
import com.anytypeio.anytype.core_utils.ext.arg
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment
import com.anytypeio.anytype.ui.settings.typography
class DeleteSpaceWarning : BaseBottomSheetComposeFragment() {
private val name: String get() = arg(ARG_NAME)
var onDeletionAccepted: () -> Unit = {}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
MaterialTheme(typography = typography) {
Warning(
actionButtonText = stringResource(R.string.delete),
cancelButtonText = stringResource(R.string.back),
title = stringResource(R.string.delete_space_title),
subtitle = stringResource(R.string.delete_space_subtitle),
onNegativeClick = { dismiss() },
onPositiveClick = { onDeletionAccepted() },
isInProgress = false
)
}
}
}
}
override fun injectDependencies() {
// Do nothing
}
override fun releaseDependencies() {
// Do nothing
}
companion object {
const val ARG_NAME = "arg.space-delete-warning.name"
fun new() : DeleteSpaceWarning = DeleteSpaceWarning().apply {
arguments = bundleOf()
}
}
}

View file

@ -19,19 +19,19 @@ import com.anytypeio.anytype.core_utils.ext.subscribe
import com.anytypeio.anytype.core_utils.ext.toast
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment
import com.anytypeio.anytype.di.common.componentManager
import com.anytypeio.anytype.presentation.types.TypeCreationViewModel
import com.anytypeio.anytype.presentation.types.CreateObjectTypeViewModel
import com.anytypeio.anytype.ui.settings.typography
import com.anytypeio.anytype.ui.types.picker.REQUEST_KEY_PICK_EMOJI
import com.anytypeio.anytype.ui.types.picker.REQUEST_KEY_REMOVE_EMOJI
import com.anytypeio.anytype.ui.types.picker.RESULT_EMOJI_UNICODE
import javax.inject.Inject
class TypeCreationFragment : BaseBottomSheetComposeFragment() {
class CreateObjectTypeFragment : BaseBottomSheetComposeFragment() {
@Inject
lateinit var factory: TypeCreationViewModel.Factory
lateinit var factory: CreateObjectTypeViewModel.Factory
private val vm by viewModels<TypeCreationViewModel> { factory }
private val vm by viewModels<CreateObjectTypeViewModel> { factory }
private val preparedName get() = argString(ARG_TYPE_NAME)
@ -68,11 +68,11 @@ class TypeCreationFragment : BaseBottomSheetComposeFragment() {
vm.onPreparedString(preparedName)
subscribe(vm.navigation) {
when (it) {
is TypeCreationViewModel.Navigation.BackWithCreatedType -> {
is CreateObjectTypeViewModel.Navigation.BackWithCreatedType -> {
setFragmentResult(REQUEST_CREATE_OBJECT, bundleOf())
findNavController().popBackStack()
}
is TypeCreationViewModel.Navigation.SelectEmoji -> {
is CreateObjectTypeViewModel.Navigation.SelectEmoji -> {
findNavController().navigate(
R.id.openEmojiPicker, bundleOf(ARG_SHOW_REMOVE_BUTTON to it.showRemove)
)
@ -83,11 +83,11 @@ class TypeCreationFragment : BaseBottomSheetComposeFragment() {
}
override fun injectDependencies() {
componentManager().typeCreationComponent.get().inject(this)
componentManager().createObjectTypeComponent.get().inject(this)
}
override fun releaseDependencies() {
componentManager().typeCreationComponent.release()
componentManager().createObjectTypeComponent.release()
}
companion object {

View file

@ -12,7 +12,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.anytypeio.anytype.presentation.types.TypeCreationViewModel
import com.anytypeio.anytype.presentation.types.CreateObjectTypeViewModel
import com.anytypeio.anytype.ui.types.create.TypeScreenDefaults.PaddingBottom
import com.anytypeio.anytype.ui.types.create.TypeScreenDefaults.PaddingTop
import com.anytypeio.anytype.ui.types.views.TypeCreationHeader
@ -20,7 +20,7 @@ import com.anytypeio.anytype.ui.types.views.TypeEditWidget
@ExperimentalMaterialApi
@Composable
fun TypeCreationScreen(vm: TypeCreationViewModel, preparedName: String) {
fun TypeCreationScreen(vm: CreateObjectTypeViewModel, preparedName: String) {
val state by vm.uiState.collectAsStateWithLifecycle()
val inputValue = remember { mutableStateOf(preparedName) }

View file

@ -21,16 +21,16 @@ import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_ui.foundation.Dragger
import com.anytypeio.anytype.presentation.types.TypeCreationViewModel
import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
import com.anytypeio.anytype.core_ui.views.Title1
import com.anytypeio.anytype.core_ui.views.UXBody
import com.anytypeio.anytype.presentation.types.CreateObjectTypeViewModel
import com.anytypeio.anytype.ui.types.views.HeaderDefaults.ColorTextActive
import com.anytypeio.anytype.ui.types.views.HeaderDefaults.ColorTextDisabled
@Composable
fun TypeCreationHeader(
vm: TypeCreationViewModel,
vm: CreateObjectTypeViewModel,
nameValid: MutableState<Boolean>,
inputValue: MutableState<Id>,
) {

View file

@ -402,5 +402,12 @@ Do the computation of an expensive paragraph of text on a background thread:
<string name="creation_date">Creation date</string>
<string name="created_by">Created by</string>
<string name="space_id">Space ID</string>
<string name="network_id">Network ID</string>
<string name="delete_space_subtitle">This space will be deleted irrevocably. You cant undo this action.</string>
<string name="delete_space_title_with_param">Delete \'%1$s\' space</string>
<string name="delete_space_title">Are you sure to delete this space?</string>
<string name="space_id_copied_toast_msg">Space ID copied</string>
<string name="network_id_copied_toast_msg">Network ID copied</string>
<string name="created_by_id_copied_toast_msg">Created-by ID copied</string>
</resources>

View file

@ -15,5 +15,6 @@ data class Config(
val spaceView: Id,
val widgets: Id,
val analytics: Id,
val device: Id
val device: Id,
val network: Id
)

View file

@ -53,6 +53,7 @@ fun Rpc.Account.Select.Response.toAccountSetup(): AccountSetup {
widgets = info.widgetsId,
analytics = info.analyticsId,
device = info.deviceId,
network = info.networkId
),
status = status?.core() ?: AccountStatus.Unknown
)

View file

@ -757,5 +757,6 @@ fun Account.Info.config() : Config = Config(
spaceView = spaceViewId,
widgets = widgetsId,
analytics = analyticsId,
device = deviceId
device = deviceId,
network = networkId
)

View file

@ -66,24 +66,21 @@ class SpaceSettingsViewModel(
).mapNotNull { results ->
results.firstOrNull()
}.map { wrapper ->
val spaceId = wrapper.getValue<Id>(Relations.TARGET_SPACE_ID)
SpaceData(
name = wrapper.name.orEmpty(),
icon = wrapper.spaceIcon(
builder = urlBuilder,
spaceGradientProvider = gradientProvider
),
createdDate = wrapper
createdDateInMillis = wrapper
.getValue<Double?>(Relations.CREATED_DATE)
.toString(),
?.let { timeInSeconds -> (timeInSeconds * 1000L).toLong() },
createdBy = wrapper
.getValue<Id?>(Relations.CREATOR)
.toString(),
spaceId = spaceId,
isDeletable = if (spaceId == null)
false
else
isSpaceDeletable(spaceId)
spaceId = config.space,
network = config.network,
isDeletable = isSpaceDeletable(config.space)
)
}
}.collect { spaceViewState.value = ViewState.Success(it) }
@ -167,8 +164,9 @@ class SpaceSettingsViewModel(
data class SpaceData(
val spaceId: Id?,
val createdDate: String?,
val createdDateInMillis: Long?,
val createdBy: Id?,
val network: Id?,
val name: String,
val icon: SpaceIconView,
val isDeletable: Boolean = false

View file

@ -27,13 +27,13 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import timber.log.Timber
class TypeCreationViewModel(
class CreateObjectTypeViewModel(
private val createTypeInteractor: CreateType,
private val urlBuilder: UrlBuilder,
private val emojiProvider: EmojiProvider,
private val analytics: Analytics,
private val spaceManager: SpaceManager
) : NavigationViewModel<TypeCreationViewModel.Navigation>() {
) : NavigationViewModel<CreateObjectTypeViewModel.Navigation>() {
private val unicodeIconFlow = MutableStateFlow("")
@ -113,7 +113,7 @@ class TypeCreationViewModel(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return TypeCreationViewModel(
return CreateObjectTypeViewModel(
createTypeInteractor = createType,
urlBuilder = urlBuilder,
emojiProvider = emojiProvider,

View file

@ -34,7 +34,8 @@ fun StubConfig(
widgets: Id = MockDataFactory.randomUuid(),
analytics: Id = MockDataFactory.randomUuid(),
device: Id = MockDataFactory.randomUuid(),
space: Id = MockDataFactory.randomUuid()
space: Id = MockDataFactory.randomUuid(),
network: Id = MockDataFactory.randomUuid()
) : Config = Config(
home = home,
profile = profile,
@ -43,7 +44,8 @@ fun StubConfig(
space = space,
widgets = widgets,
analytics = analytics,
device = device
device = device,
network = network
)
fun StubFeatureConfig(