diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5dc177e --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.idea +out/* +*.iml +target + +settings.xml + +.gradle/ +build/ +out/ diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..a985521 --- /dev/null +++ b/build.gradle @@ -0,0 +1,53 @@ +buildscript { + repositories { + mavenLocal() + jcenter() + mavenCentral() + } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +plugins { + id "org.jetbrains.kotlin.plugin.serialization" version "$kotlin_version" + id("org.jetbrains.compose") version "$compose_version" +} + +apply plugin: 'kotlin' + +project.group = "com.insanusmokrassar" + +repositories { + mavenLocal() + jcenter() + mavenCentral() + maven { url "https://kotlin.bintray.com/kotlinx" } + maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" } +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" + implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlin_serialisation_runtime_version" + implementation "dev.inmo:micro_utils.coroutines:$micro_utils_version" + implementation(compose.desktop.currentOs) + + implementation "io.ktor:ktor-client:$ktor_version" + implementation "io.ktor:ktor-client-cio:$ktor_version" +} + +compose.desktop { + application { + mainClass = "com.insanusmokrassar.kmppscriptbuilder.BuilderKt" + } +} + +compileKotlin { + kotlinOptions { + jvmTarget = "11" + useIR = true + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..55c69b0 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,10 @@ +kotlin_version=1.4.30 +kotlin_coroutines_version=1.4.2 +kotlin_serialisation_runtime_version=1.1.0 +#tornadofx_version=1.7.20 +ktor_version=1.5.1 +micro_utils_version=0.4.25 + +javafxplugin_version=0.0.9 + +compose_version=0.3.0 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..9ab0a83 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ca8c529 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..1660178 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,8 @@ +pluginManagement { + repositories { + gradlePluginPortal() + maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" } + } +} + +rootProject.name = 'KotlinPublicationScriptsBuilder' diff --git a/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/Builder.kt b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/Builder.kt new file mode 100644 index 0000000..284aa21 --- /dev/null +++ b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/Builder.kt @@ -0,0 +1,62 @@ +package com.insanusmokrassar.kmppscriptbuilder + +import androidx.compose.desktop.Window +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import com.insanusmokrassar.kmppscriptbuilder.utils.* +import com.insanusmokrassar.kmppscriptbuilder.views.* +import java.io.File + +//private val uncaughtExceptionsBC = BroadcastChannel(Channel.CONFLATED) +//val uncaughtExceptionsFlow: Flow = uncaughtExceptionsBC.asFlow() + +fun main(args: Array) = Window(title = "Kotlin Multiplatform Publishing Builder") { + val builder = BuilderView() + MaterialTheme( + Colors( + primary = Color(0x01, 0x57, 0x9b), + primaryVariant = Color(0x00, 0x2f, 0x6c), + secondary = Color(0xb2, 0xeb, 0xf2), + secondaryVariant = Color(0x81, 0xb9, 0xbf), + background = Color(0xe1, 0xe2, 0xe1), + surface = Color(0xf5, 0xf5, 0xf6), + error = Color(0xb7, 0x1c, 0x1c), + onPrimary = Color.White, + onSecondary = Color.Black, + onBackground = Color.Black, + onSurface = Color.Black, + onError = Color.White, + isLight = MaterialTheme.colors.isLight, + ) + ) { + Box( + Modifier.fillMaxSize() + .background(color = Color(245, 245, 245)) + ) { + + val stateVertical = rememberScrollState(0) + Box( + modifier = Modifier + .fillMaxSize() + .verticalScroll(stateVertical) + ) { + builder.init() + + } + + VerticalScrollbar( + modifier = Modifier.align(Alignment.CenterEnd).fillMaxHeight(), + adapter = rememberScrollbarAdapter(stateVertical) + ) + } + } + + if (args.isNotEmpty()) { + val config = loadConfigFile(File(args.first())) + builder.config = config + } +} diff --git a/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/export/jvm_only/MavenTemplater.kt b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/export/jvm_only/MavenTemplater.kt new file mode 100644 index 0000000..8d6b81d --- /dev/null +++ b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/export/jvm_only/MavenTemplater.kt @@ -0,0 +1,69 @@ +package com.insanusmokrassar.kmppscriptbuilder.export.jvm_only + +import com.insanusmokrassar.kmppscriptbuilder.models.* + +fun MavenConfig.buildJvmOnlyMavenConfig(licenses: List): String = """ + apply plugin: 'maven-publish' + ${if (includeGpgSigning) "apply plugin: 'signing'\n" else ""} + + task javadocJar(type: Jar) { + from javadoc + classifier = 'javadoc' + } + task sourcesJar(type: Jar) { + from sourceSets.main.allSource + classifier = 'sources' + } + + publishing { + publications { + maven(MavenPublication) { + from components.java + + artifact javadocJar + artifact sourcesJar + + pom { + resolveStrategy = Closure.DELEGATE_FIRST + + description = "$description" + name = "$name" + url = "$url" + + scm { + developerConnection = "scm:git:[fetch=]${vcsUrl}[push=]${vcsUrl}" + url = "$vcsUrl" + } + + developers { + ${developers.joinToString("\n") { """ + developer { + id = "${it.id}" + name = "${it.name}" + email = "${it.eMail}" + } + """ }} + } + + licenses { + ${licenses.joinToString("\n") { """ + license { + name = "${it.title}" + url = "${it.url}" + } + """ }} + } + } + repositories { + ${repositories.joinToString("\n ") { it.build(" ") }} + } + } + } + } + ${if (includeGpgSigning) """ + signing { + useGpgCmd() + sign publishing.publications + } + """ else ""} +""".trimIndent() \ No newline at end of file diff --git a/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/export/mpp/MavenTemplater.kt b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/export/mpp/MavenTemplater.kt new file mode 100644 index 0000000..188d590 --- /dev/null +++ b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/export/mpp/MavenTemplater.kt @@ -0,0 +1,56 @@ +package com.insanusmokrassar.kmppscriptbuilder.export.mpp + +import com.insanusmokrassar.kmppscriptbuilder.models.* + +fun MavenConfig.buildMultiplatformMavenConfig(licenses: List): String = """ + apply plugin: 'maven-publish' + ${if (includeGpgSigning) "apply plugin: 'signing'\n" else ""} + task javadocsJar(type: Jar) { + classifier = 'javadoc' + } + + publishing { + publications.all { + artifact javadocsJar + + pom { + description = "$description" + name = "$name" + url = "$url" + + scm { + developerConnection = "scm:git:[fetch=]${vcsUrl}[push=]${vcsUrl}" + url = "$vcsUrl" + } + + developers { + ${developers.joinToString("\n") { """ + developer { + id = "${it.id}" + name = "${it.name}" + email = "${it.eMail}" + } + """ }} + } + + licenses { + ${licenses.joinToString("\n") { """ + license { + name = "${it.title}" + url = "${it.url}" + } + """ }} + } + } + repositories { + ${repositories.joinToString("\n ") { it.build(" ") }} + } + } + } + ${if (includeGpgSigning) """ + signing { + useGpgCmd() + sign publishing.publications + } + """ else ""} +""".trimIndent() \ No newline at end of file diff --git a/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/models/BintrayConfig.kt b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/models/BintrayConfig.kt new file mode 100644 index 0000000..19eccc6 --- /dev/null +++ b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/models/BintrayConfig.kt @@ -0,0 +1,13 @@ +package com.insanusmokrassar.kmppscriptbuilder.models + +import kotlinx.serialization.Serializable + +@Serializable +data class BintrayConfig( + val repoUser: String = "\${project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')}", + val repo: String, + val packageName: String, + val packageVcs: String, + val autoPublish: Boolean = false, + val overridePublish: Boolean = false +) diff --git a/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/models/Config.kt b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/models/Config.kt new file mode 100644 index 0000000..a95d62d --- /dev/null +++ b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/models/Config.kt @@ -0,0 +1,72 @@ +package com.insanusmokrassar.kmppscriptbuilder.models + +import com.insanusmokrassar.kmppscriptbuilder.export.jvm_only.buildJvmOnlyMavenConfig +import com.insanusmokrassar.kmppscriptbuilder.export.mpp.buildMultiplatformMavenConfig +import kotlinx.serialization.* +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +@Serializable(ProjectTypeSerializer::class) +sealed class ProjectType { + abstract val name: String +// abstract fun buildBintrayGradleConfig(bintrayConfig: BintrayConfig, licenses: List): String + abstract fun buildMavenGradleConfig(mavenConfig: MavenConfig, licenses: List): String +} + +@Serializer(ProjectType::class) +internal object ProjectTypeSerializer : KSerializer { + override val descriptor: SerialDescriptor = String.serializer().descriptor + override fun deserialize(decoder: Decoder): ProjectType { + return when (decoder.decodeString()) { + JVMProjectType.name -> JVMProjectType + else -> MultiplatformProjectType + } + } + + override fun serialize(encoder: Encoder, value: ProjectType) { + encoder.encodeString(value.name) + } +} + +object MultiplatformProjectType : ProjectType() { + override val name: String = "Multiplatform" +// override fun buildBintrayGradleConfig( +// bintrayConfig: BintrayConfig, +// licenses: List +// ): String = bintrayConfig.buildMultiplatformGradleConfig( +// licenses +// ) + + override fun buildMavenGradleConfig( + mavenConfig: MavenConfig, + licenses: List + ): String = mavenConfig.buildMultiplatformMavenConfig( + licenses + ) +} + +object JVMProjectType : ProjectType() { + override val name: String = "JVM" +// override fun buildBintrayGradleConfig( +// bintrayConfig: BintrayConfig, +// licenses: List +// ): String = bintrayConfig.buildJvmOnlyGradleConfig( +// licenses +// ) + + override fun buildMavenGradleConfig( + mavenConfig: MavenConfig, + licenses: List + ): String = mavenConfig.buildJvmOnlyMavenConfig( + licenses + ) +} + +@Serializable +data class Config( + val licenses: List, + val mavenConfig: MavenConfig, + val type: ProjectType = MultiplatformProjectType +) diff --git a/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/models/Developer.kt b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/models/Developer.kt new file mode 100644 index 0000000..cc9461a --- /dev/null +++ b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/models/Developer.kt @@ -0,0 +1,10 @@ +package com.insanusmokrassar.kmppscriptbuilder.models + +import kotlinx.serialization.Serializable + +@Serializable +data class Developer( + val id: String, + val name: String, + val eMail: String +) diff --git a/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/models/License.kt b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/models/License.kt new file mode 100644 index 0000000..f7a3811 --- /dev/null +++ b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/models/License.kt @@ -0,0 +1,48 @@ +package com.insanusmokrassar.kmppscriptbuilder.models + +import com.insanusmokrassar.kmppscriptbuilder.utils.serialFormat +import io.ktor.client.HttpClient +import io.ktor.client.request.get +import io.ktor.client.request.url +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.MapSerializer +import kotlinx.serialization.builtins.serializer + +@Serializable +data class License( + val id: String, + val title: String, + val url: String? = null +) + +private val commonLicensesListDeserializer = MapSerializer(String.serializer(), License.serializer()) + +private var licenses: Map? = null + +suspend fun HttpClient.getLicenses(): Map { + val answer = get { + url("https://licenses.opendefinition.org/licenses/groups/all.json") + } + return serialFormat.decodeFromString(commonLicensesListDeserializer, answer).also { gotLicenses -> + licenses = gotLicenses + } +} + +suspend fun HttpClient.searchLicense(name: String): List { + val licenses = licenses ?: getLicenses() + val lowerCase = name.toLowerCase() + val upperCase = name.toUpperCase() + return licenses.values.filter { + it.title.toLowerCase().contains(lowerCase) || it.title.toUpperCase().contains(upperCase) || it.title.contains(name) + || it.id.toLowerCase().contains(lowerCase) || it.id.toUpperCase().contains(upperCase) || it.id.contains(name) + } +} + +fun Map.searchLicense(name: String): List { + val lowerCase = name.toLowerCase() + val upperCase = name.toUpperCase() + return values.filter { + it.title.toLowerCase().contains(lowerCase) || it.title.toUpperCase().contains(upperCase) || it.title.contains(name) + || it.id.toLowerCase().contains(lowerCase) || it.id.toUpperCase().contains(upperCase) || it.id.contains(name) + } +} diff --git a/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/models/MavenConfig.kt b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/models/MavenConfig.kt new file mode 100644 index 0000000..d2512fd --- /dev/null +++ b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/models/MavenConfig.kt @@ -0,0 +1,35 @@ +package com.insanusmokrassar.kmppscriptbuilder.models + +import kotlinx.serialization.Serializable + +@Serializable +data class MavenConfig( + val name: String, + val description: String, + val url: String, + val vcsUrl: String, + val includeGpgSigning: Boolean = false, + val developers: List, + val repositories: List = emptyList() +) + +@Serializable +data class MavenPublishingRepository( + val name: String, + val url: String +) { + private val nameCapitalized by lazy { + name.toUpperCase() + } + + fun build(indent: String) = """maven { + name = "$name" + url = uri("$url") + credentials { + username = project.hasProperty('${nameCapitalized}_USER') ? project.property('${nameCapitalized}_USER') : System.getenv('${nameCapitalized}_USER') + password = project.hasProperty('${nameCapitalized}_PASSWORD') ? project.property('${nameCapitalized}_PASSWORD') : System.getenv('${nameCapitalized}_PASSWORD') + } +}""".replace("\n", "\n$indent") +} + +val SonatypeRepository = MavenPublishingRepository("sonatype", "https://oss.sonatype.org/service/local/staging/deploy/maven2/") diff --git a/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/utils/FilesHandling.kt b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/utils/FilesHandling.kt new file mode 100644 index 0000000..72b6e04 --- /dev/null +++ b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/utils/FilesHandling.kt @@ -0,0 +1,75 @@ +package com.insanusmokrassar.kmppscriptbuilder.utils + +import com.insanusmokrassar.kmppscriptbuilder.models.Config +import java.io.File +import javax.swing.JFileChooser + +private const val appExtension = "kpsb" + +private var lastFile: File? = null + +fun loadConfigFile(file: File): Config { + lastFile = file + return serialFormat.decodeFromString(Config.serializer(), file.readText()) +} + +fun loadConfig(): Config? { + val fc = JFileChooser(lastFile ?.parent) + fc.addChoosableFileFilter(FileFilter("Kotlin Publication Scripts Builder", Regex(".*\\.$appExtension"))) + fc.addChoosableFileFilter(FileFilter("JSON", Regex(".*\\.json"))) + return when (fc.showOpenDialog(null)) { + JFileChooser.APPROVE_OPTION -> { + val file = fc.selectedFile + lastFile = file + return serialFormat.decodeFromString(Config.serializer(), fc.selectedFile.readText()) + } + else -> null + } +} + +fun saveConfig(config: Config): Boolean { + return lastFile ?.also { + it.writeText(serialFormat.encodeToString(Config.serializer(), config)) + } != null +} + +fun exportGradle(config: Config): Boolean { + val fc = JFileChooser(lastFile ?.parent) + fc.fileSelectionMode = JFileChooser.DIRECTORIES_ONLY + return when (fc.showSaveDialog(null)) { + JFileChooser.APPROVE_OPTION -> { + val file = fc.selectedFile + val mavenConfigContent = config.type.buildMavenGradleConfig( + config.mavenConfig, + config.licenses + ) + File(file, "publish.gradle").apply { + delete() + createNewFile() + writeText(mavenConfigContent) + } + true + } + else -> false + } +} + +fun saveAs(config: Config): Boolean { + val fc = JFileChooser(lastFile ?.parent) + fc.addChoosableFileFilter(FileFilter("Kotlin Publication Scripts Builder", Regex(".*\\.$appExtension"))) + fc.addChoosableFileFilter(FileFilter("JSON", Regex(".*\\.json"))) + return when (fc.showSaveDialog(null)) { + JFileChooser.APPROVE_OPTION -> { + val file = fc.selectedFile + val resultFile = if (file.extension == "") { + File(file.parentFile, "${file.name}.$appExtension") + } else { + file + } + resultFile.writeText(serialFormat.encodeToString(Config.serializer(), config)) + lastFile = resultFile + true + } + else -> false + } +} diff --git a/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/utils/NewFileFilterFactory.kt b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/utils/NewFileFilterFactory.kt new file mode 100644 index 0000000..983338c --- /dev/null +++ b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/utils/NewFileFilterFactory.kt @@ -0,0 +1,16 @@ +package com.insanusmokrassar.kmppscriptbuilder.utils + +import java.io.File +import javax.swing.filechooser.FileFilter + +fun FileFilter(description: String, fileFilter: (File) -> Boolean) = object : FileFilter() { + override fun accept(f: File?): Boolean { + return fileFilter(f ?: return false) + } + + override fun getDescription(): String = description +} + +fun FileFilter(description: String, nameRegex: Regex) = FileFilter(description) { + it.name.matches(nameRegex) +} diff --git a/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/utils/SerialFormat.kt b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/utils/SerialFormat.kt new file mode 100644 index 0000000..1e8e62b --- /dev/null +++ b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/utils/SerialFormat.kt @@ -0,0 +1,7 @@ +package com.insanusmokrassar.kmppscriptbuilder.utils + +import kotlinx.serialization.json.Json + +val serialFormat = Json { + ignoreUnknownKeys = true +} diff --git a/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/utils/UIElements.kt b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/utils/UIElements.kt new file mode 100644 index 0000000..d75fd02 --- /dev/null +++ b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/utils/UIElements.kt @@ -0,0 +1,60 @@ +package com.insanusmokrassar.kmppscriptbuilder.utils + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +val commonTextFieldTextStyle = TextStyle( + fontSize = 12.sp +) + +@Composable +inline fun TitleText(text: String) = Text( + text, Modifier.padding(0.dp, 8.dp), fontSize = 18.sp +) + +@Composable +inline fun CommonText(text: String, modifier: Modifier = Modifier) = Text( + text, modifier = modifier +) + +@Composable +inline fun CommonTextField(presetText: String, hint: String, noinline onChange: (String) -> Unit) = OutlinedTextField( + presetText, + onChange, + Modifier.fillMaxWidth(), + singleLine = true, + label = { + CommonText(hint) + } +) + +@Composable +inline fun SwitchWithLabel( + label: String, + checked: Boolean, + placeSwitchAtTheStart: Boolean = false, + switchEnabled: Boolean = true, + modifier: Modifier = Modifier.padding(0.dp, 8.dp), + horizontalArrangement: Arrangement.Horizontal = Arrangement.Start, + verticalAlignment: Alignment.Vertical = Alignment.Top, + switchModifier: Modifier = Modifier.padding(8.dp, 0.dp), + noinline onCheckedChange: (Boolean) -> Unit +) = Row(modifier, horizontalArrangement, verticalAlignment) { + val switchCreator = @Composable { + Switch(checked, onCheckedChange, switchModifier, enabled = switchEnabled) + } + if (placeSwitchAtTheStart) { + switchCreator() + } + CommonText(label, Modifier.align(Alignment.CenterVertically).clickable { }) + if (!placeSwitchAtTheStart) { + switchCreator() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/utils/View.kt b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/utils/View.kt new file mode 100644 index 0000000..37cd860 --- /dev/null +++ b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/utils/View.kt @@ -0,0 +1,34 @@ +package com.insanusmokrassar.kmppscriptbuilder.utils + +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +abstract class View { + protected open val defaultModifier = Modifier.fillMaxWidth().padding(8.dp) + + @Composable + abstract fun build() +} + +abstract class VerticalView(val title: String) : View() { + abstract val content: @Composable ColumnScope.() -> Unit + @Composable + override fun build() { + TitleText(title) + + Column( + defaultModifier + ) { + content() + } + + Spacer(Modifier.fillMaxWidth().height(8.dp)) + } +} + +@Composable +fun T.init(): T = apply { + build() +} \ No newline at end of file diff --git a/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/views/BuilderView.kt b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/views/BuilderView.kt new file mode 100644 index 0000000..c5e070c --- /dev/null +++ b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/views/BuilderView.kt @@ -0,0 +1,103 @@ +package com.insanusmokrassar.kmppscriptbuilder.views + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.svgResource +import androidx.compose.ui.unit.dp +import com.insanusmokrassar.kmppscriptbuilder.models.Config +import com.insanusmokrassar.kmppscriptbuilder.utils.* + +class BuilderView : View() { + private val projectTypeView = ProjectTypeView() + private val mavenInfoView = MavenInfoView() + private val licensesView = LicensesView() + private var saveAvailableState by mutableStateOf(false) + + override val defaultModifier: Modifier = Modifier.fillMaxSize() + + var config: Config + get() = Config(licensesView.licenses, mavenInfoView.mavenConfig, projectTypeView.projectType) + set(value) { + licensesView.licenses = value.licenses + mavenInfoView.mavenConfig = value.mavenConfig + projectTypeView.projectType = value.type + saveAvailableState = true + } + + @Composable + override fun build() { + Box(Modifier.fillMaxSize()) { + Column() { + TopAppBar( + @Composable { + CommonText("Kotlin publication scripts builder", Modifier.clickable { println(config) }) + }, + actions = { + IconButton( + { + loadConfig()?.also { + config = it + } + } + ) { + Image( + painter = svgResource("images/open_file.svg"), + contentDescription = "Open file" + ) + } + + if (saveAvailableState) { + IconButton( + { + saveConfig(config) + } + ) { + Image( + painter = svgResource("images/save_file.svg"), + contentDescription = "Save file" + ) + } + } + + if (saveAvailableState) { + IconButton( + { + exportGradle(config) + } + ) { + Image( + painter = svgResource("images/export_gradle.svg"), + contentDescription = "Export Gradle script" + ) + } + } + + IconButton( + { + if (saveAs(config)) { + saveAvailableState = true + } + } + ) { + Image( + painter = svgResource("images/save_as.svg"), + contentDescription = "Export Gradle script" + ) + } + } + ) + Column(Modifier.padding(8.dp)) { + projectTypeView.init() + Divider() + licensesView.init() + Divider() + mavenInfoView.init() + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/views/DevelopersView.kt b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/views/DevelopersView.kt new file mode 100644 index 0000000..8d1accc --- /dev/null +++ b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/views/DevelopersView.kt @@ -0,0 +1,51 @@ +package com.insanusmokrassar.kmppscriptbuilder.views + +import androidx.compose.runtime.* +import com.insanusmokrassar.kmppscriptbuilder.models.Developer +import com.insanusmokrassar.kmppscriptbuilder.utils.* + +class DeveloperState( + id: String = "", + name: String = "", + eMail: String = "" +) { + var id: String by mutableStateOf(id) + var name: String by mutableStateOf(name) + var eMail: String by mutableStateOf(eMail) + + fun toDeveloper() = Developer(id, name, eMail) +} + +private fun Developer.toDeveloperState() = DeveloperState(id, name, eMail) + +class DevelopersView : ListView("Developers info") { + var developers: List + get() = itemsList.map { it.toDeveloper() } + set(value) { + itemsList.clear() + itemsList.addAll( + value.map { it.toDeveloperState() } + ) + } + + override val addItemText: String = "Add developer" + override val removeItemText: String = "Remove developer" + + override fun createItem(): DeveloperState = DeveloperState() + @Composable + override fun buildView(item: DeveloperState) { + CommonTextField( + item.id, + "Developer username" + ) { item.id = it } + CommonTextField( + item.name, + "Developer name" + ) { item.name = it } + CommonTextField( + item.eMail, + "Developer E-Mail" + ) { item.eMail = it } + } + +} diff --git a/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/views/LicensesView.kt b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/views/LicensesView.kt new file mode 100644 index 0000000..0059027 --- /dev/null +++ b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/views/LicensesView.kt @@ -0,0 +1,95 @@ +package com.insanusmokrassar.kmppscriptbuilder.views + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.material.Button +import androidx.compose.material.Divider +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.insanusmokrassar.kmppscriptbuilder.models.License +import com.insanusmokrassar.kmppscriptbuilder.models.getLicenses +import com.insanusmokrassar.kmppscriptbuilder.utils.* +import io.ktor.client.HttpClient +import kotlinx.coroutines.* + +private class LicenseState( + id: String = "", + title: String = "", + url: String? = null +) { + var id: String by mutableStateOf(id) + var title: String by mutableStateOf(title) + var url: String? by mutableStateOf(url) + + fun toLicense() = License(id, title, url) +} + +private fun License.toLicenseState() = LicenseState(id, title, url) + +class LicensesView: VerticalView("Licenses") { + private var licensesListState = mutableStateListOf() + var licenses: List + get() = licensesListState.map { it.toLicense() } + set(value) { + licensesListState.clear() + licensesListState.addAll(value.map { it.toLicenseState() }) + } + private val availableLicensesState = mutableStateListOf() + private val licensesOffersToShow = mutableStateListOf() + private var licenseSearchFilter by mutableStateOf("") + + init { + CoroutineScope(Dispatchers.Default).launch { + val client = HttpClient() + availableLicensesState.addAll(client.getLicenses().values) + client.close() + } + } + + override val content: @Composable ColumnScope.() -> Unit = { + CommonTextField(licenseSearchFilter, "Search filter") { filterText -> + licenseSearchFilter = filterText + licensesOffersToShow.clear() + if (licenseSearchFilter.isNotEmpty()) { + licensesOffersToShow.addAll( + availableLicensesState.filter { filterText.all { symbol -> symbol.toLowerCase() in it.title } } + ) + } + } + Column { + licensesOffersToShow.forEach { + Column(Modifier.padding(16.dp, 8.dp, 8.dp, 8.dp)) { + CommonText(it.title, Modifier.clickable { + licensesListState.add(it.toLicenseState()) + licenseSearchFilter = "" + licensesOffersToShow.clear() + }) + Divider() + } + } + } + Button({ licensesListState.add(LicenseState()) }, Modifier.padding(8.dp)) { + CommonText("Add empty license") + } + licensesListState.forEach { license -> + Column(Modifier.padding(8.dp)) { + CommonTextField( + license.id, + "License ID" + ) { license.id = it } + CommonTextField( + license.title, + "License title" + ) { license.title = it } + CommonTextField( + license.url ?: "", + "License URL" + ) { license.url = it } + Button({ licensesListState.remove(license) }, Modifier.padding(8.dp)) { + CommonText("Remove") + } + } + } + } +} diff --git a/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/views/ListView.kt b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/views/ListView.kt new file mode 100644 index 0000000..cec116b --- /dev/null +++ b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/views/ListView.kt @@ -0,0 +1,34 @@ +package com.insanusmokrassar.kmppscriptbuilder.views + +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.insanusmokrassar.kmppscriptbuilder.models.Developer +import com.insanusmokrassar.kmppscriptbuilder.utils.* + +abstract class ListView(title: String) : VerticalView(title) { + protected val itemsList = mutableStateListOf() + + protected open val addItemText: String = "Add" + protected open val removeItemText: String = "Remove" + + protected abstract fun createItem(): T + @Composable + protected abstract fun buildView(item: T) + + override val content: @Composable ColumnScope.() -> Unit = { + Button({ itemsList.add(createItem()) }) { + CommonText(addItemText) + } + itemsList.forEach { item -> + Column(Modifier.padding(8.dp)) { + buildView(item) + Button({ itemsList.remove(item) }, Modifier.padding(8.dp)) { + CommonText(removeItemText) + } + } + } + } +} diff --git a/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/views/MavenInfoView.kt b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/views/MavenInfoView.kt new file mode 100644 index 0000000..2c66480 --- /dev/null +++ b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/views/MavenInfoView.kt @@ -0,0 +1,77 @@ +package com.insanusmokrassar.kmppscriptbuilder.views + +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.runtime.* +import com.insanusmokrassar.kmppscriptbuilder.models.MavenConfig +import com.insanusmokrassar.kmppscriptbuilder.models.SonatypeRepository +import com.insanusmokrassar.kmppscriptbuilder.utils.* + +class MavenInfoView : VerticalView("Project information") { + private var projectNameProperty by mutableStateOf("") + private var projectDescriptionProperty by mutableStateOf("") + private var projectUrlProperty by mutableStateOf("") + private var projectVcsUrlProperty by mutableStateOf("") + private var includeGpgSignProperty by mutableStateOf(true) + private var publishToMavenCentralProperty by mutableStateOf(false) + private val developersView = DevelopersView() + private val repositoriesView = RepositoriesView() + + var mavenConfig: MavenConfig + get() = MavenConfig( + projectNameProperty, + projectDescriptionProperty, + projectUrlProperty, + projectVcsUrlProperty, + includeGpgSignProperty, + developersView.developers, + repositoriesView.repositories + if (publishToMavenCentralProperty) { + listOf(SonatypeRepository) + } else { + emptyList() + } + ) + set(value) { + projectNameProperty = value.name + projectDescriptionProperty = value.description + projectUrlProperty = value.url + projectVcsUrlProperty = value.vcsUrl + includeGpgSignProperty = value.includeGpgSigning + publishToMavenCentralProperty = value.repositories.any { it == SonatypeRepository } + developersView.developers = value.developers + repositoriesView.repositories = value.repositories.filter { it != SonatypeRepository } +// developersView.developers = value.developers + } + + override val content: @Composable ColumnScope.() -> Unit = { + CommonTextField( + projectNameProperty, + "Public project name" + ) { projectNameProperty = it } + CommonTextField( + projectDescriptionProperty, + "Public project description" + ) { projectDescriptionProperty = it } + CommonTextField( + projectUrlProperty, + "Public project URL" + ) { projectUrlProperty = it } + CommonTextField( + projectVcsUrlProperty, + "Public project VCS URL (with .git)" + ) { projectVcsUrlProperty = it } + + SwitchWithLabel( + "Include GPG Signing", + includeGpgSignProperty, + placeSwitchAtTheStart = true + ) { includeGpgSignProperty = it } + + SwitchWithLabel( + "Include publication to MavenCentral", + publishToMavenCentralProperty, + placeSwitchAtTheStart = true + ) { publishToMavenCentralProperty = it } + developersView.init() + repositoriesView.init() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/views/ProjectTypeView.kt b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/views/ProjectTypeView.kt new file mode 100644 index 0000000..4301668 --- /dev/null +++ b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/views/ProjectTypeView.kt @@ -0,0 +1,33 @@ +package com.insanusmokrassar.kmppscriptbuilder.views + +import androidx.compose.foundation.layout.* +import androidx.compose.material.Switch +import androidx.compose.material.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.insanusmokrassar.kmppscriptbuilder.models.* +import com.insanusmokrassar.kmppscriptbuilder.utils.VerticalView + +class ProjectTypeView : VerticalView("Project type") { + private var projectTypeState by mutableStateOf(false) + private val calculatedProjectType: ProjectType + get() = if (projectTypeState) JVMProjectType else MultiplatformProjectType + var projectType: ProjectType + get() = calculatedProjectType + set(value) { + projectTypeState = value == JVMProjectType + } + + override val content: @Composable ColumnScope.() -> Unit = { + Row(horizontalArrangement = Arrangement.spacedBy(5.dp)) { + Text("Multiplatform", Modifier.alignByBaseline()) + Switch( + projectTypeState, + { projectTypeState = it }, + Modifier.padding(4.dp, 0.dp) + ) + Text("JVM", Modifier.alignByBaseline()) + } + } +} diff --git a/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/views/RepositoriesView.kt b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/views/RepositoriesView.kt new file mode 100644 index 0000000..78415c9 --- /dev/null +++ b/src/main/kotlin/com/insanusmokrassar/kmppscriptbuilder/views/RepositoriesView.kt @@ -0,0 +1,45 @@ +package com.insanusmokrassar.kmppscriptbuilder.views + +import androidx.compose.runtime.* +import com.insanusmokrassar.kmppscriptbuilder.models.MavenPublishingRepository +import com.insanusmokrassar.kmppscriptbuilder.utils.* + +class RepositoryState( + name: String = "", + url: String = "" +) { + var name: String by mutableStateOf(name) + var url: String by mutableStateOf(url) + + fun toRepository() = MavenPublishingRepository(name, url) +} + +private fun MavenPublishingRepository.toRepositoryState() = RepositoryState(name, url) + +class RepositoriesView : ListView("Repositories info") { + var repositories: List + get() = itemsList.map { it.toRepository() } + set(value) { + itemsList.clear() + itemsList.addAll( + value.map { it.toRepositoryState() } + ) + } + + override val addItemText: String = "Add repository" + override val removeItemText: String = "Remove repository" + + override fun createItem(): RepositoryState = RepositoryState() + @Composable + override fun buildView(item: RepositoryState) { + CommonTextField( + item.name, + "Repository name" + ) { item.name = it } + CommonTextField( + item.url, + "Repository url" + ) { item.url = it } + } + +} diff --git a/src/main/resources/images/export_gradle.svg b/src/main/resources/images/export_gradle.svg new file mode 100644 index 0000000..7c50f67 --- /dev/null +++ b/src/main/resources/images/export_gradle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/resources/images/open_file.svg b/src/main/resources/images/open_file.svg new file mode 100644 index 0000000..db9c6fa --- /dev/null +++ b/src/main/resources/images/open_file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/resources/images/save_as.svg b/src/main/resources/images/save_as.svg new file mode 100644 index 0000000..8974c0a --- /dev/null +++ b/src/main/resources/images/save_as.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/resources/images/save_file.svg b/src/main/resources/images/save_file.svg new file mode 100644 index 0000000..1ba1f97 --- /dev/null +++ b/src/main/resources/images/save_file.svg @@ -0,0 +1 @@ + \ No newline at end of file