Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,30 @@ package io.github.composegears.valkyrie.ui.screen.settings.tabs.about
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.composegears.tiamat.compose.TiamatPreview
import com.composegears.tiamat.compose.navDestination
import io.github.composegears.valkyrie.ValkyrieBuildConfig
import io.github.composegears.valkyrie.jewel.HorizontalDivider
import io.github.composegears.valkyrie.jewel.settings.GroupSpacing
import io.github.composegears.valkyrie.jewel.tooling.PreviewTheme
import io.github.composegears.valkyrie.sdk.compose.foundation.animation.rememberShimmer
import io.github.composegears.valkyrie.sdk.compose.foundation.layout.CenterVerticalRow
import io.github.composegears.valkyrie.sdk.compose.foundation.layout.Spacer
import io.github.composegears.valkyrie.sdk.compose.icons.ValkyrieIcons
import io.github.composegears.valkyrie.sdk.compose.icons.colored.PluginIcon
import io.github.composegears.valkyrie.ui.screen.settings.tabs.about.domain.ContributorUiModel
import io.github.composegears.valkyrie.ui.screen.settings.tabs.about.ui.ContributorItem
import io.github.composegears.valkyrie.util.stringResource
import org.jetbrains.compose.ui.tooling.preview.Preview
import org.jetbrains.jewel.foundation.theme.JewelTheme
Expand All @@ -27,11 +37,16 @@ import org.jetbrains.jewel.ui.component.VerticallyScrollableContainer
import org.jetbrains.jewel.ui.typography

val AboutSettingsScreen by navDestination {
AboutSettingsUi()
val viewModel = viewModel { AboutSettingsViewModel() }
val contributors by viewModel.contributors.collectAsState()

AboutSettingsUi(contributors = contributors)
}

