commit 1656e706a0e9dad56f2c6062ee352fdab86ff058 Author: RyanairRecruitment <106657483+RyanairRecruitment@users.noreply.github.com> Date: Thu Jun 12 10:14:10 2025 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cee9f91 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +app/build +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/LICENCE.md b/LICENCE.md new file mode 100644 index 0000000..c453195 --- /dev/null +++ b/LICENCE.md @@ -0,0 +1,7 @@ +### LICENCE + +Copyright © 2022 Ryanair Labs + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files ( the "Software" ), to deal in the Software only with the scope of recruitment process, with +limitation the rights to publish, distribute, sublicense, and/or sell copies of the Software. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ccc9ada --- /dev/null +++ b/README.md @@ -0,0 +1,95 @@ +# Android challenge + +## The task + +We would like you to build an app to search for flights. + +You are provided with a codebase containing a bit of code so that you can start working on the task faster. It +includes some parts of the UI, basic network layer implementation with DTOs & DI setup. Feel free to modify any file +you find in the repo – this is only there to help you out. + +Please develop an Android app with the following requirements: + +--- + +### Search screen + +* User can search for flights with the following criteria: + * Origin & destination station + * Departure date + * Number of adults, teen & children +* Destination can only be selected after picking origin +* All fields need to be filled to make a search +* Number of adults must be greater than 0 + +### Station search screen + +* User can select a station from a list +* Stations are grouped by country (country name to be shown before each group) +* User can use search field to filter the airports list +* Screen works with 2 configurations + * Origin selection – all stations are available for selection + * Destination selection – only stations to which we can travel from origin selection are available for selection + +### Results screen + +* A list of results for the specified search criteria is shown +* Show only the crucial information about the trip +* Make sure to handle network errors and empty results + +___ + +**URLs:** + +* for `AirportService` and `RoutesService`: +* for `FlightService`: + +*Tip: For test purposes try DUB (Dublin) – STN (London Stansted) route – it has flights every day.* + +--- + +### What we expect from you + +* Architecture for both domain and UI layer. Please avoid using framework libraries if possible. +* Use `kotlinx.coroutines` library. Do not use Live Data. +* Squeeze all the best from Kotlin! +* UI part is not crucial, we would more appreciate better code than great design in this task. +* Creativity - shine in areas you are best! +* Understanding of your solution. + +### Questions for you + +Please submit the answers to the following questions in the README.md file +* How long did you spend on the task? +* Rate from 0-10, how would you assess the complexity of the task? _0 - very trivial_, _10 - extremely complex_ +* If you had more time (or if you were doing it in production) what would you have changed/improved? +* Which parts of the code would you like to highlight? +* In your opinion, which Google library was the best addition to the Android Dev world in the last years? + +> If you have the need to comment the provided task, we would love to hear that from you 🙂 +> ... +> ... + +### Time frame + +You have __4 days__ to submit your solution. There is no need to spend more than 12 hours on the task as we don't seek perfection. +Focus on the key areas of Android development +--- + +#### Please fork this repository. After finishing a task, create pull request to the parent repository ``main`` branch. + +You are allowed to use AI tools to assist with this programming task. However, you will be asked to explain your solutions in detail. + +--- + +In case of any questions feel free to drop us an email at _AndroidDevelopers@ryanair.com_ any time. + + + +Fly high! + +___ + +### COPYRIGHTS + +Copyright licensed by © Ryanair Labs diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..88a3501 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,76 @@ +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("org.jetbrains.kotlin.plugin.compose") + id("com.google.devtools.ksp") + id("com.google.dagger.hilt.android") +} + +android { + namespace = "com.ryanair.androidchallenge" + compileSdk = 35 + + defaultConfig { + applicationId = "com.ryanair.androidchallenge" + versionCode = 1 + versionName = "1.0" + multiDexEnabled = true + + minSdk = 28 + targetSdk = 35 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + getByName("debug") { + isDebuggable = true + isMinifyEnabled = false + isShrinkResources = false + } + getByName("release") { + isMinifyEnabled = true + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } + buildFeatures { + viewBinding = true + compose = true + } +} + +dependencies { + + val composeBom = platform("androidx.compose:compose-bom:2025.04.01") + implementation(composeBom) + + implementation("androidx.compose.material3:material3") + implementation("androidx.compose.material:material-icons-core") + implementation("androidx.compose.ui:ui-tooling-preview") + + implementation("androidx.activity:activity-compose:1.10.1") + implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.9.0") + + implementation("androidx.core:core-ktx:1.16.0") + implementation("androidx.appcompat:appcompat:1.7.0") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.9.0") + + // Dependency Injection + implementation("com.google.dagger:hilt-android:2.56.2") + ksp("com.google.dagger:hilt-compiler:2.56.2") + + // Network + ksp("com.squareup.moshi:moshi-kotlin-codegen:1.15.2") + implementation("com.squareup.moshi:moshi:1.15.2") + implementation("com.squareup.retrofit2:retrofit:2.11.0") + implementation("com.squareup.retrofit2:converter-moshi:2.11.0") + implementation("com.squareup.okhttp3:logging-interceptor:4.12.0") + +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..ff59496 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle.kts. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d7fc4a9 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/ryanair/androidchallenge/core/App.kt b/app/src/main/java/com/ryanair/androidchallenge/core/App.kt new file mode 100644 index 0000000..076e10c --- /dev/null +++ b/app/src/main/java/com/ryanair/androidchallenge/core/App.kt @@ -0,0 +1,7 @@ +package com.ryanair.androidchallenge.core + +import android.app.Application +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class App : Application() \ No newline at end of file diff --git a/app/src/main/java/com/ryanair/androidchallenge/di/NetworkModule.kt b/app/src/main/java/com/ryanair/androidchallenge/di/NetworkModule.kt new file mode 100644 index 0000000..0e2fc4f --- /dev/null +++ b/app/src/main/java/com/ryanair/androidchallenge/di/NetworkModule.kt @@ -0,0 +1,57 @@ +package com.ryanair.androidchallenge.di + +import com.ryanair.androidchallenge.repository.airports.api.AirportService +import com.ryanair.androidchallenge.repository.airports.api.RoutesService +import com.ryanair.androidchallenge.repository.flights.api.FlightService +import com.squareup.moshi.Moshi +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +class NetworkModule { + + @Singleton + @Provides + fun provideMoshi() = Moshi.Builder().build() + + @Singleton + @Provides + fun provideHttpLoggingInterceptor() = HttpLoggingInterceptor().apply { + setLevel(HttpLoggingInterceptor.Level.BODY) + } + + @Singleton + @Provides + fun providesOkHttpClient(loggingInterceptor: HttpLoggingInterceptor) = + OkHttpClient.Builder().also { builder -> + builder.addInterceptor(loggingInterceptor) + }.build() + + @Singleton + @Provides + fun provideRetrofit(client: OkHttpClient, moshi: Moshi): Retrofit = Retrofit.Builder() + // TODO("add URL provided in the task instructions") + .addConverterFactory(MoshiConverterFactory.create(moshi)) + .client(client) + .build() + + @Singleton + @Provides + fun provideAirportService(retrofit: Retrofit): AirportService = retrofit.create(AirportService::class.java) + + @Singleton + @Provides + fun provideRoutesService(retrofit: Retrofit): RoutesService = retrofit.create(RoutesService::class.java) + + @Singleton + @Provides + fun provideFlightService(retrofit: Retrofit): FlightService = retrofit.create(FlightService::class.java) +} \ No newline at end of file diff --git a/app/src/main/java/com/ryanair/androidchallenge/repository/airports/api/AirportService.kt b/app/src/main/java/com/ryanair/androidchallenge/repository/airports/api/AirportService.kt new file mode 100644 index 0000000..de28d6f --- /dev/null +++ b/app/src/main/java/com/ryanair/androidchallenge/repository/airports/api/AirportService.kt @@ -0,0 +1,12 @@ +package com.ryanair.androidchallenge.repository.airports.api + +import com.ryanair.androidchallenge.repository.airports.model.AirportResponse +import retrofit2.http.GET +import retrofit2.http.Path + +interface AirportService { + + @GET("/views/locate/5/airports/{language}/active") + suspend fun getAirports(@Path("language") languageCode: String): List + +} \ No newline at end of file diff --git a/app/src/main/java/com/ryanair/androidchallenge/repository/airports/api/RoutesService.kt b/app/src/main/java/com/ryanair/androidchallenge/repository/airports/api/RoutesService.kt new file mode 100644 index 0000000..c5c497a --- /dev/null +++ b/app/src/main/java/com/ryanair/androidchallenge/repository/airports/api/RoutesService.kt @@ -0,0 +1,19 @@ +package com.ryanair.androidchallenge.repository.airports.api + +import com.ryanair.androidchallenge.repository.airports.model.RouteResponse +import retrofit2.http.GET +import retrofit2.http.Path + +interface RoutesService { + + @GET("/views/locate/5/routes/{language}") + suspend fun getAllRoutes( + @Path("language") languageCode: String, + ): List + + @GET("/views/locate/5/routes/{language}/airport/{departure}") + suspend fun getRoutes( + @Path("language") languageCode: String, + @Path("departure") departureAirportCode: String, + ): List +} \ No newline at end of file diff --git a/app/src/main/java/com/ryanair/androidchallenge/repository/airports/model/AirportResponse.kt b/app/src/main/java/com/ryanair/androidchallenge/repository/airports/model/AirportResponse.kt new file mode 100644 index 0000000..4c96339 --- /dev/null +++ b/app/src/main/java/com/ryanair/androidchallenge/repository/airports/model/AirportResponse.kt @@ -0,0 +1,51 @@ +package com.ryanair.androidchallenge.repository.airports.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class AirportResponse( + @Json(name = "code") val code: String?, + @Json(name = "name") val name: String?, + @Json(name = "seoName") val seoName: String?, + @Json(name = "base") val isBase: Boolean?, + @Json(name = "timeZone") val timeZone: String?, + @Json(name = "city") val city: City?, + @Json(name = "macCity") val macCity: MacCity?, + @Json(name = "region") val region: Region?, + @Json(name = "country") val country: Country?, + @Json(name = "coordinates") val coordinates: Coordinates? +) { + + @JsonClass(generateAdapter = true) + data class City( + @Json(name = "code") val code: String?, + @Json(name = "name") val name: String? + ) + + @JsonClass(generateAdapter = true) + data class MacCity( + @Json(name = "code") val code: String?, + @Json(name = "macCode") val macCode: String?, + @Json(name = "name") val name: String? + ) + + @JsonClass(generateAdapter = true) + data class Region( + @Json(name = "code") val code: String?, + @Json(name = "name") val name: String? + ) + + @JsonClass(generateAdapter = true) + data class Country( + @Json(name = "code") val code: String?, + @Json(name = "name") val name: String?, + @Json(name = "currency") val currencyCode: String? + ) + + @JsonClass(generateAdapter = true) + data class Coordinates( + @Json(name = "latitude") val latitude: Double?, + @Json(name = "longitude") val longitude: Double? + ) +} diff --git a/app/src/main/java/com/ryanair/androidchallenge/repository/airports/model/RouteResponse.kt b/app/src/main/java/com/ryanair/androidchallenge/repository/airports/model/RouteResponse.kt new file mode 100644 index 0000000..22c128f --- /dev/null +++ b/app/src/main/java/com/ryanair/androidchallenge/repository/airports/model/RouteResponse.kt @@ -0,0 +1,50 @@ +package com.ryanair.androidchallenge.repository.airports.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class RouteResponse( + @Json(name = "departureAirport") val departureAirport: Airport.Departure?, + @Json(name = "arrivalAirport") val arrivalAirport: Airport.Arrival?, + @Json(name = "connectingAirport") val connectingAirport: Airport.Connecting? +) { + + sealed interface Airport { + + @Json(name = "code") + val code: String? + + @Json(name = "name") + val name: String? + + @Json(name = "macCity") + val macCity: MacCity? + + @JsonClass(generateAdapter = true) + data class Departure( + override val code: String?, + override val name: String?, + override val macCity: MacCity?, + ) : Airport + + @JsonClass(generateAdapter = true) + data class Arrival( + override val code: String?, + override val name: String?, + override val macCity: MacCity? + ) : Airport + + @JsonClass(generateAdapter = true) + data class Connecting( + override val code: String?, + override val name: String?, + override val macCity: MacCity? + ) : Airport + + @JsonClass(generateAdapter = true) + data class MacCity( + @Json(name = "macCode") val macCode: String? + ) + } +} diff --git a/app/src/main/java/com/ryanair/androidchallenge/repository/flights/api/FlightService.kt b/app/src/main/java/com/ryanair/androidchallenge/repository/flights/api/FlightService.kt new file mode 100644 index 0000000..5c08faa --- /dev/null +++ b/app/src/main/java/com/ryanair/androidchallenge/repository/flights/api/FlightService.kt @@ -0,0 +1,26 @@ +package com.ryanair.androidchallenge.repository.flights.api + +import com.ryanair.androidchallenge.repository.flights.model.FlightResponse +import retrofit2.http.GET +import retrofit2.http.Headers +import retrofit2.http.Query + +interface FlightService { + + // example query: v4/en-gb/Availability?dateout=2025-09-17&origin=WRO&destination=DUB&adt=1&ToUs=AGREED + @Headers( + "client: android", + "client-version: 3.300.0" + ) + @GET("v4/en-gb/Availability") + suspend fun getFlights( + @Query("dateout") date: String, + @Query("origin") origin: String, + @Query("destination") destination: String, + @Query("adt") adult: Int, + @Query("teen") teen: Int, + @Query("chd") child: Int, + @Query("inf") inf: Int = 0, + @Query("ToUs") toUs: String = "AGREED" + ): FlightResponse +} \ No newline at end of file diff --git a/app/src/main/java/com/ryanair/androidchallenge/repository/flights/model/FlightResponse.kt b/app/src/main/java/com/ryanair/androidchallenge/repository/flights/model/FlightResponse.kt new file mode 100644 index 0000000..3dd4887 --- /dev/null +++ b/app/src/main/java/com/ryanair/androidchallenge/repository/flights/model/FlightResponse.kt @@ -0,0 +1,56 @@ +package com.ryanair.androidchallenge.repository.flights.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class FlightResponse( + @Json(name = "currency") val currency: String?, + @Json(name = "currPrecision") val currPrecision: Int?, + @Json(name = "trips") val trips: List? +) + +@JsonClass(generateAdapter = true) +data class Trip( + @Json(name = "dates") val dates: List?, + @Json(name = "origin") val origin: String?, + @Json(name = "destination") val destination: String? +) + +@JsonClass(generateAdapter = true) +data class TripDate( + @Json(name = "dateOut") val dateOut: String?, + @Json(name = "flights") val flights: List? +) + +@JsonClass(generateAdapter = true) +data class TripFlight( + @Json(name = "faresLeft") val faresLeft: Int?, + @Json(name = "regularFare") val regularFare: RegularFare?, + @Json(name = "flightNumber") val flightNumber: String?, + @Json(name = "time") val dateTimes: List?, + @Json(name = "duration") val duration: String?, + @Json(name = "segments") val segments: List?, + @Json(name = "operatedBy") val operatedBy: String?, +) + +@JsonClass(generateAdapter = true) +data class RegularFare( + @Json(name = "fares") val fares: List? +) + +@JsonClass(generateAdapter = true) +data class TripFare( + @Json(name = "type") val type: String?, + @Json(name = "amount") val amount: Double?, + @Json(name = "count") val count: Int? +) + +@JsonClass(generateAdapter = true) +data class Segment( + @Json(name = "origin") val origin: String?, + @Json(name = "destination") val destination: String?, + @Json(name = "flightNumber") val flightNumber: String?, + @Json(name = "time") val dateTimes: List?, + @Json(name = "duration") val duration: String? +) \ No newline at end of file diff --git a/app/src/main/java/com/ryanair/androidchallenge/ui/MainActivity.kt b/app/src/main/java/com/ryanair/androidchallenge/ui/MainActivity.kt new file mode 100644 index 0000000..476825c --- /dev/null +++ b/app/src/main/java/com/ryanair/androidchallenge/ui/MainActivity.kt @@ -0,0 +1,21 @@ +package com.ryanair.androidchallenge.ui + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import com.ryanair.androidchallenge.ui.search.SearchScreen +import com.ryanair.androidchallenge.ui.theme.AndroidChallengeTheme +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class MainActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + AndroidChallengeTheme { + SearchScreen() + } + } + } +} diff --git a/app/src/main/java/com/ryanair/androidchallenge/ui/search/SearchScreen.kt b/app/src/main/java/com/ryanair/androidchallenge/ui/search/SearchScreen.kt new file mode 100644 index 0000000..3427947 --- /dev/null +++ b/app/src/main/java/com/ryanair/androidchallenge/ui/search/SearchScreen.kt @@ -0,0 +1,118 @@ +package com.ryanair.androidchallenge.ui.search + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Search +import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import com.ryanair.androidchallenge.ui.theme.Theme + +@Composable +internal fun SearchScreen() { + Column( + modifier = Modifier + .padding(Theme.spacing.spacing16) + .fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceBetween + ) { + Column( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + ) { + Card( + modifier = Modifier + .fillMaxWidth() + .clickable { }, + border = BorderStroke(Theme.spacing.spacing1, Theme.colors.primary), + colors = CardDefaults.cardColors().copy(containerColor = Theme.colors.background) + ) { + Box(modifier = Modifier.padding(Theme.spacing.spacing8)) { + Column { + Text(text = "From") + + Text(text = "", maxLines = 3) + } + } + } + Spacer(modifier = Modifier.height(Theme.spacing.spacing16)) + Card( + modifier = Modifier + .fillMaxWidth() + .clickable { }, + border = BorderStroke(Theme.spacing.spacing1, Theme.colors.primary), + colors = CardDefaults.cardColors().copy(containerColor = Theme.colors.background) + ) { + Box(modifier = Modifier.padding(Theme.spacing.spacing8)) { + Column { + Text(text = "To") + + Text(text = "", maxLines = 3) + } + } + } + Spacer(modifier = Modifier.height(Theme.spacing.spacing16)) + Card( + modifier = Modifier + .fillMaxWidth() + .clickable { }, + border = BorderStroke(Theme.spacing.spacing1, Theme.colors.primary), + colors = CardDefaults.cardColors().copy(containerColor = Theme.colors.background) + ) { + Box(modifier = Modifier.padding(Theme.spacing.spacing8)) { + Column { + Text(text = "Departure date") + + Text(text = "", maxLines = 3) + } + } + } + Spacer(modifier = Modifier.height(Theme.spacing.spacing16)) + Card( + modifier = Modifier + .fillMaxWidth() + .clickable { }, + border = BorderStroke(Theme.spacing.spacing1, Theme.colors.primary), + colors = CardDefaults.cardColors().copy(containerColor = Theme.colors.background) + ) { + Box(modifier = Modifier.padding(Theme.spacing.spacing8)) { + Column { + Text(text = "Passengers") + + Text(text = "", maxLines = 3) + } + } + } + } + Button(modifier = Modifier + .fillMaxWidth(), + enabled = false, + onClick = { /*TODO*/ }) { + Row { + Icon( + imageVector = Icons.Rounded.Search, + contentDescription = "" + ) + Text(text = "Search") + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ryanair/androidchallenge/ui/theme/Color.kt b/app/src/main/java/com/ryanair/androidchallenge/ui/theme/Color.kt new file mode 100644 index 0000000..5443dfb --- /dev/null +++ b/app/src/main/java/com/ryanair/androidchallenge/ui/theme/Color.kt @@ -0,0 +1,23 @@ +package com.ryanair.androidchallenge.ui.theme + +import androidx.compose.runtime.Stable +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.graphics.Color + +object Palette { + + val Purple80 = Color(0xFFD0BCFF) + val Purple40 = Color(0xFF6650a4) + val White = Color(0xFFFFFFFF) + +} + +@Stable +data class ColorScheme( + val primary: Color = Palette.Purple80, + val primaryAccent: Color = Palette.Purple40, + val background: Color = Palette.White, +) + + +val LocalColorScheme = staticCompositionLocalOf { ColorScheme() } \ No newline at end of file diff --git a/app/src/main/java/com/ryanair/androidchallenge/ui/theme/Spacing.kt b/app/src/main/java/com/ryanair/androidchallenge/ui/theme/Spacing.kt new file mode 100644 index 0000000..cd9a8a3 --- /dev/null +++ b/app/src/main/java/com/ryanair/androidchallenge/ui/theme/Spacing.kt @@ -0,0 +1,37 @@ +package com.ryanair.androidchallenge.ui.theme + +import androidx.compose.runtime.Stable +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +@Stable +data class Spacing( + val spacing0: Dp = 0.dp, + val spacing1: Dp = 1.dp, + val spacing2: Dp = 2.dp, + val spacing4: Dp = 4.dp, + val spacing6: Dp = 6.dp, + val spacing8: Dp = 8.dp, + val spacing10: Dp = 10.dp, + val spacing12: Dp = 12.dp, + val spacing14: Dp = 14.dp, + val spacing16: Dp = 16.dp, + val spacing18: Dp = 18.dp, + val spacing20: Dp = 20.dp, + val spacing22: Dp = 22.dp, + val spacing24: Dp = 24.dp, + val spacing26: Dp = 26.dp, + val spacing28: Dp = 28.dp, + val spacing30: Dp = 30.dp, + val spacing32: Dp = 32.dp, + val spacing36: Dp = 36.dp, + val spacing38: Dp = 38.dp, + val spacing40: Dp = 40.dp, + val spacing42: Dp = 42.dp, + val spacing48: Dp = 48.dp, + val spacing56: Dp = 56.dp, + val spacing66: Dp = 66.dp, +) + +val LocalSpacing = staticCompositionLocalOf { Spacing() } diff --git a/app/src/main/java/com/ryanair/androidchallenge/ui/theme/Theme.kt b/app/src/main/java/com/ryanair/androidchallenge/ui/theme/Theme.kt new file mode 100644 index 0000000..5b3086c --- /dev/null +++ b/app/src/main/java/com/ryanair/androidchallenge/ui/theme/Theme.kt @@ -0,0 +1,38 @@ +package com.ryanair.androidchallenge.ui.theme + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.ReadOnlyComposable + + +@Composable +fun AndroidChallengeTheme( + colorScheme: ColorScheme = Theme.colors, + typography: Typography = Theme.typography, + spacing: Spacing = Theme.spacing, + content: @Composable () -> Unit +) { + CompositionLocalProvider( + LocalColorScheme provides colorScheme, + LocalTypography provides typography, + LocalSpacing provides spacing + ) { + content() + } +} + +object Theme { + val colors: ColorScheme + @Composable + @ReadOnlyComposable + get() = LocalColorScheme.current + val typography: Typography + @Composable + @ReadOnlyComposable + get() = LocalTypography.current + + val spacing: Spacing + @Composable + @ReadOnlyComposable + get() = LocalSpacing.current +} diff --git a/app/src/main/java/com/ryanair/androidchallenge/ui/theme/Typography.kt b/app/src/main/java/com/ryanair/androidchallenge/ui/theme/Typography.kt new file mode 100644 index 0000000..a5f009b --- /dev/null +++ b/app/src/main/java/com/ryanair/androidchallenge/ui/theme/Typography.kt @@ -0,0 +1,51 @@ +package com.ryanair.androidchallenge.ui.theme + +import androidx.compose.runtime.Stable +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + + +@Stable +data class Typography( + val titleSmall: TextStyle = TextStyle( + fontFamily = FontFamily.Default, + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + lineHeight = 20.sp, + ), + val titleLarge: TextStyle = TextStyle( + fontFamily = FontFamily.Default, + fontSize = 18.sp, + fontWeight = FontWeight.Bold, + lineHeight = 20.sp, + ), + val subtitleSmall: TextStyle = TextStyle( + fontFamily = FontFamily.Default, + fontSize = 12.sp, + fontWeight = FontWeight.Bold, + lineHeight = 16.sp, + ), + val subtitleLarge: TextStyle = TextStyle( + fontFamily = FontFamily.Default, + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + lineHeight = 20.sp, + ), + val bodySmall: TextStyle = TextStyle( + fontFamily = FontFamily.Default, + fontSize = 10.sp, + fontWeight = FontWeight.Normal, + lineHeight = 12.sp, + ), + val bodyLarge: TextStyle = TextStyle( + fontFamily = FontFamily.Default, + fontSize = 14.sp, + fontWeight = FontWeight.Normal, + lineHeight = 20.sp, + ) +) + +val LocalTypography = staticCompositionLocalOf { Typography() } \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..41ea3f9 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_flight_land.xml b/app/src/main/res/drawable/ic_baseline_flight_land.xml new file mode 100644 index 0000000..a47afda --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_flight_land.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_flight_takeoff.xml b/app/src/main/res/drawable/ic_baseline_flight_takeoff.xml new file mode 100644 index 0000000..e74cffd --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_flight_takeoff.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..b03f0f5 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..3a02f80 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,12 @@ + + + #1f60f5 + #0037c1 + #1A6F8DFF + @color/white + #f1c931 + #ba9900 + @color/black + #ff000000 + #ffffffff + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..4b0eea0 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,18 @@ + + + 0dp + 2dp + 4dp + 6dp + 8dp + 10dp + 12dp + 16dp + 18dp + 20dp + 26dp + 32dp + 36dp + 42dp + 64dp + \ No newline at end of file diff --git a/app/src/main/res/values/plurals.xml b/app/src/main/res/values/plurals.xml new file mode 100644 index 0000000..08f24d6 --- /dev/null +++ b/app/src/main/res/values/plurals.xml @@ -0,0 +1,19 @@ + + + + %d Adult + %d Adults + + + %d Teen + %d Teens + + + %d Child + %d Children + + + %d Stop + %d Stops + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..355a891 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,32 @@ + + AndroidChallenge + From + To + Departure date + Passengers + Search + You need to select departure airport first + Select departure date + Adult + Teen + Child + Cancel + Submit + Unable to connect to server + No internet connection + You need to fill all data first. + Unfortunately there is no flight on particular date.\nChoose different one. + Go back + Total price: + Adult: %d x %.2f %s + Teen: %d x %.2f %s + Child: %d x %.2f %s + Search airport + Find airport + Flights + Search origin airport + Search destination airport + Flight details + Price range: + There are no flights in selected price range + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..2e9a2d1 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,8 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + id("com.android.application") version ("8.8.0") apply false + id("org.jetbrains.kotlin.android") version ("2.1.20") apply false + id("org.jetbrains.kotlin.plugin.compose") version ("2.1.20") apply false + id("com.google.dagger.hilt.android") version ("2.56.2") apply false + id("com.google.devtools.ksp") version ("2.1.20-2.0.1") +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..cd0519b --- /dev/null +++ b/gradle.properties @@ -0,0 +1,23 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c 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..5c145cd --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri May 10 15:41:43 CEST 2024 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# 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. +# + +############################################################################## +## +## 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='"-Xmx64m" "-Xms64m"' + +# 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 or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; 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=`expr $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" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@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 Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@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="-Xmx64m" "-Xms64m" + +@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 execute + +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 execute + +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 + +: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 %* + +: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.kts b/settings.gradle.kts new file mode 100644 index 0000000..5b2965b --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,17 @@ +rootProject.name = "AndroidChallenge" + +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} +include(":app")