feature: About screen (#106)

Co-authored-by: TEYSSANDIER Raphael <rteyssandier@sephora.fr>
This commit is contained in:
Raphael Teyssandier 2025-08-15 15:07:41 +02:00 committed by GitHub
parent e1922afffc
commit d22bd8642f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 271 additions and 0 deletions

View file

@ -0,0 +1,7 @@
package io.github.openflocon.flocondesktop.common.utils
import java.net.URI
actual fun openInBrowser(uri: URI) {
// no op
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

After

Width:  |  Height:  |  Size: 2.2 MiB

Before After
Before After

View file

@ -0,0 +1,5 @@
package io.github.openflocon.flocondesktop.common.utils
import java.net.URI
expect fun openInBrowser(uri: URI)

View file

@ -1,6 +1,9 @@
package io.github.openflocon.flocondesktop
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberWindowState
@ -9,6 +12,7 @@ import coil3.compose.setSingletonImageLoaderFactory
import coil3.network.ktor3.KtorNetworkFetcherFactory
import flocondesktop.composeapp.generated.resources.Res
import flocondesktop.composeapp.generated.resources.app_icon_small
import io.github.openflocon.flocondesktop.about.AboutScreen
import io.github.openflocon.flocondesktop.window.MIN_WINDOW_HEIGHT
import io.github.openflocon.flocondesktop.window.MIN_WINDOW_WIDTH
import io.github.openflocon.flocondesktop.window.WindowStateData
@ -16,12 +20,19 @@ import io.github.openflocon.flocondesktop.window.WindowStateSaver
import io.github.openflocon.flocondesktop.window.size
import io.github.openflocon.flocondesktop.window.windowPosition
import org.jetbrains.compose.resources.painterResource
import java.awt.Desktop
import java.awt.Dimension
fun main() {
System.setProperty("apple.awt.application.name", "Flocon")
return application {
var openAbout by remember { mutableStateOf(false) }
Desktop.getDesktop().setAboutHandler {
openAbout = true
}
setSingletonImageLoaderFactory { context ->
ImageLoader
.Builder(context)
@ -61,6 +72,12 @@ fun main() {
window.minimumSize = Dimension(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT)
App()
if (openAbout) {
AboutScreen(
onCloseRequest = { openAbout = false }
)
}
}
}
}

View file

@ -0,0 +1,92 @@
package io.github.openflocon.flocondesktop.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.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.WindowPlacement
import androidx.compose.ui.window.WindowPosition
import androidx.compose.ui.window.rememberWindowState
import flocondesktop.composeapp.generated.resources.Res
import flocondesktop.composeapp.generated.resources.app_icon
import io.github.openflocon.flocondesktop.common.utils.openInBrowser
import io.github.openflocon.library.designsystem.FloconTheme
import io.github.openflocon.library.designsystem.components.FloconSurface
import io.github.openflocon.library.designsystem.components.FloconTextButton
import org.jetbrains.compose.resources.painterResource
import java.net.URI
@Composable
internal fun AboutScreen(
onCloseRequest: () -> Unit
) {
Window(
title = "About",
onCloseRequest = onCloseRequest,
state = rememberWindowState(
placement = WindowPlacement.Floating,
position = WindowPosition(Alignment.Center),
size = DpSize(Dp.Unspecified, Dp.Unspecified)
)
) {
FloconSurface {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.padding(16.dp)
) {
Image(
painter = painterResource(Res.drawable.app_icon),
contentDescription = null,
modifier = Modifier.size(120.dp)
)
Text(
text = "Flocon",
style = FloconTheme.typography.titleSmall,
color = FloconTheme.colorPalette.onSurface
)
FloconTextButton(
onClick = {
openInBrowser(URI.create("https://github.com/openflocon/Flocon"))
}
) {
Text(
text = "GitHub"
)
}
FlowRow(
verticalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterVertically),
horizontalArrangement = Arrangement.spacedBy(
4.dp,
Alignment.CenterHorizontally
),
modifier = Modifier
.widthIn(max = 400.dp)
.heightIn(max = 400.dp)
.verticalScroll(rememberScrollState())
) {
contributors.fastForEach {
ContributorView(it)
}
}
}
}
}
}

View file

@ -0,0 +1,26 @@
package io.github.openflocon.flocondesktop.about
import androidx.compose.runtime.Immutable
@Immutable
internal data class Contributor(
val firstName: String,
val lastName: String,
val profile: String,
val image: String
)
internal val contributors = listOf(
Contributor(
firstName = "Florent",
lastName = "Champigny",
profile = "https://github.com/florent37",
image = "https://avatars.githubusercontent.com/u/5754972?v=4"
),
Contributor(
firstName = "Raphael",
lastName = "Teyssandier",
profile = "https://github.com/doTTTTT",
image = "https://avatars.githubusercontent.com/u/13266870?v=4"
)
)

View file

@ -0,0 +1,86 @@
package io.github.openflocon.flocondesktop.about
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsHoveredAsState
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
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.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
import coil3.compose.LocalPlatformContext
import coil3.request.ImageRequest
import io.github.openflocon.flocondesktop.common.utils.openInBrowser
import io.github.openflocon.library.designsystem.FloconTheme
import java.net.URI
@Composable
internal fun ContributorView(
contributor: Contributor
) {
val interactionSource = remember { MutableInteractionSource() }
val hovered by interactionSource.collectIsHoveredAsState()
val borderColor = if (hovered) FloconTheme.colorPalette.onSurface else Color.Transparent
val shape = RoundedCornerShape(10.dp)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.width(120.dp)
.height(140.dp)
.clip(shape)
.border(width = 2.dp, color = borderColor, shape = shape)
.clickable(
onClick = { openInBrowser(URI.create(contributor.profile)) },
interactionSource = interactionSource,
indication = LocalIndication.current
)
.padding(8.dp)
) {
AsyncImage(
model = ImageRequest.Builder(LocalPlatformContext.current)
.data(contributor.image)
.listener(onError = { _, error ->
error.throwable.printStackTrace()
})
.build(),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.size(80.dp)
.clip(shape)
)
Spacer(Modifier.weight(1f))
Text(
text = contributor.firstName,
style = FloconTheme.typography.bodyMedium,
color = FloconTheme.colorPalette.onSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Spacer(Modifier.height(4.dp))
Text(
text = contributor.lastName,
style = FloconTheme.typography.bodySmall,
color = FloconTheme.colorPalette.onSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}

View file

@ -0,0 +1,16 @@
package io.github.openflocon.flocondesktop.common.utils
import java.awt.Desktop
import java.net.URI
actual fun openInBrowser(uri: URI) {
val osName by lazy(LazyThreadSafetyMode.NONE) { System.getProperty("os.name").lowercase() }
val desktop = Desktop.getDesktop()
when {
Desktop.isDesktopSupported() && desktop.isSupported(Desktop.Action.BROWSE) -> desktop.browse(uri)
"mac" in osName -> Runtime.getRuntime().exec("open $uri")
"nix" in osName || "nux" in osName -> Runtime.getRuntime().exec("xdg-open $uri")
else -> throw RuntimeException("cannot open $uri")
}
}

View file

@ -0,0 +1,22 @@
package io.github.openflocon.library.designsystem.components
import androidx.compose.foundation.layout.RowScope
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import io.github.openflocon.library.designsystem.FloconTheme
@Composable
fun FloconTextButton(
onClick: () -> Unit,
content: @Composable RowScope.() -> Unit
) {
TextButton(
onClick = onClick,
colors = ButtonDefaults.textButtonColors(
containerColor = FloconTheme.colorPalette.inverseSurface,
contentColor = FloconTheme.colorPalette.inverseOnSurface
),
content = content
)
}