From c3d6882e5fd2a69a8294243486b0e21405748e5c Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Fri, 26 Jan 2024 18:03:39 +0100 Subject: [PATCH] DROID-2173 Multispaces | Enhancement | Allow changing space gradient icon in space settings (#803) --- .../anytype/di/common/ComponentManager.kt | 5 - .../di/feature/settings/MainSettingsDi.kt | 97 ------ .../anytype/di/main/MainComponent.kt | 4 +- .../ui/settings/MainSettingFragment.kt | 182 ----------- .../settings/space/SpaceSettingsFragment.kt | 19 +- localization/src/main/res/values/strings.xml | 1 + .../settings/FilesStorageViewModel.kt | 4 +- .../settings/MainSettingsViewModel.kt | 301 ------------------ .../spaces/SpaceSettingsViewModel.kt | 25 +- .../ui_settings/main/MainSettingScreen.kt | 262 ++------------- .../ui_settings/space/SpaceSettingsScreen.kt | 65 ---- 11 files changed, 72 insertions(+), 893 deletions(-) delete mode 100644 app/src/main/java/com/anytypeio/anytype/di/feature/settings/MainSettingsDi.kt delete mode 100644 app/src/main/java/com/anytypeio/anytype/ui/settings/MainSettingFragment.kt delete mode 100644 presentation/src/main/java/com/anytypeio/anytype/presentation/settings/MainSettingsViewModel.kt delete mode 100644 ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/space/SpaceSettingsScreen.kt diff --git a/app/src/main/java/com/anytypeio/anytype/di/common/ComponentManager.kt b/app/src/main/java/com/anytypeio/anytype/di/common/ComponentManager.kt index 336a310f4..7a2df5de8 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/common/ComponentManager.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/common/ComponentManager.kt @@ -86,7 +86,6 @@ import com.anytypeio.anytype.di.feature.settings.DaggerAppearanceComponent import com.anytypeio.anytype.di.feature.settings.DaggerFilesStorageComponent import com.anytypeio.anytype.di.feature.settings.DaggerSpacesStorageComponent import com.anytypeio.anytype.di.feature.settings.LogoutWarningModule -import com.anytypeio.anytype.di.feature.settings.MainSettingsModule import com.anytypeio.anytype.di.feature.settings.ProfileModule import com.anytypeio.anytype.di.feature.sharing.DaggerAddToAnytypeComponent import com.anytypeio.anytype.di.feature.spaces.DaggerCreateSpaceComponent @@ -800,10 +799,6 @@ class ComponentManager( main.logoutWarningComponent().module(LogoutWarningModule).build() } - val mainSettingsComponent = Component { - main.mainSettingsComponent().module(MainSettingsModule).build() - } - val filesStorageComponent = Component { DaggerFilesStorageComponent.builder() .withDependencies(findComponentDependencies()) diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/settings/MainSettingsDi.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/settings/MainSettingsDi.kt deleted file mode 100644 index 7f9932544..000000000 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/settings/MainSettingsDi.kt +++ /dev/null @@ -1,97 +0,0 @@ -package com.anytypeio.anytype.di.feature.settings - -import com.anytypeio.anytype.analytics.base.Analytics -import com.anytypeio.anytype.core_utils.di.scope.PerScreen -import com.anytypeio.anytype.device.share.debug.DebugSpaceDeviceFileContentSaver -import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers -import com.anytypeio.anytype.domain.block.repo.BlockRepository -import com.anytypeio.anytype.domain.debugging.DebugSpaceContentSaver -import com.anytypeio.anytype.domain.debugging.DebugSpaceShareDownloader -import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer -import com.anytypeio.anytype.domain.misc.UrlBuilder -import com.anytypeio.anytype.domain.`object`.SetObjectDetails -import com.anytypeio.anytype.domain.spaces.SetSpaceDetails -import com.anytypeio.anytype.domain.workspace.SpaceManager -import com.anytypeio.anytype.presentation.settings.MainSettingsViewModel -import com.anytypeio.anytype.presentation.spaces.SpaceGradientProvider -import com.anytypeio.anytype.presentation.util.downloader.UriFileProvider -import com.anytypeio.anytype.providers.DefaultUriFileProvider -import com.anytypeio.anytype.ui.settings.MainSettingFragment -import dagger.Binds -import dagger.Module -import dagger.Provides -import dagger.Subcomponent - -@Subcomponent( - modules = [ - MainSettingsModule::class, - MainSettingsModule.Bindings::class - ] -) -@PerScreen -interface MainSettingsSubComponent { - - @Subcomponent.Builder - interface Builder { - fun module(module: MainSettingsModule): Builder - fun build(): MainSettingsSubComponent - } - - fun inject(fragment: MainSettingFragment) -} - -@Module -object MainSettingsModule { - - @JvmStatic - @Provides - @PerScreen - fun provideSetObjectDetails( - repo: BlockRepository, - dispatchers: AppCoroutineDispatchers - ): SetObjectDetails = SetObjectDetails( - repo = repo, - dispatchers = dispatchers - ) - - @JvmStatic - @Provides - @PerScreen - fun provideSpaceGradientProvider(): SpaceGradientProvider = SpaceGradientProvider.Default - - @JvmStatic - @Provides - @PerScreen - fun provideViewModelFactory( - analytics: Analytics, - storelessSubscriptionContainer: StorelessSubscriptionContainer, - urlBuilder: UrlBuilder, - spaceGradientProvider: SpaceGradientProvider, - debugSpaceShareDownloader: DebugSpaceShareDownloader, - spaceManager: SpaceManager, - setSpaceDetails: SetSpaceDetails - ): MainSettingsViewModel.Factory = MainSettingsViewModel.Factory( - analytics = analytics, - storelessSubscriptionContainer = storelessSubscriptionContainer, - urlBuilder = urlBuilder, - setSpaceDetails = setSpaceDetails, - spaceGradientProvider = spaceGradientProvider, - debugSpaceShareDownloader = debugSpaceShareDownloader, - spaceManager = spaceManager - ) - - @Module - interface Bindings { - @PerScreen - @Binds - fun bindUriFileProvider( - defaultProvider: DefaultUriFileProvider - ): UriFileProvider - - @PerScreen - @Binds - fun bindSpaceDebugDeviceSharer( - saver: DebugSpaceDeviceFileContentSaver - ): DebugSpaceContentSaver - } -} \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/di/main/MainComponent.kt b/app/src/main/java/com/anytypeio/anytype/di/main/MainComponent.kt index b18a96594..05d3cb2d3 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/main/MainComponent.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/main/MainComponent.kt @@ -1,6 +1,6 @@ package com.anytypeio.anytype.di.main -import com.anytypeio.anytype.app.AndroidApplication + import com.anytypeio.anytype.app.AndroidApplication import com.anytypeio.anytype.di.common.ComponentDependencies import com.anytypeio.anytype.di.common.ComponentDependenciesKey import com.anytypeio.anytype.di.feature.AppPreferencesDependencies @@ -33,7 +33,6 @@ import com.anytypeio.anytype.di.feature.settings.AboutAppDependencies import com.anytypeio.anytype.di.feature.settings.AppearanceDependencies import com.anytypeio.anytype.di.feature.settings.FilesStorageDependencies import com.anytypeio.anytype.di.feature.settings.LogoutWarningSubComponent -import com.anytypeio.anytype.di.feature.settings.MainSettingsSubComponent import com.anytypeio.anytype.di.feature.settings.ProfileSubComponent import com.anytypeio.anytype.di.feature.settings.SpacesStorageDependencies import com.anytypeio.anytype.di.feature.sharing.AddToAnytypeDependencies @@ -129,7 +128,6 @@ interface MainComponent : fun keychainPhraseComponentBuilder(): KeychainPhraseSubComponent.Builder fun personalizationSettingsComponentBuilder(): PersonalizationSettingsSubComponent.Builder fun logoutWarningComponent(): LogoutWarningSubComponent.Builder - fun mainSettingsComponent(): MainSettingsSubComponent.Builder //endregion } diff --git a/app/src/main/java/com/anytypeio/anytype/ui/settings/MainSettingFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/settings/MainSettingFragment.kt deleted file mode 100644 index 7d6c966be..000000000 --- a/app/src/main/java/com/anytypeio/anytype/ui/settings/MainSettingFragment.kt +++ /dev/null @@ -1,182 +0,0 @@ -package com.anytypeio.anytype.ui.settings - -import android.content.Intent -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.Toast -import androidx.compose.material.MaterialTheme -import androidx.compose.ui.platform.ViewCompositionStrategy -import androidx.core.os.bundleOf -import androidx.fragment.app.viewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import com.anytypeio.anytype.R -import com.anytypeio.anytype.core_ui.common.ComposeDialogView -import com.anytypeio.anytype.core_utils.ext.setupBottomSheetBehavior -import com.anytypeio.anytype.core_utils.ext.shareFile -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 -import com.anytypeio.anytype.presentation.settings.MainSettingsViewModel -import com.anytypeio.anytype.presentation.settings.MainSettingsViewModel.Command -import com.anytypeio.anytype.presentation.settings.MainSettingsViewModel.Event -import com.anytypeio.anytype.presentation.util.downloader.UriFileProvider -import com.anytypeio.anytype.ui.editor.modals.IconPickerFragmentBase.Companion.ARG_CONTEXT_ID_KEY -import com.anytypeio.anytype.ui.sets.ARG_SHOW_REMOVE_BUTTON -import com.anytypeio.anytype.ui.settings.system.SettingsActivity -import com.anytypeio.anytype.ui_settings.main.MainSettingScreen -import java.io.File -import javax.inject.Inject -import kotlinx.coroutines.launch -import timber.log.Timber - -@Deprecated("To be deleted") -class MainSettingFragment : BaseBottomSheetComposeFragment() { - - @Inject - lateinit var factory: MainSettingsViewModel.Factory - - @Inject - lateinit var featureToggles: FeatureToggles - - @Inject - lateinit var uriFileProvider: UriFileProvider - - private val vm by viewModels { factory } - - private val onProfileClicked = { - vm.onOptionClicked(Event.OnProfileClicked) - } - - private val onAboutAppClicked = { - vm.onOptionClicked(Event.OnAboutClicked) - } - - private val onPersonalizationClicked = { - vm.onOptionClicked(Event.OnPersonalizationClicked) - } - - private val onAppearanceClicked = { - vm.onOptionClicked(Event.OnAppearanceClicked) - } - - private val onDebugClicked = { - vm.onOptionClicked(Event.OnDebugClicked) - } - - private val onSpaceImageClicked = { - vm.onOptionClicked(Event.OnSpaceImageClicked) - } - - private val onNameSet = { name: String -> - vm.onNameSet(name) - } - - private val onFileStorageClicked = { - vm.onOptionClicked(Event.OnFilesStorageClicked) - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = ComposeDialogView( - context = requireContext(), - dialog = requireDialog() - ).apply { - setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) - setContent { - MaterialTheme(typography = typography) { - MainSettingScreen( - workspace = vm.workspaceAndAccount.collectAsStateWithLifecycle().value, - onProfileClicked = onProfileClicked, - onAboutAppClicked = onAboutAppClicked, - onAppearanceClicked = onAppearanceClicked, - onDebugClicked = onDebugClicked, - onPersonalizationClicked = onPersonalizationClicked, - onSpaceIconClick = onSpaceImageClicked, - onNameSet = onNameSet, - onFileStorageClick = onFileStorageClicked - ) - } - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupBottomSheetBehavior(PADDING_TOP) - - viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { - vm.commands.collect { command -> processCommands(command) } - } - } - } - - private fun processCommands(command: Command) { - when (command) { - is Command.OpenAboutScreen -> { - safeNavigate(R.id.actionOpenAboutAppScreen) - } - is Command.OpenProfileScreen -> { - safeNavigate(R.id.actionOpenProfileScreen) - } - is Command.OpenAppearanceScreen -> { - safeNavigate(R.id.actionOpenAppearanceScreen) - } - is Command.OpenPersonalizationScreen -> { - safeNavigate(R.id.actionOpenPersonalizationScreen) - } - is Command.OpenDebugScreen -> { - startActivity(Intent(requireActivity(), SettingsActivity::class.java)) - } - is Command.OpenSpaceImageSet -> { - safeNavigate( - R.id.actionOpenImagePickerScreen, bundleOf( - ARG_CONTEXT_ID_KEY to command.id, - ARG_SHOW_REMOVE_BUTTON to command.showRemoveButton - ) - ) - } - is Command.OpenFilesStorageScreen -> { - safeNavigate(R.id.actionOpenFilesStorageScreen) - } - is Command.Toast -> { - toast( - msg = command.msg, - duration = if (command.isLongDuration) - Toast.LENGTH_LONG - else - Toast.LENGTH_SHORT - ) - } - is Command.ShareSpaceDebug -> { - try { - shareFile( - uriFileProvider.getUriForFile(File(command.path)) - ) - } catch (e: Exception) { - Timber.e(e, "Error while sharing space debug").also { - toast("Error while sharing space debug. Please try again later.") - } - } - } - } - } - - override fun injectDependencies() { - componentManager().mainSettingsComponent.get().inject(this) - } - - override fun releaseDependencies() { - componentManager().mainSettingsComponent.release() - } -} - -private const val PADDING_TOP = 54 \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/settings/space/SpaceSettingsFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/settings/space/SpaceSettingsFragment.kt index 24846a942..ce70405d0 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/settings/space/SpaceSettingsFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/settings/space/SpaceSettingsFragment.kt @@ -75,9 +75,13 @@ class SpaceSettingsFragment : BaseBottomSheetComposeFragment() { ).apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { - MaterialTheme(typography = typography) { + MaterialTheme( + typography = typography, + colors = MaterialTheme.colors.copy( + surface = colorResource(id = R.color.context_menu_background) + ) + ) { SpaceSettingsScreen( - onSpaceIconClick = {}, onNameSet = vm::onNameSet, spaceData = vm.spaceViewState.collectAsStateWithLifecycle().value, onDeleteSpaceClicked = throttledClick( @@ -125,7 +129,8 @@ class SpaceSettingsFragment : BaseBottomSheetComposeFragment() { successToast = context.getString(R.string.created_by_id_copied_toast_msg) ) }, - onDebugClicked = vm::onSpaceDebugClicked + onDebugClicked = vm::onSpaceDebugClicked, + onRandomGradientClicked = vm::onRandomSpaceGradientClicked ) LaunchedEffect(Unit) { vm.toasts.collect { toast(it) } } LaunchedEffect(Unit) { @@ -176,7 +181,6 @@ class SpaceSettingsFragment : BaseBottomSheetComposeFragment() { @Composable fun SpaceSettingsScreen( spaceData: ViewState, - onSpaceIconClick: () -> Unit, onNameSet: (String) -> Unit, onDeleteSpaceClicked: () -> Unit, onFileStorageClick: () -> Unit, @@ -184,7 +188,8 @@ fun SpaceSettingsScreen( onSpaceIdClicked: (Id) -> Unit, onNetworkIdClicked: (Id) -> Unit, onCreatedByClicked: (Id) -> Unit, - onDebugClicked: () -> Unit + onDebugClicked: () -> Unit, + onRandomGradientClicked: () -> Unit ) { LazyColumn( modifier = Modifier @@ -205,8 +210,8 @@ fun SpaceSettingsScreen( is ViewState.Success -> spaceData.data.icon else -> null }, - onSpaceIconClick = onSpaceIconClick, - onNameSet = onNameSet + onNameSet = onNameSet, + onRandomGradientClicked = onRandomGradientClicked ) } item { Divider() } diff --git a/localization/src/main/res/values/strings.xml b/localization/src/main/res/values/strings.xml index 16fcfe036..2c39a4b96 100644 --- a/localization/src/main/res/values/strings.xml +++ b/localization/src/main/res/values/strings.xml @@ -59,6 +59,7 @@ Space Space name Personal Space + Apply random gradient Delete space You can store up to %1$s of your files on our encrypted backup node for free. If you reach the limit, files will be stored only locally. In order to save space on your local device, you can offload all your files to our encrypted backup node. The files will be loaded back when you open them. diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/settings/FilesStorageViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/settings/FilesStorageViewModel.kt index ab52a1c65..3052d760e 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/settings/FilesStorageViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/settings/FilesStorageViewModel.kt @@ -377,4 +377,6 @@ class FilesStorageViewModel( companion object { const val WARNING_PERCENT = 0.9f } -} \ No newline at end of file +} + +const val SPACE_STORAGE_SUBSCRIPTION_ID = "settings_space_storage_subscription" diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/settings/MainSettingsViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/settings/MainSettingsViewModel.kt deleted file mode 100644 index 3f490ea38..000000000 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/settings/MainSettingsViewModel.kt +++ /dev/null @@ -1,301 +0,0 @@ -package com.anytypeio.anytype.presentation.settings - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.viewModelScope -import com.anytypeio.anytype.analytics.base.Analytics -import com.anytypeio.anytype.analytics.base.EventsDictionary -import com.anytypeio.anytype.analytics.base.sendEvent -import com.anytypeio.anytype.core_models.Filepath -import com.anytypeio.anytype.core_models.Id -import com.anytypeio.anytype.core_models.Relations -import com.anytypeio.anytype.core_models.primitives.SpaceId -import com.anytypeio.anytype.core_utils.ext.throttleFirst -import com.anytypeio.anytype.domain.base.fold -import com.anytypeio.anytype.domain.debugging.DebugSpaceShareDownloader -import com.anytypeio.anytype.domain.library.StoreSearchByIdsParams -import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer -import com.anytypeio.anytype.domain.misc.UrlBuilder -import com.anytypeio.anytype.domain.spaces.SetSpaceDetails -import com.anytypeio.anytype.domain.workspace.SpaceManager -import com.anytypeio.anytype.presentation.profile.ProfileIconView -import com.anytypeio.anytype.presentation.profile.profileIcon -import com.anytypeio.anytype.presentation.spaces.SpaceGradientProvider -import com.anytypeio.anytype.presentation.spaces.SpaceIconView -import com.anytypeio.anytype.presentation.spaces.spaceIcon -import javax.inject.Inject -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch -import timber.log.Timber - -@Deprecated("Legacy. To be deleted") -class MainSettingsViewModel( - private val analytics: Analytics, - private val storelessSubscriptionContainer: StorelessSubscriptionContainer, - private val urlBuilder: UrlBuilder, - private val setSpaceDetails: SetSpaceDetails, - private val spaceGradientProvider: SpaceGradientProvider, - private val debugSpaceShareDownloader: DebugSpaceShareDownloader, - private val spaceManager: SpaceManager -) : ViewModel() { - - val events = MutableSharedFlow(replay = 0) - val commands = MutableSharedFlow(replay = 0) - - val workspaceAndAccount = spaceManager - .observe() - .flatMapLatest { config -> - storelessSubscriptionContainer.subscribe( - StoreSearchByIdsParams( - subscription = SPACE_STORAGE_SUBSCRIPTION_ID, - targets = listOf(config.spaceView, config.profile), - keys = listOf( - Relations.ID, - Relations.SPACE_ID, - Relations.NAME, - Relations.ICON_EMOJI, - Relations.ICON_IMAGE, - Relations.ICON_OPTION - ) - ) - ).map { result -> - val workspace = result.find { it.id == config.spaceView } - if (workspace == null) Timber.w("Workspace not found") - val profile = result.find { it.id == config.profile } - if (profile == null) Timber.w("Profile not found") - WorkspaceAndAccount.Account( - space = workspace?.let { - WorkspaceAndAccount.SpaceData( - name = workspace.name.orEmpty(), - icon = workspace.spaceIcon(urlBuilder, spaceGradientProvider) - ) - }, - profile = profile?.let { - WorkspaceAndAccount.ProfileData( - name = profile.name.orEmpty(), - icon = profile.profileIcon(urlBuilder) - ) - } - ) - }.catch { Timber.e(it, "Error while observing space and account") } - }.stateIn( - viewModelScope, - SharingStarted.WhileSubscribed(STOP_SUBSCRIPTION_TIMEOUT), - WorkspaceAndAccount.Idle - ) - - init { - events - .throttleFirst() - .onEach { event -> dispatchAnalyticEvent(event) } - .onEach { event -> dispatchCommand(event) } - .launchIn(viewModelScope) - - viewModelScope.launch { - analytics.sendEvent( - eventName = EventsDictionary.screenSettingSpacesSpaceIndex - ) - } - } - - fun onOptionClicked(event: Event) { - viewModelScope.launch { events.emit(event) } - } - - private suspend fun dispatchCommand(event: Event) { - when (event) { - Event.OnAboutClicked -> commands.emit(Command.OpenAboutScreen) - Event.OnProfileClicked -> commands.emit(Command.OpenProfileScreen) - Event.OnAppearanceClicked -> commands.emit(Command.OpenAppearanceScreen) - Event.OnPersonalizationClicked -> commands.emit(Command.OpenPersonalizationScreen) - Event.OnDebugClicked -> { - proceedWithSpaceDebug() - } - Event.OnSpaceImageClicked -> { - val config = spaceManager.getConfig() - if (config != null) { - commands.emit( - Command.OpenSpaceImageSet( - id = config.spaceView, - showRemoveButton = isShowRemoveButton() - ) - ) - } else { - commands.emit(Command.Toast(COULD_NOT_GET_CONFIG_ERROR)) - } - } - Event.OnFilesStorageClicked -> { - commands.emit(Command.OpenFilesStorageScreen) - } - } - } - - private fun isShowRemoveButton() : Boolean { - return (workspaceAndAccount.value as? WorkspaceAndAccount.Account) - ?.space?.icon !is SpaceIconView.Gradient - } - - private fun proceedWithSpaceDebug() { - viewModelScope.launch { - debugSpaceShareDownloader - .stream(Unit) - .collect { result -> - result.fold( - onLoading = { - commands.emit( - Command.Toast(SPACE_DEBUG_MSG, isLongDuration = true) - ) - }, - onSuccess = { path -> - commands.emit( - Command.ShareSpaceDebug(path) - ) - } - ) - } - } - } - - private fun dispatchAnalyticEvent(event: Event) { - when (event) { - Event.OnAboutClicked -> { - viewModelScope.sendEvent( - analytics = analytics, - eventName = EventsDictionary.MENU_HELP - ) - } - Event.OnProfileClicked -> { - viewModelScope.sendEvent( - analytics = analytics, - eventName = EventsDictionary.accountDataSettingsShow - ) - } - Event.OnAppearanceClicked -> { - viewModelScope.sendEvent( - analytics = analytics, - eventName = EventsDictionary.appearanceScreenShow - ) - } - Event.OnPersonalizationClicked -> { - viewModelScope.sendEvent( - analytics = analytics, - eventName = EventsDictionary.personalisationSettingsShow - ) - } - Event.OnSpaceImageClicked -> {} - Event.OnDebugClicked -> {} - Event.OnFilesStorageClicked -> {} - } - } - - override fun onCleared() { - super.onCleared() - viewModelScope.launch { - storelessSubscriptionContainer.unsubscribe( - listOf(SPACE_STORAGE_SUBSCRIPTION_ID) - ) - } - } - - fun onNameSet(name: String) { - Timber.d("onNameSet") - viewModelScope.launch { - val config = spaceManager.getConfig() - if (config != null) { - setSpaceDetails.async( - SetSpaceDetails.Params( - space = SpaceId(config.space), - details = mapOf(Relations.NAME to name) - ) - ).fold( - onFailure = { - Timber.e(it, "Error while updating object details") - }, - onSuccess = { - Timber.d("Name successfully set for current space: ${config.space}") - } - ) - } else { - Timber.w("Something went wrong: config is empty") - } - } - } - - class Factory @Inject constructor( - private val analytics: Analytics, - private val storelessSubscriptionContainer: StorelessSubscriptionContainer, - private val urlBuilder: UrlBuilder, - private val setSpaceDetails: SetSpaceDetails, - private val spaceGradientProvider: SpaceGradientProvider, - private val debugSpaceShareDownloader: DebugSpaceShareDownloader, - private val spaceManager: SpaceManager - ) : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun create( - modelClass: Class - ): T = MainSettingsViewModel( - analytics = analytics, - storelessSubscriptionContainer = storelessSubscriptionContainer, - urlBuilder = urlBuilder, - spaceGradientProvider = spaceGradientProvider, - debugSpaceShareDownloader = debugSpaceShareDownloader, - spaceManager = spaceManager, - setSpaceDetails = setSpaceDetails - ) as T - } - - sealed class Event { - object OnAboutClicked : Event() - object OnAppearanceClicked : Event() - object OnProfileClicked : Event() - object OnPersonalizationClicked : Event() - object OnDebugClicked : Event() - object OnSpaceImageClicked : Event() - object OnFilesStorageClicked : Event() - } - - sealed class Command { - object OpenAboutScreen : Command() - object OpenAppearanceScreen : Command() - object OpenProfileScreen : Command() - object OpenPersonalizationScreen : Command() - object OpenDebugScreen : Command() - class OpenSpaceImageSet(val id: Id, val showRemoveButton: Boolean) : Command() - object OpenFilesStorageScreen : Command() - data class Toast(val msg: String, val isLongDuration: Boolean = false) : Command() - data class ShareSpaceDebug(val path: Filepath): Command() - } - - sealed class WorkspaceAndAccount { - object Idle : WorkspaceAndAccount() - class Account( - val space: SpaceData?, - val profile: ProfileData? - ) : WorkspaceAndAccount() - - data class SpaceData( - val name: String, - val icon: SpaceIconView - ) - - data class ProfileData( - val name: String, - val icon: ProfileIconView, - ) - } - - companion object { - const val SPACE_DEBUG_MSG = "Kindly share this debug logs with Anytype developers." - const val COULD_NOT_GET_CONFIG_ERROR = "Could not get config. Please, try again later." - } -} - -const val SPACE_STORAGE_SUBSCRIPTION_ID = "settings_space_storage_subscription" -const val STOP_SUBSCRIPTION_TIMEOUT = 1_000L \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/spaces/SpaceSettingsViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/spaces/SpaceSettingsViewModel.kt index 6b8ed64bb..e1b65e9d3 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/spaces/SpaceSettingsViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/spaces/SpaceSettingsViewModel.kt @@ -46,7 +46,8 @@ class SpaceSettingsViewModel( private val urlBuilder: UrlBuilder, private val deleteSpace: DeleteSpace, private val configStorage: ConfigStorage, - private val debugSpaceShareDownloader: DebugSpaceShareDownloader + private val debugSpaceShareDownloader: DebugSpaceShareDownloader, + private val spaceGradientProvider: SpaceGradientProvider ): BaseViewModel() { val commands = MutableSharedFlow() @@ -165,6 +166,22 @@ class SpaceSettingsViewModel( proceedWithSpaceDebug() } + fun onRandomSpaceGradientClicked() { + viewModelScope.launch { + val config = spaceConfig + if (config != null) { + setSpaceDetails.async( + SetSpaceDetails.Params( + space = SpaceId(config.space), + details = mapOf( + Relations.ICON_OPTION to spaceGradientProvider.randomId().toDouble() + ) + ) + ) + } + } + } + fun onDeleteSpaceClicked() { viewModelScope.launch { analytics.sendEvent( @@ -274,7 +291,8 @@ class SpaceSettingsViewModel( private val spaceManager: SpaceManager, private val deleteSpace: DeleteSpace, private val configStorage: ConfigStorage, - private val debugFileShareDownloader: DebugSpaceShareDownloader + private val debugFileShareDownloader: DebugSpaceShareDownloader, + private val spaceGradientProvider: SpaceGradientProvider ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create( @@ -288,7 +306,8 @@ class SpaceSettingsViewModel( analytics = analytics, deleteSpace = deleteSpace, configStorage = configStorage, - debugSpaceShareDownloader = debugFileShareDownloader + debugSpaceShareDownloader = debugFileShareDownloader, + spaceGradientProvider = spaceGradientProvider ) as T } diff --git a/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/main/MainSettingScreen.kt b/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/main/MainSettingScreen.kt index a80687d7a..10589c8b7 100644 --- a/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/main/MainSettingScreen.kt +++ b/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/main/MainSettingScreen.kt @@ -1,179 +1,42 @@ package com.anytypeio.anytype.ui_settings.main -import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.MaterialTheme +import androidx.compose.material.DropdownMenu +import androidx.compose.material.DropdownMenuItem import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.colorResource -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.core.graphics.toColorInt -import coil.compose.rememberAsyncImagePainter -import com.anytypeio.anytype.core_ui.foundation.Arrow -import com.anytypeio.anytype.core_ui.foundation.Divider import com.anytypeio.anytype.core_ui.foundation.Dragger -import com.anytypeio.anytype.core_ui.foundation.Option import com.anytypeio.anytype.core_ui.views.BodyRegular -import com.anytypeio.anytype.presentation.profile.ProfileIconView -import com.anytypeio.anytype.presentation.settings.MainSettingsViewModel import com.anytypeio.anytype.presentation.spaces.SpaceIconView import com.anytypeio.anytype.ui_settings.R -@Deprecated("To be deleted") -@Composable -fun MainSettingScreen( - workspace: MainSettingsViewModel.WorkspaceAndAccount, - onSpaceIconClick: () -> Unit, - onProfileClicked: () -> Unit, - onAboutAppClicked: () -> Unit, - onDebugClicked: () -> Unit, - onPersonalizationClicked: () -> Unit, - onAppearanceClicked: () -> Unit, - onNameSet: (String) -> Unit, - onFileStorageClick: () -> Unit -) { - Column(Modifier.fillMaxSize()) { - SpaceHeader( - modifier = Modifier.align(Alignment.CenterHorizontally), - workspace = workspace, - onSpaceIconClick = onSpaceIconClick, - onNameSet = onNameSet - ) - Spacer( - modifier = Modifier - .height(10.dp) - .padding(top = 4.dp) - ) - Divider() - Spacer(modifier = Modifier.height(26.dp)) - Settings( - onProfileClicked = onProfileClicked, - onPersonalizationClicked = onPersonalizationClicked, - onAppearanceClicked = onAppearanceClicked, - onAboutAppClicked = onAboutAppClicked, - onDebugClicked = onDebugClicked, - accountData = workspace, - onFileStorageClick = onFileStorageClick - ) - Box(modifier = Modifier.height(16.dp)) - } -} - -@Composable -private fun Settings( - onProfileClicked: () -> Unit, - onPersonalizationClicked: () -> Unit, - onAppearanceClicked: () -> Unit, - onAboutAppClicked: () -> Unit, - onDebugClicked: () -> Unit, - accountData: MainSettingsViewModel.WorkspaceAndAccount, - onFileStorageClick: () -> Unit -) { - Section( - modifier = Modifier.padding(start = 20.dp, bottom = 4.dp), - title = stringResource(id = R.string.settings) - ) - ProfileOption( - data = (accountData as? MainSettingsViewModel.WorkspaceAndAccount.Account)?.profile, - text = stringResource(R.string.profile), - onClick = onProfileClicked - ) - Divider(paddingStart = 60.dp) - Option( - image = R.drawable.ic_personalization, - text = stringResource(R.string.personalization), - onClick = onPersonalizationClicked - ) - Divider(paddingStart = 60.dp) - Option( - image = R.drawable.ic_appearance, - text = stringResource(R.string.appearance), - onClick = onAppearanceClicked - ) - Divider(paddingStart = 60.dp) - Option( - image = R.drawable.ic_file_storage, - text = stringResource(R.string.file_storage), - onClick = onFileStorageClick - ) - Divider(paddingStart = 60.dp) - Option( - image = R.drawable.ic_debug, - text = stringResource(R.string.space_debug), - onClick = onDebugClicked - ) - Divider(paddingStart = 60.dp) - Option( - image = R.drawable.ic_about, - text = stringResource(R.string.about), - onClick = onAboutAppClicked - ) - Divider(paddingStart = 60.dp) -} - -@Composable -fun SpaceHeader( - modifier: Modifier = Modifier, - workspace: MainSettingsViewModel.WorkspaceAndAccount, - onSpaceIconClick: () -> Unit, - onNameSet: (String) -> Unit -) { - when (workspace) { - is MainSettingsViewModel.WorkspaceAndAccount.Account -> { - Box(modifier = modifier.padding(vertical = 6.dp)) { - Dragger() - } - Box(modifier = modifier.padding(top = 12.dp, bottom = 28.dp)) { - SpaceNameBlock() - } - Box(modifier = modifier.padding(bottom = 16.dp)) { - workspace.space?.icon?.let { - SpaceImageBlock( - icon = it, - onSpaceIconClick = onSpaceIconClick - ) - } - } - workspace.space?.name?.let { -// SpaceNameBlock( -// name = it, -// onNameSet = onNameSet -// ) - } - } - is MainSettingsViewModel.WorkspaceAndAccount.Idle -> {} - } -} - @Composable fun SpaceHeader( name: String?, icon: SpaceIconView?, modifier: Modifier = Modifier, - onSpaceIconClick: () -> Unit, - onNameSet: (String) -> Unit + onNameSet: (String) -> Unit, + onRandomGradientClicked: () -> Unit ) { + val isSpaceIconMenuExpanded = remember { + mutableStateOf(false) + } Box(modifier = modifier.padding(vertical = 6.dp)) { Dragger() } @@ -184,9 +47,31 @@ fun SpaceHeader( if (icon != null) { SpaceImageBlock( icon = icon, - onSpaceIconClick = onSpaceIconClick, + onSpaceIconClick = { + isSpaceIconMenuExpanded.value = !isSpaceIconMenuExpanded.value + }, gradientCornerRadius = 4.dp ) + DropdownMenu( + expanded = isSpaceIconMenuExpanded.value, + offset = DpOffset(x = 0.dp, y = 6.dp), + onDismissRequest = { + isSpaceIconMenuExpanded.value = false + } + ) { + DropdownMenuItem( + onClick = { + onRandomGradientClicked() + isSpaceIconMenuExpanded.value = false + }, + ) { + Text( + text = stringResource(R.string.space_settings_apply_random_gradient), + style = BodyRegular, + color = colorResource(id = R.color.text_primary) + ) + } + } } } if (name != null) { @@ -198,87 +83,6 @@ fun SpaceHeader( } } -@Composable -fun ProfileOption( - data: MainSettingsViewModel.WorkspaceAndAccount.ProfileData?, - text: String, - onClick: () -> Unit = {} -) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .height(52.dp) - .clickable(onClick = onClick) - - ) { - data?.let { - when (val icon = it.icon) { - is ProfileIconView.Image -> { - Image( - painter = rememberAsyncImagePainter( - model = icon.url, - error = painterResource(id = R.drawable.ic_home_widget_space) - ), - contentDescription = "Custom image profile", - contentScale = ContentScale.Crop, - modifier = Modifier - .padding(start = 20.dp) - .size(28.dp) - .clip(RoundedCornerShape(14.dp)) - ) - } - else -> { - val nameFirstChar = if (data.name.isEmpty()) { - stringResource(id = R.string.account_default_name) - } else { - data.name.first().uppercaseChar().toString() - } - Box( - modifier = Modifier - .padding(start = 20.dp) - .size(28.dp) - .clip(RoundedCornerShape(14.dp)) - .background(colorResource(id = R.color.shape_primary)) - ) { - Text( - text = nameFirstChar, - style = MaterialTheme.typography.h3.copy( - color = colorResource(id = R.color.text_white), - fontSize = 14.sp - ), - modifier = Modifier.align(Alignment.Center) - ) - } - - } - } - } ?: kotlin.run { - Image( - painterResource(R.drawable.ic_account_and_data), - contentDescription = "Option icon", - modifier = Modifier.padding( - start = 20.dp - ) - ) - } - - Text( - text = text, - color = colorResource(R.color.text_primary), - modifier = Modifier.padding( - start = 12.dp - ), - style = BodyRegular - ) - Box( - modifier = Modifier.weight(1.0f, true), - contentAlignment = Alignment.CenterEnd - ) { - Arrow() - } - } -} - @Composable fun GradientComposeView( modifier: Modifier, diff --git a/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/space/SpaceSettingsScreen.kt b/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/space/SpaceSettingsScreen.kt deleted file mode 100644 index f60319b61..000000000 --- a/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/space/SpaceSettingsScreen.kt +++ /dev/null @@ -1,65 +0,0 @@ -package com.anytypeio.anytype.ui_settings.space - -import androidx.compose.foundation.layout.padding -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import com.anytypeio.anytype.core_ui.foundation.Divider -import com.anytypeio.anytype.core_ui.foundation.Option -import com.anytypeio.anytype.presentation.settings.MainSettingsViewModel -import com.anytypeio.anytype.ui_settings.R -import com.anytypeio.anytype.ui_settings.main.ProfileOption -import com.anytypeio.anytype.ui_settings.main.Section - -@Composable -private fun Settings( - onProfileClicked: () -> Unit, - onPersonalizationClicked: () -> Unit, - onAppearanceClicked: () -> Unit, - onAboutAppClicked: () -> Unit, - onDebugClicked: () -> Unit, - accountData: MainSettingsViewModel.WorkspaceAndAccount, - onFileStorageClick: () -> Unit -) { - Section( - modifier = Modifier.padding(start = 20.dp, bottom = 4.dp), - title = stringResource(id = R.string.space_settings) - ) - ProfileOption( - data = (accountData as? MainSettingsViewModel.WorkspaceAndAccount.Account)?.profile, - text = stringResource(R.string.profile), - onClick = onProfileClicked - ) - Divider(paddingStart = 60.dp) - Option( - image = R.drawable.ic_personalization, - text = stringResource(R.string.personalization), - onClick = onPersonalizationClicked - ) - Divider(paddingStart = 60.dp) - Option( - image = R.drawable.ic_appearance, - text = stringResource(R.string.appearance), - onClick = onAppearanceClicked - ) - Divider(paddingStart = 60.dp) - Option( - image = R.drawable.ic_file_storage, - text = stringResource(R.string.file_storage), - onClick = onFileStorageClick - ) - Divider(paddingStart = 60.dp) - Option( - image = R.drawable.ic_debug, - text = stringResource(R.string.space_debug), - onClick = onDebugClicked - ) - Divider(paddingStart = 60.dp) - Option( - image = R.drawable.ic_about, - text = stringResource(R.string.about), - onClick = onAboutAppClicked - ) - Divider(paddingStart = 60.dp) -} \ No newline at end of file