diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/AboutScreen.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/AboutScreen.kt index a9e1d5e..3a6ee7a 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/AboutScreen.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/AboutScreen.kt @@ -3,21 +3,31 @@ package mg.dot.feufaro.ui import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.OpenInNew import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Code -import androidx.compose.material.icons.filled.Email +import androidx.compose.material.icons.filled.Description import androidx.compose.material.icons.filled.Language import androidx.compose.material3.* -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.platform.UriHandler +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog @@ -27,12 +37,82 @@ import feufaro.composeapp.generated.resources.icon import mg.dot.feufaro.di.rememberAppVersion import mg.dot.feufaro.getPlatform import org.jetbrains.compose.resources.painterResource +import java.awt.Desktop +import java.net.URI -@OptIn(ExperimentalMaterial3Api::class) +private var lastClickTime = 0L + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) @Composable fun AboutDialog(onDismiss: () -> Unit) { val platform = getPlatform() val appVersion = rememberAppVersion() + val uriHandler = LocalUriHandler.current + val developers = listOf( + "RANDIMBISOA Mahefa" to "https://github.com/DotMG", + "RAKOTOARIMALALA Hasinjato M." to "https://github.com/Hasinjato", + "RAMANAHADRAY Tsinjo Anjaratahina" to "https://github.com/Tiji-Tahina" + ) + + fun openWebUrl(url: String, uriHandler: UriHandler) { + val currentTime = System.currentTimeMillis() + if (currentTime - lastClickTime < 1000) return + lastClickTime = currentTime + + val isAndroid = getPlatform().name.startsWith("Android") + + if (isAndroid) { + try { + uriHandler.openUri(url) + } catch (e: Exception) { + println("Erreur Android : ${e.message}") + } + return + } + + // Linux / Windows + Thread { + try { + val os = System.getProperty("os.name")?.lowercase() ?: "" + when { + os.contains("win") -> { + ProcessBuilder("cmd.exe", "/c", "start", url.replace("&", "^&")).start() + } + else -> { // Linux / Unix + val candidates = listOf( + arrayOf("gio", "open", url), // GNOME / GTK + arrayOf("kioclient6", "exec", url), // KDE Plasma 6 + arrayOf("kioclient5", "exec", url), // KDE Plasma 5 + arrayOf("x-www-browser", url), // Debian / Ubuntu + arrayOf("firefox", url), + arrayOf("google-chrome", url), + arrayOf("zen-browser", url) + ) + + var success = false + for (cmd in candidates) { + try { + val process = ProcessBuilder(*cmd).start() + if (process.isAlive || process.waitFor() == 0) { + success = true + break + } + } catch (_: Exception) { + } + } + + if (!success && Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { + Desktop.getDesktop().browse(URI(url)) + } + } + } + } catch (e: Exception) { + println("Erreur ouverture URL : ${e.message}") + } + }.start() + } + + val devScrollState = rememberScrollState() Dialog( onDismissRequest = onDismiss, properties = DialogProperties( @@ -56,67 +136,123 @@ fun AboutDialog(onDismiss: () -> Unit) { Column( modifier = Modifier .fillMaxWidth() - .padding(24.dp), + .padding(10.dp), horizontalAlignment = Alignment.CenterHorizontally ) { - Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.TopEnd) { - IconButton( - onClick = onDismiss, - modifier = Modifier.size(32.dp) + Column( + modifier = Modifier + .fillMaxWidth() + .weight(1f, fill = false) + .verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.TopEnd) { + IconButton( + onClick = onDismiss, + modifier = Modifier.size(32.dp) + ) { + Icon( + imageVector = Icons.Default.Close, + contentDescription = "Fermer", + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } + + Surface( + modifier = Modifier.size(100.dp), + shape = CircleShape, + color = MaterialTheme.colorScheme.primary.copy(alpha = 0.5f), + tonalElevation = 4.dp ) { - Icon( - imageVector = Icons.Default.Close, - contentDescription = "Fermer", - tint = MaterialTheme.colorScheme.onSurfaceVariant + Image( + painter = painterResource(Res.drawable.icon), + contentDescription = "Logo Feufaro", + modifier = Modifier + .padding(12.dp) + .clip(CircleShape) ) } + + Spacer(modifier = Modifier.height(16.dp)) + + Text( + text = "Feufaro", + style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.Bold), + color = MaterialTheme.colorScheme.primary + ) + + Surface( + color = MaterialTheme.colorScheme.secondaryContainer, + shape = RoundedCornerShape(12.dp), + modifier = Modifier.padding(top = 4.dp, bottom = 16.dp) + ) { + Text( + text = appVersion, + modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp), + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.onSecondaryContainer + ) + } + + Text( + text = "L'application pour lire, éditer et analyser vos partitions Solfa. Ecrivez et créez des harmonies, écoutez ses mélodies et organisez votre playlist en toute simplicité.", + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.onSurfaceVariant, + lineHeight = 22.sp + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Box( + modifier = Modifier + .fillMaxWidth() + .height(120.dp) + .clip(RoundedCornerShape(12.dp)) + .background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f)) + ) { + CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) { + Column( + modifier = Modifier + .fillMaxWidth() + .verticalScroll(devScrollState), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + developers.forEach { (name, gitUrl) -> + AssistChip( + onClick = { openWebUrl(gitUrl, uriHandler) }, + label = { + AutoResizedText( + text = name, + style = MaterialTheme.typography.labelMedium, + minFontSize = 9.sp + ) + }, + leadingIcon = { + Icon( + imageVector = Icons.Default.Code, + contentDescription = null, + modifier = Modifier.size(16.dp) + ) + }, + trailingIcon = { + Icon( + imageVector = Icons.AutoMirrored.Default.OpenInNew, + contentDescription = null, + modifier = Modifier.size(14.dp), + tint = MaterialTheme.colorScheme.primary + ) + }, + shape = RoundedCornerShape(12.dp) + ) + } + } + } + } } - Surface( - modifier = Modifier.size(100.dp), - shape = CircleShape, - color = MaterialTheme.colorScheme.primary.copy(alpha = 0.5f), - tonalElevation = 4.dp - ) { - Image( - painter = painterResource(Res.drawable.icon), - contentDescription = "Logo Feufaro", - modifier = Modifier - .padding(12.dp) - .clip(CircleShape) - ) - } - - Spacer(modifier = Modifier.height(16.dp)) - - Text( - text = "Feufaro", - style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.Bold), - color = MaterialTheme.colorScheme.primary - ) - - Surface( - color = MaterialTheme.colorScheme.secondaryContainer, - shape = RoundedCornerShape(12.dp), - modifier = Modifier.padding(top = 4.dp, bottom = 16.dp) - ) { - Text( - text = appVersion, - modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp), - style = MaterialTheme.typography.labelMedium, - color = MaterialTheme.colorScheme.onSecondaryContainer - ) - } - - Text( - text = "L'application pour lire, éditer et analyser vos partitions Solfa. Ecrivez et créez des harmonies, écoutez ses mélodies et organisez votre playlist en toute simplicité.", - style = MaterialTheme.typography.bodyMedium, - textAlign = TextAlign.Center, - color = MaterialTheme.colorScheme.onSurfaceVariant, - lineHeight = 22.sp - ) - - Spacer(modifier = Modifier.height(24.dp)) HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f)) Spacer(modifier = Modifier.height(16.dp)) @@ -124,9 +260,21 @@ fun AboutDialog(onDismiss: () -> Unit) { modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly ) { - InfoIconItem(icon = Icons.Default.Language, label = "Site Web", onClick = { }) - InfoIconItem(icon = Icons.Default.Email, label = "Contact", onClick = { }) - InfoIconItem(icon = Icons.Default.Code, label = "Licence", onClick = { }) + InfoIconItem( + icon = Icons.Default.Language, + label = "Site Web", + onClick = { openWebUrl("https://feufar.ooo", uriHandler) } + ) + InfoIconItem( + icon = Icons.Default.Code, + label = "Code Source", + onClick = { openWebUrl("https://code.homemade.mg/mahefa/feufaro", uriHandler) } + ) + InfoIconItem( + icon = Icons.Default.Description, + label = "Licence", + onClick = { openWebUrl("https://www.gnu.org/licenses/gpl-3.0-standalone.html", uriHandler) } + ) } Spacer(modifier = Modifier.height(24.dp)) @@ -163,4 +311,36 @@ fun InfoIconItem(icon: ImageVector, label: String, onClick: () -> Unit) { color = MaterialTheme.colorScheme.onSurfaceVariant ) } +} +@Composable +fun AutoResizedText( + text: String, + modifier: Modifier = Modifier, + style: TextStyle = LocalTextStyle.current, + color: Color = Color.Unspecified, + minFontSize: TextUnit = 9.sp +) { + var resizedTextStyle by remember(text) { mutableStateOf(style) } + var readyToDraw by remember(text) { mutableStateOf(false) } + + Text( + text = text, + color = color, + modifier = modifier.drawWithContent { + if (readyToDraw) drawContent() + }, + style = resizedTextStyle, + maxLines = 1, + softWrap = false, + overflow = TextOverflow.Clip, + onTextLayout = { result -> + if (result.hasVisualOverflow && resizedTextStyle.fontSize > minFontSize) { + resizedTextStyle = resizedTextStyle.copy( + fontSize = (resizedTextStyle.fontSize.value - 1).sp + ) + } else { + readyToDraw = true + } + } + ) } \ No newline at end of file