@Composable
private fun AboutSettingsUi() {
private fun AboutSettingsUi(contributors: List<ContributorUiModel>) {
val shimmer = rememberShimmer()

VerticallyScrollableContainer {
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
CenterVerticalRow(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
Expand Down Expand Up @@ -68,6 +83,24 @@ private fun AboutSettingsUi() {
uri = "https://plugins.jetbrains.com/plugin/24786-valkyrie",
text = stringResource("settings.rate.marketplace"),
)
Spacer(16.dp)
HorizontalDivider()
Spacer(16.dp)
Text(
text = stringResource("settings.about.contributors"),
style = JewelTheme.typography.h4TextStyle,
maxLines = 1,
)
Spacer(12.dp)
FlowRow(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterHorizontally),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
contributors.forEach { contributor ->
ContributorItem(contributor = contributor, shimmer = shimmer)
}
}
GroupSpacing()
}
}
Expand All @@ -76,5 +109,5 @@ private fun AboutSettingsUi() {
@Preview
@Composable
private fun AboutSettingsPreview() = PreviewTheme {
AboutSettingsUi()
TiamatPreview(AboutSettingsScreen)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package io.github.composegears.valkyrie.ui.screen.settings.tabs.about

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.composegears.leviathan.compose.inject
import io.github.composegears.valkyrie.ui.screen.settings.tabs.about.domain.AvatarByteArray
import io.github.composegears.valkyrie.ui.screen.settings.tabs.about.domain.ContributorUiModel
import io.github.composegears.valkyrie.ui.screen.webimport.common.di.NetworkModule
import io.github.composegears.valkyrie.util.result.runCatchingCancellable
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsBytes
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch

class AboutSettingsViewModel : ViewModel() {

private val httpClient = inject(NetworkModule.httpClient)

private val _contributors = MutableStateFlow(initialContributors())
val contributors = _contributors.asStateFlow()

init {
loadAvatars()
}

private fun loadAvatars() {
viewModelScope.launch {
val updated = _contributors.value.map { contributor ->
async {
val bytes = runCatchingCancellable {
AvatarByteArray(httpClient.get(contributor.avatarUrl).bodyAsBytes())
}.getOrNull()
contributor.copy(avatarBytes = bytes)
}
}.awaitAll()
_contributors.value = updated
}
}

private fun initialContributors(): List<ContributorUiModel> = listOf(
ContributorUiModel(
login = "egorikftp",
description = "Project creator & core maintainer",
profileUrl = "https://github.com/egorikftp",
avatarUrl = "https://github.com/egorikftp.png",
),
ContributorUiModel(
login = "t-regbs",
description = "Web import & Figma plugin development, ImageVector to XML conversion, KMP tuning",
profileUrl = "https://github.com/t-regbs",
avatarUrl = "https://github.com/t-regbs.png",
),
ContributorUiModel(
login = "Goooler",
description = "Build tooling, test infrastructure & dependency management",
profileUrl = "https://github.com/Goooler",
avatarUrl = "https://github.com/Goooler.png",
),
ContributorUiModel(
login = "LennartEgb",
description = "KMP generator & parser modules, IR restructuring & Compose colors handling",
profileUrl = "https://github.com/LennartEgb",
avatarUrl = "https://github.com/LennartEgb.png",
),
ContributorUiModel(
login = "vkatz",
description = "Fuzzy search, plugin state persistence & technical support",
profileUrl = "https://github.com/vkatz",
avatarUrl = "https://github.com/vkatz.png",
),
ContributorUiModel(
login = "jonapoul",
description = "Initial Gradle plugin implementation",
profileUrl = "https://github.com/jonapoul",
avatarUrl = "https://github.com/jonapoul.png",
),
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.github.composegears.valkyrie.ui.screen.settings.tabs.about.domain

import androidx.compose.runtime.Stable

@Stable
@JvmInline
value class AvatarByteArray(val bytes: ByteArray)
Comment thread
egorikftp marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.github.composegears.valkyrie.ui.screen.settings.tabs.about.domain

data class ContributorUiModel(
val login: String,
val description: String,
val profileUrl: String,
val avatarUrl: String,
val avatarBytes: AvatarByteArray? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package io.github.composegears.valkyrie.ui.screen.settings.tabs.about.ui

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.toComposeImageBitmap
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import io.github.composegears.valkyrie.sdk.compose.foundation.animation.Shimmer
import io.github.composegears.valkyrie.sdk.compose.foundation.animation.shimmer
import io.github.composegears.valkyrie.ui.screen.settings.tabs.about.domain.ContributorUiModel
import java.io.ByteArrayInputStream
import javax.imageio.ImageIO
import org.jetbrains.jewel.foundation.theme.JewelTheme
import org.jetbrains.jewel.ui.component.ExternalLink
import org.jetbrains.jewel.ui.component.Text
import org.jetbrains.jewel.ui.typography

@Composable
fun ContributorItem(
contributor: ContributorUiModel,
shimmer: Shimmer,
modifier: Modifier = Modifier,
) {
val bitmap: ImageBitmap? = remember(contributor.avatarBytes) {
contributor.avatarBytes?.let { avatar ->
runCatching {
ImageIO.read(ByteArrayInputStream(avatar.bytes)).toComposeImageBitmap()
}.getOrNull()
}
}

Column(
modifier = modifier.width(160.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
if (bitmap != null) {
Image(
modifier = Modifier.size(40.dp).clip(CircleShape),
painter = BitmapPainter(bitmap),
contentDescription = contributor.login,
)
} else {
Spacer(
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.shimmer(shimmer = shimmer, cornerRadius = 20.dp),
)
}
ExternalLink(
uri = contributor.profileUrl,
text = contributor.login,
)
Text(
text = contributor.description,
style = JewelTheme.typography.small,
textAlign = TextAlign.Center,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.github.composegears.valkyrie.util.result

import kotlin.coroutines.cancellation.CancellationException

inline fun <R> runCatchingCancellable(block: () -> R): Result<R> {
return try {
Result.success(block())
} catch (c: CancellationException) {
throw c
} catch (e: Throwable) {
Result.failure(e)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ settings.about.plugin.name=Valkyrie Plugin
settings.about.sourcecode=Source code
settings.about.issue=Submit issue or idea
settings.rate.marketplace=Rate on JetBrains Marketplace
settings.about.contributors=People behind the project ❀️
web.import.selector.title=Choose icon provider
web.import.selector.google.title=Google Material Symbols
web.import.selector.google.description=A collection of over 2,500 icons with various design options, available in a single font file
Expand Down