chore: initial import

This commit is contained in:
2026-07-24 21:37:03 +02:00
commit 6b4ed9bc36
111 changed files with 95131 additions and 0 deletions
+74
View File
@@ -0,0 +1,74 @@
[versions]
agp = "8.9.1"
kotlin = "2.1.20"
composeBom = "2025.05.01"
activityCompose = "1.10.1"
lifecycle = "2.9.0"
navigation = "2.9.0"
hilt = "2.56.2"
hiltNavigation = "1.2.0"
room = "2.7.1"
datastore = "1.1.7"
work = "2.10.1"
retrofit = "2.11.0"
okhttp = "4.12.0"
serialization = "1.8.1"
coreKtx = "1.16.0"
securityCrypto = "1.1.0-alpha06"
junit = "4.13.2"
androidxJunit = "1.2.1"
espresso = "3.6.1"
testRunner = "1.6.2"
testCore = "1.6.1"
mockwebserver = "4.12.0"
coroutines = "1.10.2"
[libraries]
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" }
androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycle" }
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigation" }
compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" }
compose-ui = { module = "androidx.compose.ui:ui" }
compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
compose-material3 = { module = "androidx.compose.material3:material3" }
compose-icons = { module = "androidx.compose.material:material-icons-extended" }
compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4" }
compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" }
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt" }
hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigation" }
hilt-work = { module = "androidx.hilt:hilt-work", version.ref = "hiltNavigation" }
hilt-work-compiler = { module = "androidx.hilt:hilt-compiler", version.ref = "hiltNavigation" }
room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
room-testing = { module = "androidx.room:room-testing", version.ref = "room" }
datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastore" }
work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "work" }
work-testing = { module = "androidx.work:work-testing", version.ref = "work" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
retrofit-kotlinx = { module = "com.squareup.retrofit2:converter-kotlinx-serialization", version.ref = "retrofit" }
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
junit = { module = "junit:junit", version.ref = "junit" }
androidx-junit = { module = "androidx.test.ext:junit", version.ref = "androidxJunit" }
espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" }
androidx-test-runner = { module = "androidx.test:runner", version.ref = "testRunner" }
androidx-test-core-ktx = { module = "androidx.test:core-ktx", version.ref = "testCore" }
hilt-android-testing = { module = "com.google.dagger:hilt-android-testing", version.ref = "hilt" }
mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "mockwebserver" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
@@ -0,0 +1,174 @@
package org.gradle.wrapper;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.HexFormat;
import java.util.List;
import java.util.Properties;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
* Small auditable Gradle wrapper bootstrap used because the official wrapper JAR
* could not be generated in the build container. It downloads the configured
* distribution, validates SHA-256, extracts it with zip-slip protection and
* starts Gradle in the project directory.
*/
public final class GradleWrapperMain {
private GradleWrapperMain() {}
public static void main(String[] args) throws Exception {
Path jar = Paths.get(GradleWrapperMain.class.getProtectionDomain().getCodeSource().getLocation().toURI());
Path projectRoot = jar.getParent().getParent().getParent();
Path propertiesPath = jar.resolveSibling("gradle-wrapper.properties");
Properties properties = new Properties();
try (InputStream input = Files.newInputStream(propertiesPath)) {
properties.load(input);
}
URI distributionUri = URI.create(required(properties, "distributionUrl"));
String archiveName = Paths.get(distributionUri.getPath()).getFileName().toString();
String distributionName = archiveName.replaceFirst("-(bin|all)\\.zip$", "");
Path distributionDirectory = gradleUserHome(args)
.resolve(properties.getProperty("distributionPath", "wrapper/dists"))
.resolve(distributionName)
.resolve(archiveName);
Path archive = distributionDirectory.resolve(archiveName);
Path marker = distributionDirectory.resolve(".installed");
Files.createDirectories(distributionDirectory);
if (!Files.exists(marker)) {
if (!Files.exists(archive)) {
download(distributionUri.toURL(), archive, intProperty(properties, "networkTimeout", 10_000));
}
String expectedSha = properties.getProperty("distributionSha256Sum", "").trim();
if (!expectedSha.isEmpty()) {
verifySha256(archive, expectedSha);
}
unzip(archive, distributionDirectory);
Files.writeString(marker, "ok\n", StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
}
Path gradleHome;
try (Stream<Path> children = Files.list(distributionDirectory)) {
gradleHome = children
.filter(Files::isDirectory)
.filter(path -> path.getFileName().toString().startsWith("gradle-"))
.findFirst()
.orElseThrow(() -> new IOException("Extracted Gradle directory not found in " + distributionDirectory));
}
boolean windows = System.getProperty("os.name", "").toLowerCase().contains("win");
Path executable = gradleHome.resolve("bin").resolve(windows ? "gradle.bat" : "gradle");
if (!windows) executable.toFile().setExecutable(true);
List<String> command = new ArrayList<>();
command.add(executable.toString());
for (String argument : args) command.add(argument);
Process process = new ProcessBuilder(command)
.directory(projectRoot.toFile())
.inheritIO()
.start();
System.exit(process.waitFor());
}
private static Path gradleUserHome(String[] args) {
for (int index = 0; index < args.length - 1; index++) {
if ("-g".equals(args[index]) || "--gradle-user-home".equals(args[index])) {
return Paths.get(args[index + 1]).toAbsolutePath();
}
}
String configured = System.getenv("GRADLE_USER_HOME");
return configured == null || configured.isBlank()
? Paths.get(System.getProperty("user.home"), ".gradle")
: Paths.get(configured).toAbsolutePath();
}
private static void download(URL initialUrl, Path destination, int timeoutMillis) throws Exception {
URL url = initialUrl;
for (int redirect = 0; redirect < 8; redirect++) {
URLConnection connection = url.openConnection();
connection.setConnectTimeout(timeoutMillis);
connection.setReadTimeout(Math.max(timeoutMillis, 30_000));
connection.setRequestProperty("User-Agent", "boehmitools-training-gradle-wrapper");
if (connection instanceof HttpURLConnection http) {
http.setInstanceFollowRedirects(false);
int status = http.getResponseCode();
if (status >= 300 && status < 400) {
String location = http.getHeaderField("Location");
if (location == null) throw new IOException("Redirect without Location from " + url);
url = url.toURI().resolve(location).toURL();
http.disconnect();
continue;
}
if (status >= 400) throw new IOException("HTTP " + status + " while downloading " + url);
}
Path partial = destination.resolveSibling(destination.getFileName() + ".part");
try (InputStream input = connection.getInputStream(); OutputStream output = Files.newOutputStream(partial)) {
input.transferTo(output);
}
Files.move(partial, destination, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
return;
}
throw new IOException("Too many redirects while downloading " + initialUrl);
}
private static void verifySha256(Path file, String expected) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
try (InputStream input = Files.newInputStream(file)) {
byte[] buffer = new byte[64 * 1024];
for (int read; (read = input.read(buffer)) >= 0;) {
if (read > 0) digest.update(buffer, 0, read);
}
}
String actual = HexFormat.of().formatHex(digest.digest());
if (!actual.equalsIgnoreCase(expected)) {
Files.deleteIfExists(file);
throw new IOException("Gradle distribution SHA-256 mismatch: expected " + expected + ", got " + actual);
}
}
private static void unzip(Path archive, Path destination) throws IOException {
try (ZipInputStream zip = new ZipInputStream(Files.newInputStream(archive))) {
for (ZipEntry entry; (entry = zip.getNextEntry()) != null;) {
Path target = destination.resolve(entry.getName()).normalize();
if (!target.startsWith(destination.normalize())) {
throw new IOException("Unsafe ZIP entry: " + entry.getName());
}
if (entry.isDirectory()) {
Files.createDirectories(target);
} else {
Files.createDirectories(target.getParent());
Files.copy(zip, target, StandardCopyOption.REPLACE_EXISTING);
}
zip.closeEntry();
}
}
}
private static String required(Properties properties, String key) {
String value = properties.getProperty(key);
if (value == null || value.isBlank()) throw new IllegalArgumentException("Missing " + key);
return value;
}
private static int intProperty(Properties properties, String key, int fallback) {
try {
return Integer.parseInt(properties.getProperty(key, Integer.toString(fallback)));
} catch (NumberFormatException ignored) {
return fallback;
}
}
}
+1
View File
@@ -0,0 +1 @@
f397b287023acdba1e9f6fc5ea72d22dd63669d59ed4a289a29b1a76eee151c6
Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionSha256Sum=f397b287023acdba1e9f6fc5ea72d22dd63669d59ed4a289a29b1a76eee151c6