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

DROID-1013 Settings | Enhancement | Space image & name editing (#3043)

DROID-1013 Settings | Enhancement | Space image & name editing
This commit is contained in:
Allan Quatermain 2023-03-27 13:47:17 +03:00 committed by GitHub
parent 34bc1f864c
commit e14ef8b66b
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 408 additions and 83 deletions

View file

@ -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
)
}

View file

@ -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<WidgetView>,
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 -> {

View file

@ -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) },

View file

@ -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
)
)
}
}
}

View file

@ -165,6 +165,7 @@
<action android:id="@+id/actionOpenAboutAppScreen" app:destination="@id/aboutAppScreen"/>
<action android:id="@+id/actionOpenPersonalizationScreen" app:destination="@+id/personalizationScreen"/>
<action android:id="@+id/actionOpenAppearanceScreen" app:destination="@+id/appearanceScreen"/>
<action android:id="@+id/actionOpenImagePickerScreen" app:destination="@+id/objectSetIconPickerScreenForSpace"/>
<action
android:id="@+id/action_profileScreen_to_splashFragment"
app:destination="@+id/main_navigation"
@ -192,6 +193,11 @@
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
</dialog>
<dialog
android:id="@+id/objectSetIconPickerScreenForSpace"
android:name="com.anytypeio.anytype.ui.sets.ObjectSetIconPickerFragment"
android:label="Object-Set-Icon-Picker-Screen" />
<dialog
android:id="@+id/accountAndDataScreen"
android:name="com.anytypeio.anytype.ui.settings.AccountAndDataFragment">

View file

@ -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,

View file

@ -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>(SpaceIcon.Loading)
val icon = MutableStateFlow<SpaceIconView>(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
}

View file

@ -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<Event>(replay = 0)
val commands = MutableSharedFlow<Command>(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 <T : ViewModel> create(
modelClass: Class<T>
): 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()
}
}
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

View file

@ -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
}

View file

@ -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

View file

@ -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")
}

View file

@ -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
)
}
}
}

View file

@ -4,15 +4,24 @@
android:viewportWidth="28"
android:viewportHeight="28">
<path
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"
android:pathData="M6.025,0L22.025,0A6,6 0,0 1,28.025 6L28.025,22A6,6 0,0 1,22.025 28L6.025,28A6,6 0,0 1,0.025 22L0.025,6A6,6 0,0 1,6.025 0z"
android:fillColor="#FFB522"/>
<path
android:pathData="M11,22C13.7614,22 16,19.7614 16,17C16,14.2386 13.7614,12 11,12C8.2386,12 6,14.2386 6,17C6,19.7614 8.2386,22 11,22Z"
android:pathData="M13.275,5.75C13.275,5.336 13.611,5 14.025,5C14.44,5 14.775,5.336 14.775,5.75V22.25C14.775,22.664 14.44,23 14.025,23C13.611,23 13.275,22.664 13.275,22.25V5.75Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M18,9h3v13h-3z"
android:pathData="M5.775,14.75C5.361,14.75 5.025,14.414 5.025,14C5.025,13.586 5.361,13.25 5.775,13.25L22.275,13.25C22.69,13.25 23.025,13.586 23.025,14C23.025,14.414 22.69,14.75 22.275,14.75L5.775,14.75Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M18,6V9H8.5V6L18,6Z"
android:pathData="M9.251,7.23C9.044,6.871 9.167,6.413 9.526,6.206C9.884,5.998 10.343,6.121 10.55,6.48L18.8,20.77C19.007,21.128 18.884,21.587 18.525,21.794C18.167,22.001 17.708,21.878 17.501,21.52L9.251,7.23Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M7.256,18.774C6.897,18.982 6.439,18.859 6.231,18.5C6.024,18.141 6.147,17.683 6.506,17.475L20.795,9.225C21.154,9.018 21.613,9.141 21.82,9.5C22.027,9.859 21.904,10.317 21.545,10.524L7.256,18.774Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M6.506,10.524C6.147,10.317 6.024,9.859 6.231,9.5C6.439,9.141 6.897,9.018 7.256,9.225L21.545,17.475C21.904,17.683 22.027,18.141 21.82,18.5C21.613,18.859 21.154,18.982 20.795,18.774L6.506,10.524Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M10.55,21.52C10.343,21.879 9.884,22.001 9.525,21.794C9.167,21.587 9.044,21.129 9.251,20.77L17.501,6.481C17.708,6.122 18.167,5.999 18.525,6.206C18.884,6.413 19.007,6.872 18.8,7.231L10.55,21.52Z"
android:fillColor="#ffffff"/>
</vector>