diff --git a/build.gradle.kts b/build.gradle.kts index 53c3cec..6fda181 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -34,7 +34,7 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") kapt("com.google.dagger:dagger-compiler:2.41") testImplementation(platform("org.junit:junit-bom:5.8.2")) - testImplementation("org.junit.jupiter:junit-jupiter") + testImplementation("org.junit.jupiter:junit-jupiter:5.8.2") testImplementation("io.mockk:mockk:1.12.3") implementation("com.squareup.retrofit2:retrofit:2.9.0") implementation("com.squareup.retrofit2:converter-scalars:2.9.0") diff --git a/src/main/kotlin/app/AppConstants.kt b/src/main/kotlin/app/AppConstants.kt index 95ac6a8..e646247 100644 --- a/src/main/kotlin/app/AppConstants.kt +++ b/src/main/kotlin/app/AppConstants.kt @@ -1,8 +1,30 @@ package app object AppConstants { + val openSourceProjects = listOf( + Project("Apache SSHD", "https://mina.apache.org/sshd-project/", apache__2_0), + Project("Google/Dagger", "https://dagger.dev/", apache__2_0), + Project("Jetbrains Compose", "https://www.jetbrains.com/lp/compose-mpp/", apache__2_0), + Project("JGit", "https://www.eclipse.org/jgit/", edl), + Project("JUnit 5", "https://junit.org/junit5/", edl), + Project("Kotlin", "https://kotlinlang.org/", apache__2_0), + Project("Kotlinx.serialization", "https://kotlinlang.org/docs/serialization.html#example-json-serialization", apache__2_0), + Project("Mockk", "https://mockk.io/", apache__2_0), + Project("Retrofit2", "https://square.github.io/retrofit/", apache__2_0), + ) + + // Remember to update build.gradle when changing this + const val APP_NAME = "Gitnuro" + const val APP_DESCRIPTION = "Gitnuro is a Git client that allows you to manage multiple repositories with a modern experience and live visual representation of your repository's state." const val APP_VERSION = "0.1.0" const val APP_VERSION_CODE = 1 const val VERSION_CHECK_URL = "https://raw.githubusercontent.com/JetpackDuba/Gitnuro/main/latest.json" -} \ No newline at end of file +} + + +private val apache__2_0 = License("Apache 2.0", "https://www.apache.org/licenses/LICENSE-2.0") +private val edl = License("EDL", "https://www.eclipse.org/org/documents/edl-v10.php") + +data class License(val name: String, val url: String) +data class Project(val name: String, val url: String, val license: License) diff --git a/src/main/kotlin/app/extensions/SystemUtils.kt b/src/main/kotlin/app/extensions/SystemUtils.kt index 3b217eb..7f420e8 100644 --- a/src/main/kotlin/app/extensions/SystemUtils.kt +++ b/src/main/kotlin/app/extensions/SystemUtils.kt @@ -1,7 +1,13 @@ package app.extensions +import java.awt.Desktop +import java.net.URI import java.nio.file.FileSystems val systemSeparator: String by lazy { FileSystems.getDefault().separator +} + +fun openUrlInBrowser(url: String) { + Desktop.getDesktop().browse(URI(url)) } \ No newline at end of file diff --git a/src/main/kotlin/app/ui/WelcomePage.kt b/src/main/kotlin/app/ui/WelcomePage.kt index 45644af..1383ac6 100644 --- a/src/main/kotlin/app/ui/WelcomePage.kt +++ b/src/main/kotlin/app/ui/WelcomePage.kt @@ -23,15 +23,15 @@ import app.AppConstants import app.AppStateManager import app.extensions.dirName import app.extensions.dirPath +import app.extensions.openUrlInBrowser import app.theme.primaryTextColor import app.theme.secondaryTextColor +import app.ui.dialogs.AppInfoDialog import app.ui.dialogs.CloneDialog import app.updates.Update import app.viewmodels.TabViewModel import openDirectoryDialog import openRepositoryDialog -import java.awt.Desktop -import java.net.URI @OptIn(ExperimentalMaterialApi::class) @@ -41,12 +41,13 @@ fun WelcomePage( ) { val appStateManager = tabViewModel.appStateManager var showCloneView by remember { mutableStateOf(false) } + var showAdditionalInfo by remember { mutableStateOf(false) } var newUpdate by remember { mutableStateOf(null) } LaunchedEffect(Unit) { val latestRelease = tabViewModel.latestRelease() - if(latestRelease != null && latestRelease.appCode > AppConstants.APP_VERSION_CODE) { + if (latestRelease != null && latestRelease.appCode > AppConstants.APP_VERSION_CODE) { newUpdate = latestRelease } } @@ -64,9 +65,8 @@ fun WelcomePage( HomeButtons( newUpdate = newUpdate, tabViewModel = tabViewModel, - onShowCloneView = { - showCloneView = true - } + onShowCloneView = { showCloneView = true }, + onShowAdditionalInfo = { showAdditionalInfo = true }, ) RecentRepositories(appStateManager, tabViewModel) @@ -83,20 +83,29 @@ fun WelcomePage( } LaunchedEffect(showCloneView) { - if (showCloneView) tabViewModel.cloneViewModel.reset() // Reset dialog before showing it + if (showCloneView) { + tabViewModel.cloneViewModel.reset() // Reset dialog before showing it + } } + if (showCloneView) { + CloneDialog( + tabViewModel.cloneViewModel, + onClose = { + showCloneView = false + tabViewModel.cloneViewModel.reset() + }, + onOpenRepository = { dir -> + tabViewModel.openRepository(dir) + }, + ) + } - if (showCloneView) CloneDialog( - tabViewModel.cloneViewModel, - onClose = { - showCloneView = false - tabViewModel.cloneViewModel.reset() - }, - onOpenRepository = { dir -> - tabViewModel.openRepository(dir) - }, - ) + if (showAdditionalInfo) { + AppInfoDialog( + onClose = { showAdditionalInfo = false }, + ) + } } @Composable @@ -104,6 +113,7 @@ fun HomeButtons( newUpdate: Update?, tabViewModel: TabViewModel, onShowCloneView: () -> Unit, + onShowAdditionalInfo: () -> Unit, ) { Column( modifier = Modifier.padding(end = 32.dp), @@ -149,7 +159,7 @@ fun HomeButtons( title = "Source code", painter = painterResource("code.svg"), onClick = { - Desktop.getDesktop().browse(URI("https://github.com/JetpackDuba/Gitnuro")) + openUrlInBrowser("https://github.com/JetpackDuba/Gitnuro") } ) @@ -157,17 +167,23 @@ fun HomeButtons( title = "Report a bug", painter = painterResource("bug.svg"), onClick = { - Desktop.getDesktop().browse(URI("https://github.com/JetpackDuba/Gitnuro/issues")) + openUrlInBrowser("https://github.com/JetpackDuba/Gitnuro/issues") } ) - if(newUpdate != null) { + IconTextButton( + title = "Additional information", + painter = painterResource("info.svg"), + onClick = onShowAdditionalInfo + ) + + if (newUpdate != null) { IconTextButton( title = "New update ${newUpdate.appVersion} available ", painter = painterResource("grade.svg"), iconColor = MaterialTheme.colors.secondary, onClick = { - Desktop.getDesktop().browse(URI(newUpdate.downloadUrl)) + openUrlInBrowser(newUpdate.downloadUrl) } ) } @@ -188,7 +204,7 @@ fun RecentRepositories(appStateManager: AppStateManager, tabViewModel: TabViewMo color = MaterialTheme.colors.primaryTextColor, ) - if(latestOpenedRepositoriesPaths.isEmpty()) { + if (latestOpenedRepositoriesPaths.isEmpty()) { Text( "Nothing to see here, open a repository first!", color = MaterialTheme.colors.secondaryTextColor, diff --git a/src/main/kotlin/app/ui/components/TextLink.kt b/src/main/kotlin/app/ui/components/TextLink.kt new file mode 100644 index 0000000..8e5d755 --- /dev/null +++ b/src/main/kotlin/app/ui/components/TextLink.kt @@ -0,0 +1,42 @@ +package app.ui.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.hoverable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsHoveredAsState +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import app.extensions.openUrlInBrowser +import app.theme.primaryTextColor + +@Composable +fun TextLink( + text: String, + url: String, + modifier: Modifier = Modifier, + colorsInverted: Boolean = false, +) { + val hoverInteraction = remember { MutableInteractionSource() } + val isHovered by hoverInteraction.collectIsHoveredAsState() + + val textColor = if (isHovered == colorsInverted) { + MaterialTheme.colors.primaryTextColor + } else { + MaterialTheme.colors.primary + } + + Text( + text = text, + modifier = Modifier + .hoverable(hoverInteraction) + .clickable { + openUrlInBrowser(url) + } + .then(modifier), + color = textColor, + ) +} \ No newline at end of file diff --git a/src/main/kotlin/app/ui/dialogs/AppInfoDialog.kt b/src/main/kotlin/app/ui/dialogs/AppInfoDialog.kt new file mode 100644 index 0000000..b75588e --- /dev/null +++ b/src/main/kotlin/app/ui/dialogs/AppInfoDialog.kt @@ -0,0 +1,99 @@ +package app.ui.dialogs + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.items +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.material.TextButton +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.sp +import app.AppConstants +import app.AppConstants.openSourceProjects +import app.Project +import app.theme.primaryTextColor +import app.ui.components.ScrollableLazyColumn +import app.ui.components.TextLink + +@Composable +fun AppInfoDialog( + onClose: () -> Unit, +) { + MaterialDialog { + Column( + modifier = Modifier + .width(600.dp) + .height(600.dp) + ) { + ScrollableLazyColumn( + modifier = Modifier + .weight(1f) + ) { + item { + Column(modifier = Modifier.padding(16.dp)) { + Text( + AppConstants.APP_NAME, + fontSize = 24.sp, + color = MaterialTheme.colors.primaryTextColor, + ) + + Text( + AppConstants.APP_DESCRIPTION, + fontSize = 14.sp, + color = MaterialTheme.colors.primaryTextColor, + modifier = Modifier.padding(top = 16.dp) + ) + + Text( + "Gitnuro has been possible thanks to the following open source projects:", + fontSize = 14.sp, + color = MaterialTheme.colors.primaryTextColor, + modifier = Modifier.padding(vertical = 16.dp) + ) + } + } + + items(openSourceProjects) { + ProjectUsed(it) + } + } + + TextButton( + modifier = Modifier + .padding(top = 16.dp, end = 8.dp) + .align(Alignment.End), + onClick = onClose + ) { + Text("Close") + } + } + } +} + +@Composable +fun ProjectUsed(project: Project) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(horizontal = 16.dp) + + ) { + TextLink( + text = project.name, + url = project.url, + modifier = Modifier + .padding(vertical = 8.dp) + ) + + Spacer(Modifier.weight(1f)) + + TextLink( + text = project.license.name, + url = project.license.url, + modifier = Modifier + .padding(vertical = 8.dp), + colorsInverted = true + ) + } +} \ No newline at end of file diff --git a/src/main/resources/info.svg b/src/main/resources/info.svg new file mode 100644 index 0000000..fb9f815 --- /dev/null +++ b/src/main/resources/info.svg @@ -0,0 +1 @@ + \ No newline at end of file