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

DROID-3225 Vault | Enhancement | Replace settings icon by user icon (#2022)

This commit is contained in:
Evgenii Kozlov 2025-01-22 21:16:53 +01:00 committed by Evgenii Kozlov
parent 4da849cae3
commit 497d8a5a09
8 changed files with 113 additions and 30 deletions

View file

@ -16,6 +16,7 @@ import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.multiplayer.SpaceInviteResolver
import com.anytypeio.anytype.domain.multiplayer.SpaceViewSubscriptionContainer
import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider
import com.anytypeio.anytype.domain.search.ProfileSubscriptionManager
import com.anytypeio.anytype.domain.workspace.SpaceManager
import com.anytypeio.anytype.other.DefaultSpaceInviteResolver
import com.anytypeio.anytype.presentation.navigation.DeepLinkToObjectDelegate
@ -79,4 +80,5 @@ interface VaultComponentDependencies : ComponentDependencies {
fun appActionManager(): AppActionManager
fun logger(): Logger
fun awaitAccount(): AwaitAccountStartManager
fun profileContainer(): ProfileSubscriptionManager
}

View file

@ -57,7 +57,8 @@ class VaultFragment : BaseComposeFragment() {
onSpaceClicked = vm::onSpaceClicked,
onCreateSpaceClicked = vm::onCreateSpaceClicked,
onSettingsClicked = vm::onSettingsClicked,
onOrderChanged = vm::onOrderChanged
onOrderChanged = vm::onOrderChanged,
profile = vm.profileView.collectAsStateWithLifecycle().value
)
}
LaunchedEffect(Unit) {

View file

@ -1,6 +1,7 @@
package com.anytypeio.anytype.ui.vault
import android.content.res.Configuration
import android.graphics.drawable.Drawable
import android.os.Build.VERSION.SDK_INT
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
@ -21,7 +22,9 @@ import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@ -34,13 +37,16 @@ import androidx.compose.ui.draw.alpha
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.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
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.BuildConfig.USE_EDGE_TO_EDGE
import com.anytypeio.anytype.R
import com.anytypeio.anytype.core_models.Id
@ -60,15 +66,22 @@ import com.anytypeio.anytype.core_ui.views.Relations3
import com.anytypeio.anytype.core_ui.views.Title1
import com.anytypeio.anytype.core_utils.insets.EDGE_TO_EDGE_MIN_SDK
import com.anytypeio.anytype.presentation.editor.cover.CoverGradient
import com.anytypeio.anytype.presentation.profile.AccountProfile
import com.anytypeio.anytype.presentation.profile.ProfileIconView
import com.anytypeio.anytype.presentation.spaces.SelectSpaceViewModel
import com.anytypeio.anytype.presentation.spaces.SpaceIconView
import com.anytypeio.anytype.presentation.vault.VaultViewModel.VaultSpaceView
import com.anytypeio.anytype.presentation.wallpaper.WallpaperColor
import com.anytypeio.anytype.ui.sharing.SharingData
import com.anytypeio.anytype.ui.widgets.types.gradient
import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
import com.bumptech.glide.integration.compose.GlideImage
import com.bumptech.glide.request.RequestListener
@Composable
fun VaultScreen(
profile: AccountProfile,
spaces: List<VaultSpaceView>,
onSpaceClicked: (VaultSpaceView) -> Unit,
onCreateSpaceClicked: () -> Unit,
@ -109,6 +122,7 @@ fun VaultScreen(
) {
VaultScreenToolbar(
profile = profile,
onPlusClicked = onCreateSpaceClicked,
onSettingsClicked = onSettingsClicked,
spaceCountLimitReached = spaces.size >= SelectSpaceViewModel.MAX_SPACE_COUNT
@ -170,8 +184,10 @@ fun VaultScreen(
}
@OptIn(ExperimentalGlideComposeApi::class)
@Composable
fun VaultScreenToolbar(
profile: AccountProfile,
spaceCountLimitReached: Boolean = false,
onPlusClicked: () -> Unit,
onSettingsClicked: () -> Unit
@ -187,16 +203,57 @@ fun VaultScreenToolbar(
color = colorResource(id = R.color.text_primary),
modifier = Modifier.align(Alignment.Center)
)
Image(
painter = painterResource(id = R.drawable.ic_vault_settings),
contentDescription = "Settings icon",
modifier = Modifier
.align(Alignment.CenterStart)
.padding(start = 16.dp)
.noRippleClickable {
onSettingsClicked()
when(profile) {
is AccountProfile.Data -> {
Box(
Modifier
.align(Alignment.CenterStart)
.padding(start = 16.dp)
.size(28.dp)
.noRippleClickable {
onSettingsClicked()
}
) {
when(val icon = profile.icon) {
is ProfileIconView.Image -> {
GlideImage(
model = icon.url,
contentDescription = "Custom image profile",
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxSize()
.clip(CircleShape)
)
}
else -> {
val nameFirstChar = if (profile.name.isEmpty()) {
stringResource(id = com.anytypeio.anytype.ui_settings.R.string.account_default_name)
} else {
profile.name.first().uppercaseChar().toString()
}
Box(
modifier = Modifier
.fillMaxSize()
.clip(CircleShape)
.background(colorResource(id = com.anytypeio.anytype.ui_settings.R.color.text_tertiary))
) {
Text(
text = nameFirstChar,
style = MaterialTheme.typography.h3.copy(
color = colorResource(id = com.anytypeio.anytype.ui_settings.R.color.text_white),
fontSize = 20.sp
),
modifier = Modifier.align(Alignment.Center)
)
}
}
}
}
)
}
AccountProfile.Idle -> {
// Draw nothing
}
}
if (!spaceCountLimitReached) {
Image(
painter = painterResource(id = R.drawable.ic_vault_top_toolbar_plus),
@ -411,7 +468,8 @@ fun LoadingSpaceCardPreview() {
fun VaultScreenToolbarPreview() {
VaultScreenToolbar(
onPlusClicked = {},
onSettingsClicked = {}
onSettingsClicked = {},
profile = AccountProfile.Idle
)
}
@ -449,6 +507,7 @@ fun VaultScreenPreview() {
onSpaceClicked = {},
onCreateSpaceClicked = {},
onSettingsClicked = {},
onOrderChanged = {}
onOrderChanged = {},
profile = AccountProfile.Idle
)
}

View file

@ -249,7 +249,7 @@ fun ParticipantScreenPreview() {
ParticipantScreen(
uiState = UiParticipantScreenState(
name = "Jetpack Compose",
icon = ProfileIconView.Emoji("M"),
icon = ProfileIconView.Placeholder("M"),
identity = "AnyId43",
description = "some description",
isOwner = true

View file

@ -59,6 +59,7 @@ import com.anytypeio.anytype.core_ui.views.BodyRegular
import com.anytypeio.anytype.core_ui.views.Caption1Regular
import com.anytypeio.anytype.core_ui.views.Title1
import com.anytypeio.anytype.core_models.membership.MembershipStatus
import com.anytypeio.anytype.presentation.profile.AccountProfile
import com.anytypeio.anytype.presentation.profile.ProfileIconView
import com.anytypeio.anytype.ui_settings.R
import kotlinx.coroutines.FlowPreview
@ -73,7 +74,7 @@ fun ProfileSettingsScreen(
isLogoutInProgress: Boolean,
onNameChange: (String) -> Unit,
onProfileIconClick: () -> Unit,
account: ProfileSettingsViewModel.AccountProfile,
account: AccountProfile,
onAppearanceClicked: () -> Unit,
onDataManagementClicked: () -> Unit,
onAboutClicked: () -> Unit,
@ -269,12 +270,12 @@ fun ActionWithProgressBar(
@Composable
private fun Header(
modifier: Modifier = Modifier,
account: ProfileSettingsViewModel.AccountProfile,
account: AccountProfile,
onProfileIconClick: () -> Unit,
onNameSet: (String) -> Unit
) {
when (account) {
is ProfileSettingsViewModel.AccountProfile.Data -> {
is AccountProfile.Data -> {
Box(modifier = modifier.padding(vertical = 6.dp)) {
Dragger()
}
@ -290,7 +291,7 @@ private fun Header(
}
ProfileNameBlock(name = account.name, onNameSet = onNameSet)
}
is ProfileSettingsViewModel.AccountProfile.Idle -> {}
is AccountProfile.Idle -> {}
}
}
@ -448,7 +449,7 @@ private fun ProfileSettingPreview() {
isLogoutInProgress = false,
onNameChange = {},
onProfileIconClick = {},
account = ProfileSettingsViewModel.AccountProfile.Data(
account = AccountProfile.Data(
"Walter",
icon = ProfileIconView.Placeholder("Walter")
),

View file

@ -28,6 +28,7 @@ import com.anytypeio.anytype.presentation.extension.sendScreenSettingsDeleteEven
import com.anytypeio.anytype.core_models.membership.MembershipStatus
import com.anytypeio.anytype.domain.search.ProfileSubscriptionManager
import com.anytypeio.anytype.presentation.membership.provider.MembershipProvider
import com.anytypeio.anytype.presentation.profile.AccountProfile
import com.anytypeio.anytype.presentation.profile.ProfileIconView
import com.anytypeio.anytype.presentation.profile.profileIcon
import kotlinx.coroutines.Job
@ -149,14 +150,6 @@ class ProfileSettingsViewModel(
}
}
sealed class AccountProfile {
data object Idle: AccountProfile()
class Data(
val name: String,
val icon: ProfileIconView
): AccountProfile()
}
class Factory(
private val analytics: Analytics,
private val container: StorelessSubscriptionContainer,

View file

@ -7,7 +7,6 @@ import com.anytypeio.anytype.domain.misc.UrlBuilder
sealed class ProfileIconView {
object Loading : ProfileIconView()
data class Placeholder(val name: String?) : ProfileIconView()
data class Emoji(val unicode: String) : ProfileIconView()
data class Image(val url: Url) : ProfileIconView()
}
@ -19,4 +18,12 @@ fun ObjectWrapper.Basic.profileIcon(builder: UrlBuilder): ProfileIconView = when
else -> ProfileIconView.Placeholder(
name = name?.trim()?.ifEmpty { null }
)
}
sealed class AccountProfile {
data object Idle: AccountProfile()
class Data(
val name: String,
val icon: ProfileIconView
): AccountProfile()
}

View file

@ -21,6 +21,7 @@ import com.anytypeio.anytype.domain.misc.DeepLinkResolver
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.domain.multiplayer.SpaceInviteResolver
import com.anytypeio.anytype.domain.multiplayer.SpaceViewSubscriptionContainer
import com.anytypeio.anytype.domain.search.ProfileSubscriptionManager
import com.anytypeio.anytype.domain.spaces.SaveCurrentSpace
import com.anytypeio.anytype.domain.vault.GetVaultSettings
import com.anytypeio.anytype.domain.vault.ObserveVaultSettings
@ -34,6 +35,8 @@ import com.anytypeio.anytype.presentation.home.OpenObjectNavigation
import com.anytypeio.anytype.presentation.home.navigation
import com.anytypeio.anytype.presentation.navigation.DeepLinkToObjectDelegate
import com.anytypeio.anytype.presentation.navigation.NavigationViewModel
import com.anytypeio.anytype.presentation.profile.AccountProfile
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
@ -42,10 +45,13 @@ import javax.inject.Inject
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
import timber.log.Timber
@ -63,12 +69,24 @@ class VaultViewModel(
private val analytics: Analytics,
private val deepLinkToObjectDelegate: DeepLinkToObjectDelegate,
private val appActionManager: AppActionManager,
private val spaceInviteResolver: SpaceInviteResolver
private val spaceInviteResolver: SpaceInviteResolver,
private val profileContainer: ProfileSubscriptionManager
) : NavigationViewModel<VaultViewModel.Navigation>(), DeepLinkToObjectDelegate by deepLinkToObjectDelegate {
val spaces = MutableStateFlow<List<VaultSpaceView>>(emptyList())
val commands = MutableSharedFlow<Command>(replay = 0)
val profileView = profileContainer.observe().map { obj ->
AccountProfile.Data(
name = obj.name.orEmpty(),
icon = obj.profileIcon(urlBuilder)
)
}.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(1000L),
AccountProfile.Idle
)
init {
Timber.i("VaultViewModel, init")
viewModelScope.launch {
@ -343,7 +361,8 @@ class VaultViewModel(
private val analytics: Analytics,
private val deepLinkToObjectDelegate: DeepLinkToObjectDelegate,
private val appActionManager: AppActionManager,
private val spaceInviteResolver: SpaceInviteResolver
private val spaceInviteResolver: SpaceInviteResolver,
private val profileContainer: ProfileSubscriptionManager
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(
@ -361,7 +380,8 @@ class VaultViewModel(
analytics = analytics,
deepLinkToObjectDelegate = deepLinkToObjectDelegate,
appActionManager = appActionManager,
spaceInviteResolver = spaceInviteResolver
spaceInviteResolver = spaceInviteResolver,
profileContainer = profileContainer
) as T
}