diff --git a/Makefile b/Makefile index df28dd2..2bcc614 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,7 @@ taskList: cleanBuild # Generate compose stability report reportCompose: manualClean @echo "Compose Compiler Report" - $(GRADLEW) assembleDebug -PcomposeCompilerReports=true --rerun-tasks + $(GRADLEW) assembleDebug -PenableComposeCompilerMetrics=true -PenableComposeCompilerReports=true @echo "✅ Done!" # Run Android build @@ -41,16 +41,66 @@ buildAndroid: cleanBuild $(GRADLEW) androidApp:app:installDebug @echo "✅ Done!" +# Run IOS build +buildIOS: cleanBuild + @echo "IOS build" + chmod +x ./scripts/ios_script.sh + ./scripts/ios_script.sh + @echo "✅ Done!" + +# Run Android Ui test +testUiAndroid: cleanBuild + @echo "Android UI test" + $(GRADLEW) androidApp:app:connectedDebugAndroidTest + @echo "✅ Done!" + # Run Desktop build buildDesktop: cleanBuild @echo "Desktop build" $(GRADLEW) :desktopapp:run @echo "✅ Done!" -# Run web build -buildWeb: cleanBuild - @echo "Web build" - $(GRADLEW) webApp:js:wasmJsBrowserDevelopmentRun +# Run Desktop Ui test +testUiDesktop: cleanBuild + @echo "Desktop UI test" + $(GRADLEW) :desktopapp:jvmTest + @echo "✅ Done!" + +# Run Desktop hot reload build +buildHotDesktop: clear + @echo "Desktop Hot reload build" + $(GRADLEW) :desktopapp:hotRunJvm --auto + @echo "✅ Done!" + +# Run wasm build +buildWasmWeb: cleanBuild + @echo "Web Wasm build" + $(GRADLEW) webApp:wasm:wasmJsBrowserDevelopmentRun + @echo "✅ Done!" + +# Run wasm UI test +testUiWasmWeb: cleanBuild + @echo "Web wasm UI test" + $(GRADLEW) webApp:wasm:wasmJsBrowserTest + @echo "✅ Done!" + +# Run js build +buildJsWeb: cleanBuild + @echo "Web JS build" + $(GRADLEW) webApp:js:jsBrowserDevelopmentRun + @echo "✅ Done!" + +# Run JS UI test +testUiJsWeb: cleanBuild + @echo "Web Js UI test" + $(GRADLEW) webApp:wasm:jsBrowserTest + @echo "✅ Done!" + + +# Run wasm UI test +testUiJsWeb: cleanBuild + @echo "Web Js UI test" + $(GRADLEW) iosApp:iosSimulatorArm64Test @echo "✅ Done!" # Run All test diff --git a/androidApp/app/build.gradle.kts b/androidApp/app/build.gradle.kts index 4dd25b3..9f089f5 100644 --- a/androidApp/app/build.gradle.kts +++ b/androidApp/app/build.gradle.kts @@ -49,4 +49,7 @@ composeCompiler { dependencies { implementation(projects.sharedCode) implementation(libs.androidx.activity.compose) + + androidTestImplementation(libs.androidx.uitest.junit4) + debugImplementation(libs.androidx.uitest.testManifest) } diff --git a/androidApp/tv/build.gradle.kts b/androidApp/tv/build.gradle.kts new file mode 100644 index 0000000..3af0aed --- /dev/null +++ b/androidApp/tv/build.gradle.kts @@ -0,0 +1,52 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.compose.multiplatform) +} + +android { + namespace = "dev.reprator.github" + compileSdk = libs.versions.android.compileSdk.get().toInt() + + defaultConfig { + applicationId = "dev.reprator.github" + minSdk = libs.versions.android.minSdk.get().toInt() + targetSdk = libs.versions.android.targetSdk.get().toInt() + versionCode = 1 + versionName = "1.0" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } + buildTypes { + getByName("release") { + isMinifyEnabled = false + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + } + + kotlin { + compilerOptions { + jvmTarget = JvmTarget.JVM_21 + } + } +} + +composeCompiler { + reportsDestination = layout.buildDirectory.dir("andorid_tv_compose_compiler") + metricsDestination = layout.buildDirectory.dir("android_tv_compose_metric") + stabilityConfigurationFile.set(rootProject.file("compose-stability.conf")) +} + +dependencies { + implementation(projects.sharedCode) + implementation(libs.androidx.activity.compose) +} diff --git a/androidApp/tv/proguard-rules.pro b/androidApp/tv/proguard-rules.pro new file mode 100644 index 0000000..fd75bd9 --- /dev/null +++ b/androidApp/tv/proguard-rules.pro @@ -0,0 +1,62 @@ +# This is a configuration file for R8 + +-verbose +-allowaccessmodification +-repackageclasses + +# Note that you cannot just include these flags in your own +# configuration file; if you are including this file, optimization +# will be turned off. You'll need to either edit this file, or +# duplicate the contents of this file and remove the include of this +# file from your project's proguard.config path property. + +# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native +-keepclasseswithmembernames class * { + native ; +} + +# We only need to keep ComposeView +-keep public class androidx.compose.ui.platform.ComposeView { + public (android.content.Context, android.util.AttributeSet); +} + +# For enumeration classes +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +-keep class * implements android.os.Parcelable { + public static final android.os.Parcelable$Creator *; +} + +# AndroidX + support library contains references to newer platform versions. +# Don't warn about those in case this app is linking against an older +# platform version. We know about them, and they are safe. +-dontwarn android.support.** +-dontwarn androidx.** + +-keepattributes SourceFile, + LineNumberTable, + RuntimeVisibleAnnotations, + RuntimeVisibleParameterAnnotations, + RuntimeVisibleTypeAnnotations, + AnnotationDefault + +-renamesourcefileattribute SourceFile + +# Dagger +-dontwarn com.google.errorprone.annotations.* + +# Retain the generic signature of retrofit2.Call until added to Retrofit. +# Issue: https://github.com/square/retrofit/issues/3580. +# Pull request: https://github.com/square/retrofit/pull/3579. +-keep,allowobfuscation,allowshrinking class retrofit2.Call + +# See https://issuetracker.google.com/issues/265188224 +-keep,allowshrinking class * extends androidx.compose.ui.node.ModifierNodeElement {} + +# Using ktor client in Android has missing proguard rule +# See https://youtrack.jetbrains.com/issue/KTOR-5528 +-dontwarn org.slf4j.** +-dontwarn org.slf4j.impl.StaticLoggerBinder \ No newline at end of file diff --git a/androidApp/tv/src/main/AndroidManifest.xml b/androidApp/tv/src/main/AndroidManifest.xml new file mode 100644 index 0000000..726f8a3 --- /dev/null +++ b/androidApp/tv/src/main/AndroidManifest.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/androidApp/tv/src/main/kotlin/dev/reprator/github/GithubApp.kt b/androidApp/tv/src/main/kotlin/dev/reprator/github/GithubApp.kt new file mode 100644 index 0000000..b58764f --- /dev/null +++ b/androidApp/tv/src/main/kotlin/dev/reprator/github/GithubApp.kt @@ -0,0 +1,12 @@ +package dev.reprator.github + +import android.app.Application +import dev.reprator.github.di.inject.component.AndroidApplicationComponent +import dev.reprator.github.di.inject.component.create + +class GithubApp : Application() { + + val component: AndroidApplicationComponent by lazy { + AndroidApplicationComponent.create(this) + } +} \ No newline at end of file diff --git a/androidApp/tv/src/main/kotlin/dev/reprator/github/MainActivity.kt b/androidApp/tv/src/main/kotlin/dev/reprator/github/MainActivity.kt new file mode 100644 index 0000000..d14c9b4 --- /dev/null +++ b/androidApp/tv/src/main/kotlin/dev/reprator/github/MainActivity.kt @@ -0,0 +1,29 @@ +package dev.reprator.github + +import android.content.Context +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import dev.reprator.github.di.inject.component.AndroidActivityComponent +import dev.reprator.github.di.inject.component.AndroidApplicationComponent +import dev.reprator.github.di.inject.component.create +import dev.reprator.github.root.App + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + enableEdgeToEdge() + super.onCreate(savedInstanceState) + + val applicationComponent = AndroidApplicationComponent.from(this) + val component = AndroidActivityComponent.create(this, applicationComponent) + + setContent { + App(component.routeFactories) + } + } +} + +private fun AndroidApplicationComponent.Companion.from(context: Context): AndroidApplicationComponent { + return (context.applicationContext as GithubApp).component +} \ No newline at end of file diff --git a/androidApp/tv/src/main/res/drawable-v24/ic_launcher_foreground.xml b/androidApp/tv/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/androidApp/tv/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/androidApp/tv/src/main/res/drawable/ic_launcher_background.xml b/androidApp/tv/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..e93e11a --- /dev/null +++ b/androidApp/tv/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/androidApp/tv/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/androidApp/tv/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/androidApp/tv/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/androidApp/tv/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/androidApp/tv/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/androidApp/tv/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/androidApp/tv/src/main/res/mipmap-hdpi/ic_launcher.png b/androidApp/tv/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..a571e60 Binary files /dev/null and b/androidApp/tv/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/androidApp/tv/src/main/res/mipmap-hdpi/ic_launcher_round.png b/androidApp/tv/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..61da551 Binary files /dev/null and b/androidApp/tv/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/androidApp/tv/src/main/res/mipmap-mdpi/ic_launcher.png b/androidApp/tv/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..c41dd28 Binary files /dev/null and b/androidApp/tv/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/androidApp/tv/src/main/res/mipmap-mdpi/ic_launcher_round.png b/androidApp/tv/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..db5080a Binary files /dev/null and b/androidApp/tv/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/androidApp/tv/src/main/res/mipmap-xhdpi/ic_launcher.png b/androidApp/tv/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..6dba46d Binary files /dev/null and b/androidApp/tv/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/androidApp/tv/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/androidApp/tv/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..da31a87 Binary files /dev/null and b/androidApp/tv/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/androidApp/tv/src/main/res/mipmap-xxhdpi/ic_launcher.png b/androidApp/tv/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..15ac681 Binary files /dev/null and b/androidApp/tv/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/androidApp/tv/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/androidApp/tv/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..b216f2d Binary files /dev/null and b/androidApp/tv/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/androidApp/tv/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/androidApp/tv/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..f25a419 Binary files /dev/null and b/androidApp/tv/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/androidApp/tv/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/androidApp/tv/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..e96783c Binary files /dev/null and b/androidApp/tv/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/androidApp/tv/src/main/res/values/strings.xml b/androidApp/tv/src/main/res/values/strings.xml new file mode 100644 index 0000000..e40e3f4 --- /dev/null +++ b/androidApp/tv/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Github + \ No newline at end of file diff --git a/androidApp/tv/src/main/res/xml/network_security_config.xml b/androidApp/tv/src/main/res/xml/network_security_config.xml new file mode 100644 index 0000000..d951897 --- /dev/null +++ b/androidApp/tv/src/main/res/xml/network_security_config.xml @@ -0,0 +1,7 @@ + + + + api.github.com + avatars.githubusercontent.com + + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index b00238d..4509dc2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,5 +5,28 @@ plugins { alias(libs.plugins.kotlin.multiplatform) apply false alias(libs.plugins.compose.multiplatform) apply false alias(libs.plugins.compose.compiler) apply false + alias(libs.plugins.compose.hotReload) alias(libs.plugins.ksp) apply false + alias(libs.plugins.spotless) apply false + alias(libs.plugins.kover) apply false +} + + +subprojects { + apply(plugin = "com.diffplug.spotless") + configure { + kotlin { + target("**/*.kt") + targetExclude("${layout.buildDirectory}/**/*.kt") + ktlint() + licenseHeaderFile(rootProject.file("scripts/spotless/copyright.kt")) + } + kotlinGradle { + target("*.gradle.kts") + targetExclude("${layout.buildDirectory}/**/*.kt") + ktlint() + // Look for the first line that doesn't have a block comment (assumed to be the license) + licenseHeaderFile(rootProject.file("scripts/spotless/copyright.kt"), "(^(?![\\/ ]\\*).*$)") + } + } } diff --git a/desktopApp/build.gradle.kts b/desktopApp/build.gradle.kts index 746e7ef..8a4bcbf 100644 --- a/desktopApp/build.gradle.kts +++ b/desktopApp/build.gradle.kts @@ -1,10 +1,12 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.compose.reload.gradle.ComposeHotRun plugins { alias(libs.plugins.compose.compiler) alias(libs.plugins.compose.multiplatform) alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.compose.hotReload) } kotlin { @@ -22,6 +24,10 @@ kotlin { } } +tasks.withType().configureEach { + mainClass = "dev.reprator.github.MainKt" +} + compose.desktop { application { mainClass = "dev.reprator.github.MainKt" @@ -31,7 +37,19 @@ compose.desktop { packageName = "dev.reprator.github" packageVersion = "1.0.0" includeAllModules = true + + linux { + iconFile.set(project.file("resources/LinuxIcon.png")) + } + windows { + iconFile.set(project.file("resources/WindowsIcon.ico")) + } + macOS { + iconFile.set(project.file("resources/MacosIcon.icns")) + bundleID = "org.company.app.desktopApp" + } } + buildTypes.release.proguard { configurationFiles.from("compose-desktop.pro") } diff --git a/desktopApp/src/jvmMain/resources/LinuxIcon.png b/desktopApp/src/jvmMain/resources/LinuxIcon.png new file mode 100644 index 0000000..a2a4517 Binary files /dev/null and b/desktopApp/src/jvmMain/resources/LinuxIcon.png differ diff --git a/desktopApp/src/jvmMain/resources/MacosIcon.icns b/desktopApp/src/jvmMain/resources/MacosIcon.icns new file mode 100644 index 0000000..083def5 Binary files /dev/null and b/desktopApp/src/jvmMain/resources/MacosIcon.icns differ diff --git a/desktopApp/src/jvmMain/resources/WindowsIcon.ico b/desktopApp/src/jvmMain/resources/WindowsIcon.ico new file mode 100644 index 0000000..7e9ab75 Binary files /dev/null and b/desktopApp/src/jvmMain/resources/WindowsIcon.ico differ diff --git a/gradle.properties b/gradle.properties index 6f8e6ea..d5ec703 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,12 +1,19 @@ -#Kotlin -kotlin.code.style=official -kotlin.daemon.jvmargs=-Xmx3072M - #Gradle -org.gradle.jvmargs=-Xmx4096M -Dfile.encoding=UTF-8 -org.gradle.configuration-cache=true +org.gradle.jvmargs=-Xmx4G org.gradle.caching=true +org.gradle.configuration-cache=true +org.gradle.daemon=true +org.gradle.parallel=true + +#Kotlin +kotlin.code.style=official +kotlin.daemon.jvmargs=-Xmx4G +kotlin.native.binary.gc=cms +kotlin.incremental.wasm=true #Android +android.useAndroidX=true android.nonTransitiveRClass=true -android.useAndroidX=true \ No newline at end of file + +#Compose +org.jetbrains.compose.experimental.jscanvas.enabled=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7882e18..ad7a9c6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,6 +9,7 @@ androidx-constraintlayout = "2.2.1" androidx-core = "1.16.0" androidx-espresso = "3.6.1" androidx-lifecycle = "2.9.1" +androidx-uiTest = "1.8.3" androidx-navigation = "2.9.0-beta03" androidx-testExt = "1.2.1" composeHotReload = "1.0.0-beta04" @@ -17,7 +18,7 @@ junit = "4.13.2" kotlin = "2.2.0" kotlinx-coroutines = "1.10.2" ktor = "3.2.2" -coil = "3.2.0" +coil = "3.3.0" kotlininject = "0.8.0" turbine = "1.2.1" ksp = "2.2.0-2.0.2" @@ -29,12 +30,16 @@ spotless = "7.1.0" kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } kotlin-testJunit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } junit = { module = "junit:junit", version.ref = "junit" } +androidx-uitest-testManifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "androidx-uiTest" } +androidx-uitest-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "androidx-uiTest" } androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-core" } androidx-testExt-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-testExt" } androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-espresso" } androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "androidx-constraintlayout" } androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" } +androidx-tv-foundation = { group = "androidx.tv", name = "tv-foundation", version = "1.0.0-alpha12" } +androidx-tv-material = { group = "androidx.tv", name = "tv-material", version = "1.0.1" } androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name ="lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } androidx-lifecycle-viewmodel-compose = { group = "org.jetbrains.androidx.lifecycle", name ="lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" } @@ -74,7 +79,7 @@ test-turbine = { module="app.cash.turbine:turbine", version = "1.2.1" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } android-library = { id = "com.android.library", version.ref = "agp" } -composeHotReload = { id = "org.jetbrains.compose.hot-reload", version.ref = "composeHotReload" } +compose-hotReload = { id = "org.jetbrains.compose.hot-reload", version.ref = "composeHotReload" } compose-multiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } diff --git a/scripts/ios_script.sh b/scripts/ios_script.sh new file mode 100755 index 0000000..28a4b85 --- /dev/null +++ b/scripts/ios_script.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +echo "🚀 Building Compose Multiplatform App..." + +# Build the shared Compose framework +echo "📦 Building shared Compose Multiplatform framework..." +./gradlew :shared:compileKotlinIosSimulatorArm64 +./gradlew :shared:linkDebugFrameworkIosSimulatorArm64 + +if [ $? -eq 0 ]; then + echo "✅ Compose Multiplatform framework built successfully!" + + echo "📱 Starting iOS Simulator..." + xcrun simctl boot "iPhone 16 Pro" 2>/dev/null || echo "Simulator already running" + open -a Simulator + + echo "🍎 Opening iOS project in Xcode..." + open iosApp/iosApp.xcodeproj + + echo "" + echo "🎯 NEXT STEPS IN XCODE:" + echo "1. Wait for Xcode to open" + echo "2. Click on 'Any iOS Device' dropdown (top-left)" + echo "3. Select 'iPhone 16 Pro' from the list" + echo "4. Press Cmd+R or click ▶️ to run" + echo "" +else + echo "❌ Framework build failed!" + exit 1 +fi \ No newline at end of file diff --git a/scripts/spotless/copyright.kt b/scripts/spotless/copyright.kt new file mode 100644 index 0000000..f34d469 --- /dev/null +++ b/scripts/spotless/copyright.kt @@ -0,0 +1,16 @@ +/* + * Copyright $YEAR @TheReprator + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + diff --git a/settings.gradle.kts b/settings.gradle.kts index dd85752..c498e3e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -34,5 +34,7 @@ plugins { include(":sharedCode") include(":androidApp:app") +include(":androidApp:tv") include(":desktopApp") include(":webapp:js") +include(":webapp:wasm") diff --git a/sharedCode/build.gradle.kts b/sharedCode/build.gradle.kts index 7f201d9..64e3ddd 100644 --- a/sharedCode/build.gradle.kts +++ b/sharedCode/build.gradle.kts @@ -1,6 +1,7 @@ -@file:OptIn(ExperimentalWasmDsl::class) +@file:OptIn(ExperimentalWasmDsl::class, ExperimentalComposeLibrary::class) import org.gradle.kotlin.dsl.getByType import org.gradle.kotlin.dsl.invoke +import org.jetbrains.compose.ExperimentalComposeLibrary import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension @@ -44,7 +45,12 @@ kotlin { wasmJs { browser() } - + + js { + browser() + binaries.executable() + } + sourceSets { val desktopMain by getting @@ -93,6 +99,7 @@ kotlin { implementation(libs.kotlin.test) implementation(libs.kotlinx.coroutines.test) implementation(libs.test.turbine) + implementation(compose.uiTest) } desktopMain.dependencies { @@ -104,6 +111,10 @@ kotlin { wasmJsMain.dependencies { implementation(libs.ktor.client.js) } + + jsMain.dependencies { + implementation(libs.ktor.client.js) + } } } diff --git a/sharedCode/src/commonTest/kotlin/dev/reprator/github/ComposeAppCommonTest.kt b/sharedCode/src/commonTest/kotlin/dev/reprator/github/ComposeAppCommonTest.kt deleted file mode 100644 index c220688..0000000 --- a/sharedCode/src/commonTest/kotlin/dev/reprator/github/ComposeAppCommonTest.kt +++ /dev/null @@ -1,12 +0,0 @@ -package dev.reprator.github - -import kotlin.test.Test -import kotlin.test.assertEquals - -class ComposeAppCommonTest { - - @Test - fun example() { - assertEquals(3, 1 + 2) - } -} \ No newline at end of file diff --git a/sharedCode/src/commonTest/kotlin/dev/reprator/github/features/userList/presentation/UserListViewModelTest.kt b/sharedCode/src/commonTest/kotlin/dev/reprator/github/features/userList/presentation/UserListViewModelTest.kt index 49a7794..9d10fb6 100644 --- a/sharedCode/src/commonTest/kotlin/dev/reprator/github/features/userList/presentation/UserListViewModelTest.kt +++ b/sharedCode/src/commonTest/kotlin/dev/reprator/github/features/userList/presentation/UserListViewModelTest.kt @@ -218,7 +218,7 @@ class UserListViewModelTest: MainDispatcherRule() { } @Test - fun searchForUserOnTypeWhenPreviousListAlreadyHaveDefaultUsers() { + fun searchForUserOnTypeWhenPreviousListAlreadyHaveDefaultUsers() = runTest { everySuspend { fetchUseCase() @@ -259,10 +259,8 @@ class UserListViewModelTest: MainDispatcherRule() { } } - - @Test - fun searchForUserOnTypeWhenDefaultUserListIsEmpty() { - + @Test + fun searchForUserOnTypeWhenDefaultUserListIsEmpty() = runTest { everySuspend { fetchUseCase() } returns AppSuccess>(emptyList()) diff --git a/sharedCode/src/jsMain/kotlin/dev/reprator/github/di/inject/application/SharedApplicationComponent.kt b/sharedCode/src/jsMain/kotlin/dev/reprator/github/di/inject/application/SharedApplicationComponent.kt new file mode 100644 index 0000000..f8e9877 --- /dev/null +++ b/sharedCode/src/jsMain/kotlin/dev/reprator/github/di/inject/application/SharedApplicationComponent.kt @@ -0,0 +1,25 @@ +package dev.reprator.github.di.inject.application + +import dev.reprator.github.di.inject.ApplicationScope + +import dev.reprator.github.util.AppCoroutineDispatchers +import io.ktor.client.engine.HttpClientEngine +import io.ktor.client.engine.js.Js +import kotlinx.coroutines.Dispatchers +import me.tatarka.inject.annotations.Provides + +actual interface SharedPlatformApplicationComponent { + + @Provides + @ApplicationScope + fun provideHttpClientEngine(): HttpClientEngine = Js.create() + + @ApplicationScope + @Provides + fun provideCoroutineDispatchers(): AppCoroutineDispatchers = AppCoroutineDispatchers( + io = Dispatchers.Default, + singleThread = Dispatchers.Default, + computation = Dispatchers.Default, + main = Dispatchers.Main, + ) +} diff --git a/sharedCode/src/jsMain/kotlin/dev/reprator/github/di/inject/component/JsApplicationComponent.kt b/sharedCode/src/jsMain/kotlin/dev/reprator/github/di/inject/component/JsApplicationComponent.kt new file mode 100644 index 0000000..014875b --- /dev/null +++ b/sharedCode/src/jsMain/kotlin/dev/reprator/github/di/inject/component/JsApplicationComponent.kt @@ -0,0 +1,14 @@ +package dev.reprator.github.di.inject.component + + +import dev.reprator.github.di.inject.ApplicationScope +import dev.reprator.github.di.inject.application.SharedApplicationComponent +import me.tatarka.inject.annotations.Component + +@Component +@ApplicationScope +abstract class JsApplicationComponent( +) : SharedApplicationComponent { + + companion object +} diff --git a/sharedCode/src/jsMain/kotlin/dev/reprator/github/di/inject/component/JsWindowComponent.kt b/sharedCode/src/jsMain/kotlin/dev/reprator/github/di/inject/component/JsWindowComponent.kt new file mode 100644 index 0000000..7177685 --- /dev/null +++ b/sharedCode/src/jsMain/kotlin/dev/reprator/github/di/inject/component/JsWindowComponent.kt @@ -0,0 +1,14 @@ +package dev.reprator.github.di.inject.component + +import dev.reprator.github.di.inject.ActivityScope +import dev.reprator.github.di.inject.activity.SharedModuleComponent +import me.tatarka.inject.annotations.Component + +@ActivityScope +@Component +abstract class JsWindowComponent( + @Component val applicationComponent: JsApplicationComponent, +) : SharedModuleComponent { + + companion object +} \ No newline at end of file diff --git a/webApp/js/build.gradle.kts b/webApp/js/build.gradle.kts index a3fee08..2b64d89 100644 --- a/webApp/js/build.gradle.kts +++ b/webApp/js/build.gradle.kts @@ -10,7 +10,7 @@ plugins { } kotlin { - wasmJs { + js { outputModuleName.set("github") browser { val rootDirPath = project.rootDir.path diff --git a/webApp/js/src/jsMain/kotlin/dev/reprator/github/Main.kt b/webApp/js/src/jsMain/kotlin/dev/reprator/github/Main.kt new file mode 100644 index 0000000..6a0a2d0 --- /dev/null +++ b/webApp/js/src/jsMain/kotlin/dev/reprator/github/Main.kt @@ -0,0 +1,30 @@ +package dev.reprator.github + +import androidx.compose.runtime.remember +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.window.ComposeViewport +import dev.reprator.github.di.inject.component.JsApplicationComponent +import dev.reprator.github.di.inject.component.JsWindowComponent +import dev.reprator.github.di.inject.component.create +import dev.reprator.github.root.App +import kotlinx.browser.document +import org.jetbrains.skiko.wasm.onWasmReady + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + onWasmReady { + val body = document.body ?: return@onWasmReady + ComposeViewport(body) { + + val applicationComponent = remember { + JsApplicationComponent.create() + } + + val component = remember(applicationComponent) { + JsWindowComponent.create(applicationComponent) + } + + App(component.routeFactories) + } + } +} \ No newline at end of file diff --git a/webApp/js/src/jsMain/resources/index.html b/webApp/js/src/jsMain/resources/index.html new file mode 100644 index 0000000..ce536a4 --- /dev/null +++ b/webApp/js/src/jsMain/resources/index.html @@ -0,0 +1,13 @@ + + + + + + Github + + + + + + + \ No newline at end of file diff --git a/webApp/js/src/wasmJsMain/resources/styles.css b/webApp/js/src/jsMain/resources/styles.css similarity index 100% rename from webApp/js/src/wasmJsMain/resources/styles.css rename to webApp/js/src/jsMain/resources/styles.css diff --git a/webApp/wasm/build.gradle.kts b/webApp/wasm/build.gradle.kts new file mode 100644 index 0000000..a3fee08 --- /dev/null +++ b/webApp/wasm/build.gradle.kts @@ -0,0 +1,40 @@ +@file:OptIn(ExperimentalWasmDsl::class) + +import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl +import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig + +plugins { + alias(libs.plugins.compose.compiler) + alias(libs.plugins.compose.multiplatform) + alias(libs.plugins.kotlin.multiplatform) +} + +kotlin { + wasmJs { + outputModuleName.set("github") + browser { + val rootDirPath = project.rootDir.path + val projectDirPath = project.projectDir.path + commonWebpackConfig { + outputFileName = "github.js" + devServer = (devServer ?: KotlinWebpackConfig.DevServer()).apply { + static = (static ?: mutableListOf()).apply { + // Serve sources to debug inside browser + add(rootDirPath) + add(projectDirPath) + } + } + } + } + binaries.executable() + } + + sourceSets { + commonMain { + dependencies { + implementation(compose.ui) + implementation(projects.sharedCode) + } + } + } +} \ No newline at end of file diff --git a/webApp/js/src/wasmJsMain/kotlin/dev/reprator/github/Main.kt b/webApp/wasm/src/wasmJsMain/kotlin/dev/reprator/github/Main.kt similarity index 100% rename from webApp/js/src/wasmJsMain/kotlin/dev/reprator/github/Main.kt rename to webApp/wasm/src/wasmJsMain/kotlin/dev/reprator/github/Main.kt diff --git a/webApp/js/src/wasmJsMain/resources/index.html b/webApp/wasm/src/wasmJsMain/resources/index.html similarity index 100% rename from webApp/js/src/wasmJsMain/resources/index.html rename to webApp/wasm/src/wasmJsMain/resources/index.html diff --git a/webApp/wasm/src/wasmJsMain/resources/styles.css b/webApp/wasm/src/wasmJsMain/resources/styles.css new file mode 100644 index 0000000..0549b10 --- /dev/null +++ b/webApp/wasm/src/wasmJsMain/resources/styles.css @@ -0,0 +1,7 @@ +html, body { + width: 100%; + height: 100%; + margin: 0; + padding: 0; + overflow: hidden; +} \ No newline at end of file