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:
parent
34bc1f864c
commit
e14ef8b66b
13 changed files with 408 additions and 83 deletions
|
@ -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
|
||||
)
|
||||
}
|
|
@ -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 -> {
|
||||
|
|
|
@ -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) },
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue