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 index c9e60e94c3..bc0700a8bd 100644 --- 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 @@ -2,6 +2,13 @@ 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.domain.base.AppCoroutineDispatchers +import com.anytypeio.anytype.domain.block.repo.BlockRepository +import com.anytypeio.anytype.domain.config.ConfigStorage +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.search.SubscriptionEventChannel import com.anytypeio.anytype.presentation.settings.MainSettingsViewModel import com.anytypeio.anytype.ui.settings.MainSettingFragment import dagger.Module @@ -24,10 +31,44 @@ interface MainSettingsSubComponent { @Module object MainSettingsModule { + @JvmStatic + @Provides + @PerScreen + fun provideStoreLessSubscriptionContainer( + repo: BlockRepository, + channel: SubscriptionEventChannel, + dispatchers: AppCoroutineDispatchers + ): StorelessSubscriptionContainer = StorelessSubscriptionContainer.Impl( + repo = repo, + channel = channel, + dispatchers = dispatchers + ) + + @JvmStatic + @Provides + @PerScreen + fun provideSetObjectDetails( + repo: BlockRepository, + dispatchers: AppCoroutineDispatchers + ): SetObjectDetails = SetObjectDetails( + repo = repo, + dispatchers = dispatchers + ) + @JvmStatic @Provides @PerScreen fun provideViewModelFactory( - analytics: Analytics - ): MainSettingsViewModel.Factory = MainSettingsViewModel.Factory(analytics) + analytics: Analytics, + storelessSubscriptionContainer: StorelessSubscriptionContainer, + configStorage: ConfigStorage, + urlBuilder: UrlBuilder, + setObjectDetails: SetObjectDetails + ): MainSettingsViewModel.Factory = MainSettingsViewModel.Factory( + analytics, + storelessSubscriptionContainer, + configStorage, + urlBuilder, + setObjectDetails + ) } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreen.kt b/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreen.kt index f2a5858a6a..eae7a89f06 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreen.kt @@ -35,7 +35,9 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip 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 @@ -50,7 +52,7 @@ import com.anytypeio.anytype.core_ui.extensions.throttledClick import com.anytypeio.anytype.core_ui.foundation.noRippleClickable import com.anytypeio.anytype.emojifier.Emojifier import com.anytypeio.anytype.presentation.home.InteractionMode -import com.anytypeio.anytype.presentation.spaces.SpaceIcon +import com.anytypeio.anytype.presentation.spaces.SpaceIconView import com.anytypeio.anytype.presentation.widgets.DropDownMenuAction import com.anytypeio.anytype.presentation.widgets.FromIndex import com.anytypeio.anytype.presentation.widgets.ToIndex @@ -74,7 +76,7 @@ import timber.log.Timber @Composable fun HomeScreen( - spaceIcon: SpaceIcon, + spaceIconView: SpaceIconView, mode: InteractionMode, widgets: List, onExpand: (TreePath) -> Unit, @@ -144,7 +146,7 @@ fun HomeScreen( exit = fadeOut() + slideOutVertically { it } ) { HomeScreenBottomToolbar( - spaceIcon = spaceIcon, + spaceIconView = spaceIconView, onSearchClicked = throttledClick(onSearchClicked), onCreateNewObjectClicked = throttledClick(onCreateNewObjectClicked), onSpaceClicked = throttledClick(onSpaceClicked), @@ -503,7 +505,7 @@ fun HomeScreenButton( @Composable fun HomeScreenBottomToolbar( - spaceIcon: SpaceIcon, + spaceIconView: SpaceIconView, modifier: Modifier, onSearchClicked: () -> Unit, onCreateNewObjectClicked: () -> Unit, @@ -548,12 +550,12 @@ fun HomeScreenBottomToolbar( .fillMaxSize() .noRippleClickable { onSpaceClicked() } ) { - Timber.d("Binding icon: $spaceIcon") - when(spaceIcon) { - is SpaceIcon.Emoji -> { + Timber.d("Binding icon: $spaceIconView") + when(spaceIconView) { + is SpaceIconView.Emoji -> { Image( painter = rememberAsyncImagePainter( - model = Emojifier.uri(spaceIcon.unicode), + model = Emojifier.uri(spaceIconView.unicode), error = painterResource(id = R.drawable.ic_home_widget_space) ), contentDescription = "Emoji space icon", @@ -562,16 +564,18 @@ fun HomeScreenBottomToolbar( .align(Alignment.Center) ) } - is SpaceIcon.Image -> { + is SpaceIconView.Image -> { Image( painter = rememberAsyncImagePainter( - model = spaceIcon.url, + model = spaceIconView.url, error = painterResource(id = R.drawable.ic_home_widget_space) ), contentDescription = "Custom image space icon", modifier = Modifier .size(24.dp) - .align(Alignment.Center) + .clip(RoundedCornerShape(3.dp)) + .align(Alignment.Center), + contentScale = ContentScale.Crop ) } else -> { diff --git a/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreenFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreenFragment.kt index eda16d4f8c..f8b41a3b7a 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreenFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/home/HomeScreenFragment.kt @@ -60,7 +60,7 @@ class HomeScreenFragment : BaseComposeFragment() { ) ) { HomeScreen( - spaceIcon = vm.icon.collectAsState().value, + spaceIconView = vm.icon.collectAsState().value, widgets = vm.views.collectAsState().value, mode = vm.mode.collectAsState().value, onExpand = { path -> vm.onExpand(path) }, 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 index f5faa33318..2f86de07da 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/settings/MainSettingFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/settings/MainSettingFragment.kt @@ -8,18 +8,22 @@ 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.unit.dp +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.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.ui.editor.modals.IconPickerFragmentBase.Companion.ARG_CONTEXT_ID_KEY import com.anytypeio.anytype.ui.settings.system.SettingsActivity import com.anytypeio.anytype.ui_settings.main.MainSettingScreen import com.google.android.material.bottomsheet.BottomSheetBehavior @@ -57,24 +61,36 @@ class MainSettingFragment : BaseBottomSheetComposeFragment() { vm.onOptionClicked(Event.OnDebugClicked) } + private val onSpaceImageClicked = { + vm.onOptionClicked(Event.OnSpaceImageClicked) + } + + private val onNameSet = { name: String -> + vm.onNameSet(name) + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View { - return ComposeView(requireContext()).apply { - setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) - setContent { - MaterialTheme(typography = typography) { - MainSettingScreen( - onAccountAndDataClicked = onAccountAndDataClicked, - onAboutAppClicked = onAboutAppClicked, - onAppearanceClicked = onAppearanceClicked, - onDebugClicked = onDebugClicked, - onPersonalizationClicked = onPersonalizationClicked, - showDebugMenu = featureToggles.isTroubleshootingMode - ) - } + ) = ComposeDialogView( + context = requireContext(), + dialog = requireDialog() + ).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + MaterialTheme(typography = typography) { + MainSettingScreen( + workspace = vm.workspaceData.collectAsStateWithLifecycle().value, + onAccountAndDataClicked = onAccountAndDataClicked, + onAboutAppClicked = onAboutAppClicked, + onAppearanceClicked = onAppearanceClicked, + onDebugClicked = onDebugClicked, + onPersonalizationClicked = onPersonalizationClicked, + showDebugMenu = featureToggles.isTroubleshootingMode, + onSpaceIconClick = onSpaceImageClicked, + onNameSet = onNameSet + ) } } } @@ -99,21 +115,28 @@ class MainSettingFragment : BaseBottomSheetComposeFragment() { private fun processCommands(command: Command) { when (command) { - Command.OpenAboutScreen -> { + is Command.OpenAboutScreen -> { safeNavigate(R.id.actionOpenAboutAppScreen) } - Command.OpenAccountAndDataScreen -> { + is Command.OpenAccountAndDataScreen -> { safeNavigate(R.id.actionOpenAccountAndDataScreen) } - Command.OpenAppearanceScreen -> { + is Command.OpenAppearanceScreen -> { safeNavigate(R.id.actionOpenAppearanceScreen) } - Command.OpenPersonalizationScreen -> { + is Command.OpenPersonalizationScreen -> { safeNavigate(R.id.actionOpenPersonalizationScreen) } - Command.OpenDebugScreen -> { + is Command.OpenDebugScreen -> { startActivity(Intent(requireActivity(), SettingsActivity::class.java)) } + is Command.OpenSpaceImageSet -> { + safeNavigate( + R.id.actionOpenImagePickerScreen, bundleOf( + ARG_CONTEXT_ID_KEY to command.id + ) + ) + } } } diff --git a/app/src/main/res/navigation/graph.xml b/app/src/main/res/navigation/graph.xml index ee38101fea..0fb7ae25bc 100644 --- a/app/src/main/res/navigation/graph.xml +++ b/app/src/main/res/navigation/graph.xml @@ -165,6 +165,7 @@ + + + diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/library/StorelessSubscriptionContainer.kt b/domain/src/main/java/com/anytypeio/anytype/domain/library/StorelessSubscriptionContainer.kt index 2a4b0619f5..8fa0b50006 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/library/StorelessSubscriptionContainer.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/library/StorelessSubscriptionContainer.kt @@ -74,7 +74,10 @@ interface StorelessSubscriptionContainer { subscription = subscription, ids = targets, keys = keys - ).results.map { SubscriptionObject(it.id, it) }.toMutableList() + ).results.map { + println() + SubscriptionObject(it.id, it) + }.toMutableList() emitAll( buildObjectsFlow( subscription = searchParams.subscription, diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt index 6b42e055d2..c88562076c 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/home/HomeScreenViewModel.kt @@ -40,7 +40,7 @@ import com.anytypeio.anytype.domain.widgets.UpdateWidget import com.anytypeio.anytype.presentation.home.Command.ChangeWidgetType.Companion.UNDEFINED_LAYOUT_CODE import com.anytypeio.anytype.presentation.navigation.NavigationViewModel import com.anytypeio.anytype.presentation.search.Subscriptions -import com.anytypeio.anytype.presentation.spaces.SpaceIcon +import com.anytypeio.anytype.presentation.spaces.SpaceIconView import com.anytypeio.anytype.presentation.spaces.spaceIcon import com.anytypeio.anytype.presentation.util.Dispatcher import com.anytypeio.anytype.presentation.widgets.BundledWidgetSourceIds @@ -117,7 +117,7 @@ class HomeScreenViewModel( // Bundled widget containing archived objects private val bin = WidgetView.Bin(Subscriptions.SUBSCRIPTION_ARCHIVED) - val icon = MutableStateFlow(SpaceIcon.Loading) + val icon = MutableStateFlow(SpaceIconView.Loading) init { val config = configStorage.get() @@ -260,7 +260,7 @@ class HomeScreenViewModel( ) ).map { result -> val obj = result.firstOrNull() - obj?.spaceIcon(urlBuilder) ?: SpaceIcon.Placeholder + obj?.spaceIcon(urlBuilder) ?: SpaceIconView.Placeholder }.flowOn(appCoroutineDispatchers.io).collect { icon.value = it } 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 index 86d580a80f..600d302fce 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/settings/MainSettingsViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/settings/MainSettingsViewModel.kt @@ -6,20 +6,60 @@ 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.Id +import com.anytypeio.anytype.core_models.Relations import com.anytypeio.anytype.core_utils.ext.throttleFirst +import com.anytypeio.anytype.domain.base.fold +import com.anytypeio.anytype.domain.config.ConfigStorage +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.`object`.SetObjectDetails +import com.anytypeio.anytype.presentation.spaces.SpaceIconView +import com.anytypeio.anytype.presentation.spaces.spaceIcon import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.SharingStarted 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 class MainSettingsViewModel( - private val analytics: Analytics + private val analytics: Analytics, + private val storelessSubscriptionContainer: StorelessSubscriptionContainer, + private val configStorage: ConfigStorage, + private val urlBuilder: UrlBuilder, + private val setObjectDetails: SetObjectDetails ) : ViewModel() { val events = MutableSharedFlow(replay = 0) val commands = MutableSharedFlow(replay = 0) + val workspaceData = storelessSubscriptionContainer.subscribe( + StoreSearchByIdsParams( + subscription = SPACE_SUBSCRIPTION_ID, + targets = listOf(configStorage.get().workspace), + keys = listOf( + Relations.ID, + Relations.NAME, + Relations.ICON_EMOJI, + Relations.ICON_IMAGE + ) + ) + ).map { result -> + val obj = result.firstOrNull() + WorkspaceData.Data( + name = obj?.name ?: "", + icon = obj?.spaceIcon(urlBuilder) ?: SpaceIconView.Placeholder + ) + }.stateIn( + viewModelScope, + SharingStarted.WhileSubscribed(STOP_SUBSCRIPTION_TIMEOUT), + WorkspaceData.Idle + ) + init { events .throttleFirst() @@ -39,6 +79,9 @@ class MainSettingsViewModel( Event.OnAppearanceClicked -> commands.emit(Command.OpenAppearanceScreen) Event.OnPersonalizationClicked -> commands.emit(Command.OpenPersonalizationScreen) Event.OnDebugClicked -> commands.emit(Command.OpenDebugScreen) + Event.OnSpaceImageClicked -> commands.emit(Command.OpenSpaceImageSet( + configStorage.get().workspace + )) } } @@ -68,18 +111,56 @@ class MainSettingsViewModel( eventName = EventsDictionary.personalisationSettingsShow ) } + Event.OnSpaceImageClicked -> {} Event.OnDebugClicked -> {} } } + override fun onCleared() { + super.onCleared() + viewModelScope.launch { + storelessSubscriptionContainer.unsubscribe( + listOf(SPACE_SUBSCRIPTION_ID) + ) + } + } + + fun onNameSet(name: String) { + viewModelScope.launch { + setObjectDetails.execute( + SetObjectDetails.Params( + ctx = configStorage.get().workspace, + details = mapOf( + Relations.NAME to name + ) + ) + ).fold( + onFailure = { + Timber.e(it, "Error while updating object details") + }, + onSuccess = { + // do nothing + } + ) + } + } + class Factory( - private val analytics: Analytics + private val analytics: Analytics, + private val storelessSubscriptionContainer: StorelessSubscriptionContainer, + private val configStorage: ConfigStorage, + private val urlBuilder: UrlBuilder, + private val setObjectDetails: SetObjectDetails ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create( modelClass: Class ): T = MainSettingsViewModel( - analytics = analytics + analytics = analytics, + storelessSubscriptionContainer = storelessSubscriptionContainer, + configStorage = configStorage, + urlBuilder = urlBuilder, + setObjectDetails = setObjectDetails ) as T } @@ -89,6 +170,7 @@ class MainSettingsViewModel( object OnAccountAndDataClicked : Event() object OnPersonalizationClicked : Event() object OnDebugClicked : Event() + object OnSpaceImageClicked : Event() } sealed class Command { @@ -97,5 +179,18 @@ class MainSettingsViewModel( object OpenAccountAndDataScreen : Command() object OpenPersonalizationScreen : Command() object OpenDebugScreen : Command() + class OpenSpaceImageSet(val id: Id) : Command() } -} \ No newline at end of file + + sealed class WorkspaceData { + object Idle : WorkspaceData() + class Data( + val name: String, + val icon: SpaceIconView + ) : WorkspaceData() + } + +} + +private const val SPACE_SUBSCRIPTION_ID = "settings_space_subscription" +private 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/SpaceIcon.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/spaces/SpaceIconView.kt similarity index 55% rename from presentation/src/main/java/com/anytypeio/anytype/presentation/spaces/SpaceIcon.kt rename to presentation/src/main/java/com/anytypeio/anytype/presentation/spaces/SpaceIconView.kt index 766aa788d0..f63409b318 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/spaces/SpaceIcon.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/spaces/SpaceIconView.kt @@ -4,21 +4,21 @@ import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.core_models.Url import com.anytypeio.anytype.domain.misc.UrlBuilder -sealed class SpaceIcon { - object Loading : SpaceIcon() - object Placeholder : SpaceIcon() - data class Emoji(val unicode: String) : SpaceIcon() - data class Image(val url: Url) : SpaceIcon() +sealed class SpaceIconView { + object Loading : SpaceIconView() + object Placeholder : SpaceIconView() + data class Emoji(val unicode: String) : SpaceIconView() + data class Image(val url: Url) : SpaceIconView() } -fun ObjectWrapper.Basic.spaceIcon(builder: UrlBuilder): SpaceIcon = when { +fun ObjectWrapper.Basic.spaceIcon(builder: UrlBuilder): SpaceIconView = when { !iconEmoji.isNullOrEmpty() -> { val emoji = checkNotNull(iconEmoji) - SpaceIcon.Emoji(emoji) + SpaceIconView.Emoji(emoji) } !iconImage.isNullOrEmpty() -> { val hash = checkNotNull(iconImage) - SpaceIcon.Image(builder.thumbnail(hash)) + SpaceIconView.Image(builder.thumbnail(hash)) } - else -> SpaceIcon.Placeholder + else -> SpaceIconView.Placeholder } \ No newline at end of file diff --git a/ui-settings/build.gradle b/ui-settings/build.gradle index dfd26c78c9..57cbef7977 100644 --- a/ui-settings/build.gradle +++ b/ui-settings/build.gradle @@ -22,6 +22,8 @@ dependencies { implementation project(':analytics') implementation project(':core-models') implementation project(':core-utils') + implementation project(':presentation') + implementation project(':library-emojifier') compileOnly libs.javaxInject @@ -34,6 +36,8 @@ dependencies { implementation libs.composeMaterial implementation libs.composeToolingPreview + implementation libs.coilCompose + debugImplementation libs.composeTooling implementation libs.timber 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 7cb4122b90..3abb72c77a 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 @@ -14,20 +14,29 @@ import androidx.compose.ui.unit.dp 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.presentation.settings.MainSettingsViewModel import com.anytypeio.anytype.ui_settings.R @Composable fun MainSettingScreen( + workspace: MainSettingsViewModel.WorkspaceData, + onSpaceIconClick: () -> Unit, onAccountAndDataClicked: () -> Unit, onAboutAppClicked: () -> Unit, onDebugClicked: () -> Unit, onPersonalizationClicked: () -> Unit, onAppearanceClicked: () -> Unit, + onNameSet: (String) -> Unit, showDebugMenu: Boolean ) { Column(Modifier.fillMaxSize()) { - Header(Modifier.align(Alignment.CenterHorizontally)) - Spacer(modifier = Modifier.height(10.dp)) + Header( + 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( @@ -90,15 +99,28 @@ private fun Settings( } @Composable -private fun Header(modifier: Modifier = Modifier) { - Box(modifier = modifier.padding(vertical = 6.dp)) { - Dragger() +private fun Header( + modifier: Modifier = Modifier, + workspace: MainSettingsViewModel.WorkspaceData, + onSpaceIconClick: () -> Unit, + onNameSet: (String) -> Unit +) { + when (workspace) { + is MainSettingsViewModel.WorkspaceData.Data -> { + 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)) { + SpaceImageBlock( + icon = workspace.icon, + onSpaceIconClick = onSpaceIconClick + ) + } + NameBlock(name = workspace.name, onNameSet = onNameSet) + } + is MainSettingsViewModel.WorkspaceData.Idle -> {} } - Box(modifier = modifier.padding(top = 12.dp, bottom = 28.dp)) { - SpaceNameBlock() - } - Box(modifier = modifier) { - SpaceImageBlock(Modifier) - } - NameBlock(name = "Personal") } \ No newline at end of file diff --git a/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/main/Views.kt b/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/main/Views.kt index f8f7b0cc92..58428b9abc 100644 --- a/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/main/Views.kt +++ b/ui-settings/src/main/java/com/anytypeio/anytype/ui_settings/main/Views.kt @@ -1,18 +1,42 @@ package com.anytypeio.anytype.ui_settings.main import androidx.compose.foundation.Image +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.MaterialTheme import androidx.compose.material.Text +import androidx.compose.material.TextFieldDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import coil.compose.rememberAsyncImagePainter +import com.anytypeio.anytype.core_ui.foundation.noRippleClickable +import com.anytypeio.anytype.emojifier.Emojifier +import com.anytypeio.anytype.presentation.spaces.SpaceIconView import com.anytypeio.anytype.ui_settings.R @Composable @@ -25,19 +49,80 @@ fun Section(modifier: Modifier = Modifier, title: String) { ) } +@OptIn(ExperimentalMaterialApi::class) @Composable -fun NameBlock(modifier: Modifier = Modifier, name: String) { +fun NameBlock( + modifier: Modifier = Modifier, + name: String, + onNameSet: (String) -> Unit +) { + + val nameValue = remember { mutableStateOf(name) } + val focusManager = LocalFocusManager.current + Column(modifier = modifier.padding(start = 20.dp)) { Text( text = "Name", color = colorResource(id = R.color.text_secondary), fontSize = 13.sp ) - Text( - text = name, - style = MaterialTheme.typography.h2, + BasicTextField( + value = nameValue.value, + onValueChange = { + nameValue.value = it + }, + modifier = Modifier.padding(top = 4.dp, end = 20.dp), + enabled = true, + textStyle = TextStyle( + fontSize = 22.sp, + fontWeight = FontWeight.SemiBold, + color = colorResource(id = R.color.text_primary) + ), + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Text, + imeAction = ImeAction.Done + ), + keyboardActions = KeyboardActions( + onDone = { + onNameSet.invoke(nameValue.value) + focusManager.clearFocus() + } + ), + singleLine = true, + decorationBox = @Composable { innerTextField -> + TextFieldDefaults.OutlinedTextFieldDecorationBox( + value = nameValue.value, + innerTextField = innerTextField, + singleLine = true, + enabled = true, + isError = false, + placeholder = { + Text(text = "Space name") + }, + colors = TextFieldDefaults.outlinedTextFieldColors( + textColor = colorResource(id = R.color.text_primary), + backgroundColor = Color.Transparent, + disabledBorderColor = Color.Transparent, + errorBorderColor = Color.Transparent, + focusedBorderColor = Color.Transparent, + unfocusedBorderColor = Color.Transparent, + placeholderColor = colorResource(id = R.color.text_tertiary), + ), + contentPadding = PaddingValues( + start = 0.dp, + top = 0.dp, + end = 0.dp, + bottom = 0.dp + ), + border = {}, + interactionSource = remember { MutableInteractionSource() }, + visualTransformation = VisualTransformation.None + ) + } ) } + + } @Composable @@ -45,17 +130,50 @@ fun SpaceNameBlock(modifier: Modifier = Modifier) { Text( text = "Space", style = MaterialTheme.typography.h3, + color = colorResource(id = R.color.text_primary) ) } @Composable -fun SpaceImageBlock(modifier: Modifier = Modifier) { - Image( - modifier = modifier - .height(96.dp) - .width(96.dp) - .padding(bottom = 21.dp), - painter = painterResource(id = R.drawable.ic_home_widget_space), - contentDescription = "space_image" - ) +fun SpaceImageBlock(icon: SpaceIconView, onSpaceIconClick: () -> Unit) { + + val spaceImageModifier = Modifier + .size(96.dp) + .clip(RoundedCornerShape(8.dp)) + .noRippleClickable { + onSpaceIconClick.invoke() + } + + when (icon) { + is SpaceIconView.Emoji -> { + Image( + painter = rememberAsyncImagePainter( + model = Emojifier.uri(icon.unicode), + error = painterResource(id = R.drawable.ic_home_widget_space) + ), + contentDescription = "Emoji space icon", + modifier = spaceImageModifier, + contentScale = ContentScale.Crop + ) + } + is SpaceIconView.Image -> { + Image( + painter = rememberAsyncImagePainter( + model = icon.url, + error = painterResource(id = R.drawable.ic_home_widget_space) + ), + contentDescription = "Custom image space icon", + contentScale = ContentScale.Crop, + modifier = spaceImageModifier + ) + } + else -> { + Image( + painter = painterResource(id = R.drawable.ic_home_widget_space), + contentDescription = "Placeholder space icon", + contentScale = ContentScale.Crop, + modifier = spaceImageModifier + ) + } + } } \ No newline at end of file diff --git a/ui-settings/src/main/res/drawable/ic_about.xml b/ui-settings/src/main/res/drawable/ic_about.xml index 57a2784e74..966dce5dfd 100644 --- a/ui-settings/src/main/res/drawable/ic_about.xml +++ b/ui-settings/src/main/res/drawable/ic_about.xml @@ -4,15 +4,24 @@ android:viewportWidth="28" android:viewportHeight="28"> + + +