Initial commit

This commit is contained in:
2025-06-12 23:20:21 +02:00
parent 1656e706a0
commit 714cdb6795
122 changed files with 3335 additions and 916 deletions

57
.gitignore vendored
View File

@ -1,16 +1,43 @@
*.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
# built application files
*.apk
*.ap_
# files for the dex VM
*.dex
# Java class files
*.class
# generated files
bin/
gen/
out/
build/
# Local configuration file (sdk path, etc)
local.properties
# Eclipse project files
.classpath
.project
# Windows thumbnail db
.DS_Store
# IDEA/Android Studio project files, because
# the project can be imported from settings.gradle.kts
*.iml
.idea/*
!.idea/copyright
# Keep the code styles.
!/.idea/codeStyles
/.idea/codeStyles/*
!/.idea/codeStyles/Project.xml
!/.idea/codeStyles/codeStyleConfig.xml
# Gradle cache
.gradle
# app signing
*.jks
/keystore.properties

1
app/.gitignore vendored
View File

@ -1 +0,0 @@
/build

View File

@ -1,76 +1,71 @@
import java.io.FileInputStream
import java.util.Properties
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")
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.flights.android.application.compose)
alias(libs.plugins.flights.android.application.hilt)
}
android {
namespace = "com.ryanair.androidchallenge"
compileSdk = 35
namespace = "dev.adriankuta.flights"
defaultConfig {
applicationId = "com.ryanair.androidchallenge"
versionCode = 1
versionName = "1.0"
multiDexEnabled = true
minSdk = 28
targetSdk = 35
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
applicationId = "dev.adriankuta.flights"
versionCode = 10
versionName = "0.0.1-${versionCode}"
//signingConfig = signingConfigs.getByName("debug")
}
/*val keystorePropertiesFile = rootProject.file("keystore.properties")
val keystoreProperties = Properties()
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
signingConfigs {
create("release") {
keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["keyPassword"] as String
storeFile = file(keystoreProperties["storeFile"] as String)
storePassword = keystoreProperties["storePassword"] as String
}
}*/
buildTypes {
getByName("debug") {
debug {
signingConfig = signingConfigs.getByName("debug")
isDebuggable = true
}
release {
//signingConfig = signingConfigs.getByName("release")
isDebuggable = true
isMinifyEnabled = false
isShrinkResources = false
}
getByName("release") {
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
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
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
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(projects.model.repository)
implementation(projects.model.data.room)
implementation(projects.model.data.simple)
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")
implementation(projects.ui.designsystem)
implementation(projects.ui.home)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.core.splashscreen)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.app.update.ktx)
}

View File

@ -0,0 +1,33 @@
# Exceptions for compose. See https://detekt.dev/docs/introduction/compose
naming:
FunctionNaming:
functionPattern: '[a-zA-Z][a-zA-Z0-9]*'
TopLevelPropertyNaming:
constantPattern: '[A-Z][A-Za-z0-9]*'
complexity:
LongParameterList:
ignoreAnnotated: [ 'Composable' ]
TooManyFunctions:
ignoreAnnotatedFunctions: [ 'Preview' ]
style:
MagicNumber:
ignorePropertyDeclaration: true
ignoreCompanionObjectPropertyDeclaration: true
ignoreAnnotated: [ 'Composable' ]
UnusedPrivateMember:
ignoreAnnotated: [ 'Composable' ]
# Deviations from defaults
formatting:
TrailingCommaOnCallSite:
active: true
autoCorrect: true
useTrailingCommaOnCallSite: true
TrailingCommaOnDeclarationSite:
active: true
autoCorrect: true
useTrailingCommaOnDeclarationSite: true

205
app/lint-baseline.xml Normal file
View File

@ -0,0 +1,205 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.10.1" type="baseline" client="gradle" dependencies="false" name="AGP (8.10.1)" variant="all" version="8.10.1">
<issue
id="UnusedAttribute"
message="Attribute `endX` is only used in API level 24 and higher (current min is 21)"
errorLine1=" android:endX=&quot;85.84757&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/drawable/ic_launcher_foreground.xml"
line="10"
column="17"/>
</issue>
<issue
id="UnusedAttribute"
message="Attribute `endY` is only used in API level 24 and higher (current min is 21)"
errorLine1=" android:endY=&quot;92.4963&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/drawable/ic_launcher_foreground.xml"
line="11"
column="17"/>
</issue>
<issue
id="UnusedAttribute"
message="Attribute `startX` is only used in API level 24 and higher (current min is 21)"
errorLine1=" android:startX=&quot;42.9492&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/drawable/ic_launcher_foreground.xml"
line="12"
column="17"/>
</issue>
<issue
id="UnusedAttribute"
message="Attribute `startY` is only used in API level 24 and higher (current min is 21)"
errorLine1=" android:startY=&quot;49.59793&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/drawable/ic_launcher_foreground.xml"
line="13"
column="17"/>
</issue>
<issue
id="UnusedAttribute"
message="Attribute `offset` is only used in API level 24 and higher (current min is 21)"
errorLine1=" android:offset=&quot;0.0&quot; />"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/drawable/ic_launcher_foreground.xml"
line="17"
column="21"/>
</issue>
<issue
id="UnusedAttribute"
message="Attribute `offset` is only used in API level 24 and higher (current min is 21)"
errorLine1=" android:offset=&quot;1.0&quot; />"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/drawable/ic_launcher_foreground.xml"
line="20"
column="21"/>
</issue>
<issue
id="UnusedAttribute"
message="Attribute `fillType` is only used in API level 24 and higher (current min is 21)"
errorLine1=" android:fillType=&quot;nonZero&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/drawable/ic_launcher_foreground.xml"
line="26"
column="9"/>
</issue>
<issue
id="Aligned16KB"
message="The native library `arm64-v8a/libmockkjvmtiagent.so` (from `io.mockk:mockk-agent-android:1.14.2`) is not 16 KB aligned">
<location
file="$GRADLE_USER_HOME/caches/8.11.1/transforms/9ee3fe20033b4dd897c7dfcf7c303d16/transformed/mockk-agent-android-1.14.2/jni/arm64-v8a/libmockkjvmtiagent.so"/>
</issue>
<issue
id="Aligned16KB"
message="The native library `arm64-v8a/libmockkjvmtiagent.so` (from `io.mockk:mockk-agent-android:1.14.2`) is not 16 KB aligned">
<location
file="$GRADLE_USER_HOME/caches/8.11.1/transforms/9ee3fe20033b4dd897c7dfcf7c303d16/transformed/mockk-agent-android-1.14.2/jni/arm64-v8a/libmockkjvmtiagent.so"/>
</issue>
<issue
id="UnusedResources"
message="The resource `R.color.purple_200` appears to be unused"
errorLine1=" &lt;color name=&quot;purple_200&quot;>#FFBB86FC&lt;/color>"
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/colors.xml"
line="3"
column="12"/>
</issue>
<issue
id="UnusedResources"
message="The resource `R.color.purple_500` appears to be unused"
errorLine1=" &lt;color name=&quot;purple_500&quot;>#FF6200EE&lt;/color>"
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/colors.xml"
line="4"
column="12"/>
</issue>
<issue
id="UnusedResources"
message="The resource `R.color.purple_700` appears to be unused"
errorLine1=" &lt;color name=&quot;purple_700&quot;>#FF3700B3&lt;/color>"
errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/colors.xml"
line="5"
column="12"/>
</issue>
<issue
id="UnusedResources"
message="The resource `R.color.teal_200` appears to be unused"
errorLine1=" &lt;color name=&quot;teal_200&quot;>#FF03DAC5&lt;/color>"
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/colors.xml"
line="6"
column="12"/>
</issue>
<issue
id="UnusedResources"
message="The resource `R.color.teal_700` appears to be unused"
errorLine1=" &lt;color name=&quot;teal_700&quot;>#FF018786&lt;/color>"
errorLine2=" ~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/colors.xml"
line="7"
column="12"/>
</issue>
<issue
id="UnusedResources"
message="The resource `R.color.black` appears to be unused"
errorLine1=" &lt;color name=&quot;black&quot;>#FF000000&lt;/color>"
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/res/values/colors.xml"
line="8"
column="12"/>
</issue>
<issue
id="UnusedResources"
message="The resource `R.color.white` appears to be unused"
errorLine1=" &lt;color name=&quot;white&quot;>#FFFFFFFF&lt;/color>"
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/res/values/colors.xml"
line="9"
column="12"/>
</issue>
<issue
id="UnusedResources"
message="The resource `R.drawable.ic_launcher_foreground` appears to be unused"
errorLine1="&lt;vector xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;"
errorLine2="^">
<location
file="src/main/res/drawable/ic_launcher_foreground.xml"
line="1"
column="1"/>
</issue>
<issue
id="MonochromeLauncherIcon"
message="The application adaptive icon is missing a monochrome tag"
errorLine1="&lt;adaptive-icon xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;>"
errorLine2="^">
<location
file="src/main/res/mipmap-anydpi-v26/ic_launcher.xml"
line="2"
column="1"/>
</issue>
<issue
id="MonochromeLauncherIcon"
message="The application adaptive roundIcon is missing a monochrome tag"
errorLine1="&lt;adaptive-icon xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;>"
errorLine2="^">
<location
file="src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml"
line="2"
column="1"/>
</issue>
</issues>

View File

@ -1,6 +1,6 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.kts.
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
@ -19,3 +19,6 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-keep class dev.adriankuta.flights.data.dto.* { *; }
-keep class dev.adriankuta.flights.data.model.* { *; }

View File

@ -0,0 +1,15 @@
package dev.adriankuta.flights
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
import timber.log.Timber
import timber.log.Timber.DebugTree
@HiltAndroidApp
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
Timber.plant(DebugTree())
}
}

View File

@ -1,21 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ryanair.androidchallenge">
xmlns:tools="http://schemas.android.com/tools">
<application
android:name=".core.App"
android:name=".MyApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:enableOnBackInvokedCallback="true"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat.DayNight">
android:theme="@style/Theme.Flights"
tools:targetApi="33">
<activity
android:name=".ui.MainActivity"
android:exported="true">
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.Flights">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,28 @@
Adam Małysz
Kubuś Puchatek
Prosiaczek
Harry Potter
SpongeBob
Miś Uszatek
Patryk (SpongeBob)
Pan krab (SpongeBob)
Brzydkie kaczątko
Alvin i Wiewiórki
Szymon (Alvin i wiewiórki)
Papa Smerf
Smerfetka
Gargamel (smerfy)
Ważniak (smerfy)
Roszpunka
Myszka Miki
Król Lew
Stitch
Elsa (kraina lodu)
Anna (kraina lodu)
Olaf (kraina lodu)
Mała syrenka
Tygrys (Kubuś Puchatek)
Kłapouchy (Kubuś Puchatek)
Maleństwo (Kubuś Puchatek)
Królik (Kubuś Puchatek)
Krzyś (Kubuś Puchatek)

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 KiB

View File

@ -1,57 +0,0 @@
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)
}

View File

@ -1,12 +0,0 @@
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<AirportResponse>
}

View File

@ -1,19 +0,0 @@
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<RouteResponse>
@GET("/views/locate/5/routes/{language}/airport/{departure}")
suspend fun getRoutes(
@Path("language") languageCode: String,
@Path("departure") departureAirportCode: String,
): List<RouteResponse>
}

View File

@ -1,51 +0,0 @@
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?
)
}

View File

@ -1,50 +0,0 @@
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?
)
}
}

View File

@ -1,26 +0,0 @@
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
}

View File

@ -1,56 +0,0 @@
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<Trip>?
)
@JsonClass(generateAdapter = true)
data class Trip(
@Json(name = "dates") val dates: List<TripDate>?,
@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<TripFlight>?
)
@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<String>?,
@Json(name = "duration") val duration: String?,
@Json(name = "segments") val segments: List<Segment>?,
@Json(name = "operatedBy") val operatedBy: String?,
)
@JsonClass(generateAdapter = true)
data class RegularFare(
@Json(name = "fares") val fares: List<TripFare>?
)
@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<String>?,
@Json(name = "duration") val duration: String?
)

View File

@ -1,21 +0,0 @@
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()
}
}
}
}

View File

@ -1,118 +0,0 @@
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")
}
}
}
}

View File

@ -1,23 +0,0 @@
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() }

View File

@ -1,37 +0,0 @@
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() }

View File

@ -1,38 +0,0 @@
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
}

View File

@ -1,51 +0,0 @@
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() }

View File

@ -0,0 +1,23 @@
package dev.adriankuta.flights
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.rememberNavController
import dev.adriankuta.flights.ui.home.navigation.HomeRoute
import dev.adriankuta.flights.ui.home.navigation.homeScreen
@Composable
fun FlightsNavGraph(
modifier: Modifier = Modifier,
navController: NavHostController = rememberNavController(),
) {
NavHost(
navController = navController,
startDestination = HomeRoute,
modifier = modifier,
) {
homeScreen()
}
}

View File

@ -0,0 +1,85 @@
package dev.adriankuta.flights
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import dev.adriankuta.flights.ui.FlightsApp
import dev.adriankuta.flights.ui.designsystem.theme.FlightsTheme
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val splashScreen = installSplashScreen()
super.onCreate(savedInstanceState)
var isLoading: Boolean by mutableStateOf(true)
lifecycleScope.launch {
@Suppress("MagicNumber")
delay(500)
isLoading = false
}
splashScreen.setKeepOnScreenCondition {
isLoading
}
enableEdgeToEdge()
setContent {
val darkTheme = shouldUseDarkTheme()
// Update the edge to edge configuration to match the theme
// This is the same parameters as the default enableEdgeToEdge call, but we manually
// resolve whether or not to show dark theme using uiState, since it can be different
// than the configuration's dark theme value based on the user preference.
DisposableEffect(darkTheme) {
enableEdgeToEdge(
statusBarStyle = SystemBarStyle.auto(
android.graphics.Color.TRANSPARENT,
android.graphics.Color.TRANSPARENT,
) { darkTheme },
navigationBarStyle = SystemBarStyle.auto(
lightScrim,
darkScrim,
) { darkTheme },
)
onDispose {}
}
FlightsTheme {
FlightsApp()
}
}
}
}
/**
* Returns `true` if dark theme should be used, as a function of the [uiState] and the
* current system context.
*/
@Composable
private fun shouldUseDarkTheme(): Boolean = isSystemInDarkTheme()
/**
* The default light scrim, as defined by androidx and the platform:
* https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:activity/activity/src/main/java/androidx/activity/EdgeToEdge.kt;l=35-38;drc=27e7d52e8604a080133e8b842db10c89b4482598
*/
private val lightScrim = android.graphics.Color.argb(0xe6, 0xFF, 0xFF, 0xFF)
/**
* The default dark scrim, as defined by androidx and the platform:
* https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:activity/activity/src/main/java/androidx/activity/EdgeToEdge.kt;l=40-44;drc=27e7d52e8604a080133e8b842db10c89b4482598
*/
private val darkScrim = android.graphics.Color.argb(0x80, 0x1b, 0x1b, 0x1b)

View File

@ -0,0 +1,25 @@
package dev.adriankuta.flights.ui
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import dev.adriankuta.flights.FlightsNavGraph
import dev.adriankuta.flights.ui.designsystem.theme.Elevation
@Composable
fun FlightsApp(
modifier: Modifier = Modifier,
) {
Surface(
tonalElevation = Elevation.Surface,
modifier = modifier,
) {
Scaffold(
snackbarHost = { InAppUpdates() },
) { paddingValues ->
FlightsNavGraph(Modifier.padding(paddingValues))
}
}
}

View File

@ -0,0 +1,93 @@
package dev.adriankuta.flights.ui
import androidx.activity.compose.LocalActivity
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import com.google.android.play.core.appupdate.AppUpdateInfo
import com.google.android.play.core.appupdate.AppUpdateManager
import com.google.android.play.core.appupdate.AppUpdateManagerFactory
import com.google.android.play.core.appupdate.AppUpdateOptions
import com.google.android.play.core.install.InstallException
import com.google.android.play.core.install.InstallStateUpdatedListener
import com.google.android.play.core.install.model.AppUpdateType
import com.google.android.play.core.install.model.InstallStatus
import com.google.android.play.core.install.model.UpdateAvailability
import kotlinx.coroutines.launch
import timber.log.Timber
import kotlin.coroutines.suspendCoroutine
@Composable
fun InAppUpdates(
modifier: Modifier = Modifier,
@AppUpdateType updateType: Int = AppUpdateType.FLEXIBLE,
) {
val activity = LocalActivity.current ?: return
val scope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() }
val appUpdateManager = remember { AppUpdateManagerFactory.create(requireNotNull(activity)) }
val flexibleUpdateListener = remember {
InstallStateUpdatedListener { state ->
if (state.installStatus() == InstallStatus.DOWNLOADED) {
// After the update is downloaded, show a notification
// and request user confirmation to restart the app.
scope.launch {
val result = snackbarHostState.showSnackbar(
message = "An update has just been downloaded",
actionLabel = "Reload",
)
when (result) {
SnackbarResult.Dismissed -> Unit
SnackbarResult.ActionPerformed -> {
appUpdateManager.completeUpdate()
}
}
}
}
}
}
LaunchedEffect(Unit) {
try {
val appUpdateInfo = appUpdateManager.checkUpdateInfo()
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE &&
appUpdateInfo.isUpdateTypeAllowed(updateType)
) {
appUpdateManager.startUpdateFlow(
appUpdateInfo,
activity,
AppUpdateOptions.newBuilder(updateType).build(),
)
}
} catch (e: InstallException) {
Timber.w(e)
}
}
DisposableEffect(appUpdateManager) {
appUpdateManager.registerListener(flexibleUpdateListener)
onDispose {
appUpdateManager.unregisterListener(flexibleUpdateListener)
}
}
SnackbarHost(
hostState = snackbarHostState,
modifier = modifier,
)
}
private suspend fun AppUpdateManager.checkUpdateInfo() =
suspendCoroutine<AppUpdateInfo> { continuation ->
appUpdateInfo.addOnSuccessListener {
continuation.resumeWith(Result.success(it))
}.addOnFailureListener {
continuation.resumeWith(Result.failure(it))
}
}

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?android:textColorPrimary">
<path
android:fillColor="@android:color/white"
android:pathData="M2.5,19h19v2h-19V19zM19.34,15.85c0.8,0.21 1.62,-0.26 1.84,-1.06c0.21,-0.8 -0.26,-1.62 -1.06,-1.84l-5.31,-1.42l-2.76,-9.02L10.12,2v8.28L5.15,8.95L4.22,6.63L2.77,6.24v5.17L19.34,15.85z" />
</vector>

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?android:textColorPrimary">
<path
android:fillColor="@android:color/white"
android:pathData="M2.5,19h19v2h-19V19zM22.07,9.64c-0.21,-0.8 -1.04,-1.28 -1.84,-1.06L14.92,10l-6.9,-6.43L6.09,4.08l4.14,7.17l-4.97,1.33l-1.97,-1.54l-1.45,0.39l2.59,4.49c0,0 7.12,-1.9 16.57,-4.43C21.81,11.26 22.28,10.44 22.07,9.64z" />
</vector>

View File

@ -1,170 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z"/>
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
</vector>

View File

@ -25,6 +25,6 @@
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 B

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View File

@ -1,12 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#1f60f5</color>
<color name="colorPrimaryVariant">#0037c1</color>
<color name="colorPrimaryVariant3">#1A6F8DFF</color>
<color name="colorOnPrimary">@color/white</color>
<color name="colorSecondary">#f1c931</color>
<color name="colorSecondaryVariant">#ba9900</color>
<color name="colorOnSecondary">@color/black</color>
<color name="black">#ff000000</color>
<color name="white">#ffffffff</color>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="match_constraints">0dp</dimen>
<dimen name="size_tinyish">2dp</dimen>
<dimen name="size_tiny">4dp</dimen>
<dimen name="size_tiny_plus">6dp</dimen>
<dimen name="size_small">8dp</dimen>
<dimen name="size_small_plus">10dp</dimen>
<dimen name="size_medium">12dp</dimen>
<dimen name="size_general">16dp</dimen>
<dimen name="size_general_plus">18dp</dimen>
<dimen name="size_extra_large">20dp</dimen>
<dimen name="size_extra_large_plus">26dp</dimen>
<dimen name="size_general_double">32dp</dimen>
<dimen name="size_general_plus_double">36dp</dimen>
<dimen name="size_double_extra_large">42dp</dimen>
<dimen name="size_general_double_double">64dp</dimen>
</resources>

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<plurals name="adult">
<item quantity="one">%d Adult</item>
<item quantity="other">%d Adults</item>
</plurals>
<plurals name="teen">
<item quantity="one">%d Teen</item>
<item quantity="other">%d Teens</item>
</plurals>
<plurals name="child">
<item quantity="one">%d Child</item>
<item quantity="other">%d Children</item>
</plurals>
<plurals name="stop">
<item quantity="one">%d Stop</item>
<item quantity="other">%d Stops</item>
</plurals>
</resources>

View File

@ -1,32 +1,3 @@
<resources>
<string name="app_name">AndroidChallenge</string>
<string name="from">From</string>
<string name="to">To</string>
<string name="departure_date">Departure date</string>
<string name="passengers">Passengers</string>
<string name="search">Search</string>
<string name="select_departure_airport_first">You need to select departure airport first</string>
<string name="select_departure_date">Select departure date</string>
<string name="adult">Adult</string>
<string name="teen">Teen</string>
<string name="child">Child</string>
<string name="cancel">Cancel</string>
<string name="submit">Submit</string>
<string name="server_error">Unable to connect to server</string>
<string name="no_internet_connection">No internet connection</string>
<string name="fill_all_data_first">You need to fill all data first.</string>
<string name="flight_not_found">Unfortunately there is no flight on particular date.\nChoose different one.</string>
<string name="go_back">Go back</string>
<string name="total_price">Total price:</string>
<string name="price_adult">Adult: %d x %.2f %s</string>
<string name="price_teen">Teen: %d x %.2f %s</string>
<string name="price_child">Child: %d x %.2f %s</string>
<string name="search_airport">Search airport</string>
<string name="find_airport">Find airport</string>
<string name="flights">Flights</string>
<string name="search_origin_airport">Search origin airport</string>
<string name="search_destination_airport">Search destination airport</string>
<string name="flight_details">Flight details</string>
<string name="price_range">Price range:</string>
<string name="no_filter_results">There are no flights in selected price range</string>
<string name="app_name" translatable="false">Flights</string>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Flights" parent="android:Theme.Material.Light.NoActionBar" />
</resources>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older that API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>

View File

@ -1,7 +1,7 @@
package com.ryanair.androidchallenge.core
package dev.adriankuta.flights
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class App : Application()
class MyApplication : Application() {}

View File

@ -0,0 +1,75 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21
/*
* Copyright 2022 The Android Open Source Project
*
* 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.
*/
plugins {
`kotlin-dsl`
}
group = "dev.adriankuta.flights.buildlogic"
java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
kotlin {
compilerOptions {
jvmTarget = JVM_21
}
}
dependencies {
compileOnly(libs.android.gradlePlugin)
compileOnly(libs.compose.gradlePlugin)
compileOnly(libs.detekt.gradlePlugin)
compileOnly(libs.kotlin.gradlePlugin)
compileOnly(libs.ksp.gradlePlugin)
}
gradlePlugin {
/**
* Register convention plugins so they are available in the build scripts of the application
*/
plugins {
register("androidLibrary") {
id = "flights.android.library"
implementationClass = "AndroidLibraryConvention"
}
register("androidLibraryCompose") {
id = "flights.android.library.compose"
implementationClass = "AndroidLibraryComposeConvention"
}
register("androidApplication") {
id = "flights.android.application"
implementationClass = "AndroidApplicationConvention"
}
register("androidApplicationCompose") {
id = "flights.android.application.compose"
implementationClass = "AndroidApplicationComposeConvention"
}
register("androidApplicationHilt") {
id = "flights.android.application.hilt"
implementationClass = "AndroidApplicationHiltConvention"
}
register("androidLibraryHilt") {
id = "flights.android.library.hilt"
implementationClass = "AndroidLibraryHiltConvention"
}
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2022 The Android Open Source Project
*
* 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.
*/
import com.android.build.api.dsl.ApplicationExtension
import dev.adriankuta.flights.configureCompose
import dev.adriankuta.flights.configureDetektForComposeModuleExceptions
import dev.adriankuta.flights.configureInstrumentation
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.getByType
@Suppress("unused") // This is called as a string in the gradle plugin block
class AndroidApplicationComposeConvention : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
pluginManager.apply("flights.android.application")
val extension = extensions.getByType<ApplicationExtension>()
extensions.configure<ApplicationExtension> {
defaultConfig {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
}
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
configureInstrumentation()
configureCompose(extension)
configureDetektForComposeModuleExceptions()
dependencies {
"androidTestImplementation"(libs.findLibrary("androidx.test.core").get())
}
}
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright 2022 The Android Open Source Project
*
* 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.
*/
import com.android.build.api.dsl.ApplicationExtension
import dev.adriankuta.flights.configureAndroidLint
import dev.adriankuta.flights.configureDetektDependencies
import dev.adriankuta.flights.configureFlavors
import dev.adriankuta.flights.configureKotlinAndroid
import dev.adriankuta.flights.configureUnitTests
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.getByType
@Suppress("unused") // This is called as a string in the gradle plugin block
class AndroidApplicationConvention : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply("com.android.application")
apply("io.gitlab.arturbosch.detekt")
apply("org.jetbrains.kotlin.android")
}
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
// android block
extensions.configure<ApplicationExtension> {
configureKotlinAndroid(this)
defaultConfig.targetSdk = libs.findVersion("targetSdk").get().toString().toInt()
defaultConfig.vectorDrawables {
useSupportLibrary = true
}
buildTypes {
debug {
testCoverage {
enableUnitTestCoverage =
false // Test coverage spanks memory on the CI with ye older AGP
enableAndroidTestCoverage = false
}
applicationIdSuffix = ".debug"
}
}
configureFlavors(this)
configureAndroidLint(this)
// configureGradleManagedDevices(this)
}
// val extension = extensions.getByType<ApplicationAndroidComponentsExtension>()
// configureJacoco(extension)
configureDetektDependencies()
//configureSonar()
configureUnitTests()
}
}
}

View File

@ -0,0 +1,22 @@
import com.android.build.api.dsl.ApplicationExtension
import dev.adriankuta.flights.configureHilt
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
@Suppress("unused") // This is called as a string in the gradle plugin block
internal class AndroidApplicationHiltConvention : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply("com.google.devtools.ksp")
apply("dagger.hilt.android.plugin")
}
// android block
extensions.configure<ApplicationExtension> {
configureHilt()
}
}
}
}

View File

@ -0,0 +1,12 @@
import dev.adriankuta.flights.configureDetektForComposeModuleExceptions
import org.gradle.api.Plugin
import org.gradle.api.Project
@Suppress("unused") // This is called as a string in the gradle plugin block
internal class AndroidDetektComposeConvention : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
configureDetektForComposeModuleExceptions()
}
}
}

View File

@ -0,0 +1,13 @@
import dev.adriankuta.flights.configureDetektForNonUiModule
import org.gradle.api.Plugin
import org.gradle.api.Project
@Suppress("unused") // This is called as a string in the gradle plugin block
internal class AndroidDetektConvention : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
configureDetektForNonUiModule()
}
}
}

View File

@ -0,0 +1,30 @@
import com.android.build.gradle.LibraryExtension
import dev.adriankuta.flights.configureCompose
import dev.adriankuta.flights.configureDetektForComposeModuleExceptions
import dev.adriankuta.flights.configureInstrumentation
import dev.adriankuta.flights.configureLibrary
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.getByType
@Suppress("unused") // This is called as a string in the gradle plugin block
internal class AndroidLibraryComposeConvention : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
configureLibrary()
extensions.configure<LibraryExtension> {
defaultConfig {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
}
val extension = extensions.getByType<LibraryExtension>()
configureCompose(extension)
configureInstrumentation()
configureDetektForComposeModuleExceptions()
}
}
}

View File

@ -0,0 +1,12 @@
import dev.adriankuta.flights.configureDetektForNonUiModule
import dev.adriankuta.flights.configureLibrary
import org.gradle.api.Plugin
import org.gradle.api.Project
@Suppress("unused") // This is called as a string in the gradle plugin block
internal class AndroidLibraryConvention : Plugin<Project> {
override fun apply(target: Project) {
target.configureLibrary()
target.configureDetektForNonUiModule()
}
}

View File

@ -0,0 +1,22 @@
import com.android.build.gradle.LibraryExtension
import dev.adriankuta.flights.configureHilt
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
@Suppress("unused") // This is called as a string in the gradle plugin block
internal class AndroidLibraryHiltConvention : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply("com.google.devtools.ksp")
apply("dagger.hilt.android.plugin")
}
// android block
extensions.configure<LibraryExtension> {
configureHilt()
}
}
}
}

View File

@ -0,0 +1,29 @@
package dev.adriankuta.flights
import com.android.build.api.dsl.CommonExtension
import org.gradle.api.Project
internal fun Project.configureAndroidLint(
commonExtension: CommonExtension<*, *, *, *, *, *>,
) {
commonExtension.apply {
lint {
baseline = file("lint-baseline.xml")
warningsAsErrors = true
disable += "AndroidGradlePluginVersion"
warning += "LintBaseline" // Still have report remind us of baseline issues
disable += "GradleDependency" // We want to mange dependency updates in own PRs.
disable += "MissingTranslation" // Translations are apart from our normal process at the moment
// region - Bug in lint/AGP apparently
// TODO Maybe it is, maybe it isnt. Its a transitory, hard to replicate bug. Ideally
// we want these lint rules applied.
disable += "StateFlowValueCalledInComposition"
disable += "FlowOperatorInvokedInComposition"
disable += "WrongNavigateRouteType"
disable += "WrongStartDestinationType"
disable += "UnknownIssueId"
// endregion
}
}
}

View File

@ -0,0 +1,50 @@
package dev.adriankuta.flights
import com.android.build.api.dsl.CommonExtension
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.kotlin.dsl.assign
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.getByType
import org.jetbrains.kotlin.compose.compiler.gradle.ComposeCompilerGradlePluginExtension
internal fun Project.configureCompose(
commonExtension: CommonExtension<*, *, *, *, *, *>,
) {
with(pluginManager) {
apply("org.jetbrains.kotlin.plugin.compose")
}
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
commonExtension.apply {
buildFeatures {
compose = true
}
}
extensions.configure<ComposeCompilerGradlePluginExtension> {
enableStrongSkippingMode = true
includeSourceInformation = true
}
dependencies {
val bom = libs.findLibrary("androidx-compose-bom").get()
"androidTestImplementation"(platform(bom))
"androidTestImplementation"(libs.findLibrary("androidx.compose.ui.test.junit").get())
"debugImplementation"(platform(bom))
"debugImplementation"(libs.findLibrary("androidx.compose.ui.tooling").get())
//"debugImplementation"(libs.findLibrary("androidx.compose.ui.testManifest").get())
"implementation"(platform(bom))
"implementation"(libs.findLibrary("androidx.compose.material3").get())
"implementation"(libs.findLibrary("androidx.compose.ui").get())
"implementation"(libs.findLibrary("androidx.compose.ui.graphics").get())
"implementation"(libs.findLibrary("androidx.compose.ui.tooling.preview").get())
"implementation"(libs.findLibrary("androidx.lifecycle.runtime.compose").get())
"implementation"(libs.findLibrary("androidx.navigation.compose").get())
"implementation"(libs.findLibrary("kotlinx.collections.immutable").get())
}
}

View File

@ -0,0 +1,83 @@
package dev.adriankuta.flights
import io.gitlab.arturbosch.detekt.extensions.DetektExtension
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.getByType
import java.io.FileWriter
internal fun Project.configureDetektDependencies() {
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
dependencies {
"detektPlugins"(libs.findLibrary("detekt.ktlint").get())
"detektPlugins"(libs.findLibrary("detekt.compose").get())
}
}
internal fun Project.configureDetektForNonUiModule() =
createDetektConfigFile(NonDefault.trimIndent())
internal fun Project.configureDetektForComposeModuleExceptions() =
createDetektConfigFile(ComposeExceptions.trimIndent())
internal fun Project.createDetektConfigFile(deviationsFromDefaultConfig: String) {
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
if (!file(DetektConfigPath).exists()) {
val detektConfigFile = project.file(DetektConfigPath)
detektConfigFile.parentFile.mkdirs()
detektConfigFile.createNewFile()
val writer = FileWriter(detektConfigFile)
writer.write(deviationsFromDefaultConfig)
writer.close()
}
configure<DetektExtension> {
toolVersion = libs.findVersion("detekt").get().toString()
config.setFrom(files(DetektConfigPath))
buildUponDefaultConfig = true
}
}
private const val DetektConfigPath = "config/detekt/detekt.yml"
private val NonDefault = """
# Deviations from defaults
formatting:
TrailingCommaOnCallSite:
active: true
autoCorrect: true
useTrailingCommaOnCallSite: true
TrailingCommaOnDeclarationSite:
active: true
autoCorrect: true
useTrailingCommaOnDeclarationSite: true
"""
private val ComposeExceptions = """
# Exceptions for compose. See https://detekt.dev/docs/introduction/compose
naming:
FunctionNaming:
functionPattern: '[a-zA-Z][a-zA-Z0-9]*'
TopLevelPropertyNaming:
constantPattern: '[A-Z][A-Za-z0-9]*'
complexity:
LongParameterList:
ignoreAnnotated: ['Composable']
TooManyFunctions:
ignoreAnnotatedFunctions: ['Preview']
style:
MagicNumber:
ignorePropertyDeclaration: true
ignoreCompanionObjectPropertyDeclaration: true
ignoreAnnotated: ['Composable']
UnusedPrivateMember:
ignoreAnnotated: ['Composable']
""" + NonDefault

View File

@ -0,0 +1,43 @@
package dev.adriankuta.flights
import com.android.build.api.dsl.ApplicationExtension
import com.android.build.api.dsl.ApplicationProductFlavor
import com.android.build.api.dsl.CommonExtension
import org.gradle.api.Project
@Suppress("EnumEntryName")
enum class FlavorDimension {
cost
}
// The content for the app can either come from local static data which is useful for demo
// purposes, or from a production backend server which supplies up-to-date, real content.
// These two product flavors reflect this behaviour.
@Suppress("EnumEntryName")
enum class FlightsFlavor(
val dimension: FlavorDimension,
val applicationIdSuffix: String? = null,
) {
free(FlavorDimension.cost),
paid(FlavorDimension.cost, applicationIdSuffix = ".paid")
}
fun Project.configureFlavors(
commonExtension: CommonExtension<*, *, *, *, *, *>,
) {
commonExtension.apply {
flavorDimensions += FlavorDimension.cost.name
productFlavors {
FlightsFlavor.values().forEach {
create(it.name) {
dimension = it.dimension.name
if (this@apply is ApplicationExtension && this is ApplicationProductFlavor) {
if (it.applicationIdSuffix != null) {
applicationIdSuffix = it.applicationIdSuffix
}
}
}
}
}
}
}

View File

@ -0,0 +1,48 @@
package dev.adriankuta.flights
import com.android.build.api.dsl.CommonExtension
import com.android.build.api.dsl.ManagedVirtualDevice
import org.gradle.api.Project
import org.gradle.kotlin.dsl.dependencies
internal fun Project.configureGradleManagedDevices(
commonExtension: CommonExtension<*, *, *, *, *, *>,
) {
commonExtension.apply {
testOptions {
managedDevices {
devices.register("pixel2api30", ManagedVirtualDevice::class.java) {
device = "Pixel 2"
apiLevel = 30
systemImageSource = "aosp"
}
// Get rid of devices.register block and uncomment the following once we can update our AGP version.
// localDevices.create("pixel2api30") {
// // Use device profiles you typically see in Android Studio.
// device = "Pixel 2"
// // Use only API levels 27 and higher.
// apiLevel = 30
// // To include Google services, use "google".
// systemImageSource = "aosp"
// }
groups.create("default") {
targetDevices.add(devices.getByName("pixel2api30"))
}
}
}
}
dependencies {
// Something has gone awry in 8.3.0-rc01. I robbed the following from here:
// https://sourceforge.net/projects/guava.mirror/files/v32.1.0/
// If we are not on 8.3.0-rc01 anymore then remove this and run a test with gradle managed
// device (ie ./gradlew pixel2api30DevDebugAndroidTest). If you dont see a dependancy problem
// with guava:listenablefuture then we are good to remove it.
modules {
module("com.google.guava:listenablefuture") {
replacedBy("com.google.guava:guava", "listenablefuture is part of guava")
}
}
}
}

View File

@ -0,0 +1,23 @@
package dev.adriankuta.flights
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.getByType
internal fun Project.configureHilt() {
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
dependencies {
"implementation"(libs.findLibrary("hilt.android").get())
"ksp"(libs.findLibrary("hilt.compiler").get())
// For instrumentation tests
"androidTestImplementation"(libs.findLibrary("hilt.testing").get())
"kspAndroidTest"(libs.findLibrary("hilt.compiler").get())
// For local unit tests hilt-android-testing
"testImplementation"(libs.findLibrary("hilt.testing").get())
"kspTest"(libs.findLibrary("hilt.compiler").get())
}
}

View File

@ -0,0 +1,18 @@
package dev.adriankuta.flights
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.getByType
internal fun Project.configureInstrumentation() {
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
dependencies {
"androidTestImplementation"(libs.findLibrary("truth").get())
"androidTestImplementation"(libs.findLibrary("mockk.android").get())
"androidTestImplementation"(libs.findLibrary("androidx.test.rules").get())
"androidTestImplementation"(libs.findLibrary("androidx.test.uiautomator").get())
//"androidTestImplementation"(libs.findLibrary("turbine").get())
}
}

View File

@ -0,0 +1,148 @@
package dev.adriankuta.flights
import com.android.build.api.variant.AndroidComponentsExtension
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.api.tasks.testing.Test
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.register
import org.gradle.kotlin.dsl.withType
import org.gradle.testing.jacoco.plugins.JacocoPluginExtension
import org.gradle.testing.jacoco.plugins.JacocoTaskExtension
import org.gradle.testing.jacoco.tasks.JacocoReport
import java.util.Locale
// Mostly robbed off: https://github.com/android/nowinandroid/blob/main/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Jacoco.kt
// ./gradlew jacocoTestReport will run lint, all the tests and generate the test coverage.
internal fun Project.configureJacoco(
androidComponentsExtension: AndroidComponentsExtension<*, *, *>,
) {
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
configure<JacocoPluginExtension> {
toolVersion = libs.findVersion("jacoco").get().toString()
}
val jacocoTestReport = tasks.create("jacocoTestReport") {
group = "Verification"
description = "Create test coverage reports"
}
androidComponentsExtension.onVariants { variant ->
if (variant.name.contains("DevDebug", ignoreCase = true)) {
val unitTestTaskName = "test${variant.name.capitalize()}UnitTest"
// val instrumentedTaskName = "defaultGroup${variant.name.capitalize()}AndroidTest" // This does dick on AGP version ancient (Weavr)
val instrumentedTaskName = "connected${variant.name.capitalize()}AndroidTest"
val lintTaskName = "lint${variant.name.capitalize()}"
val detektTaskName = "detekt${variant.name.capitalize()}"
val buildDir = layout.buildDirectory.get().asFile
val reportTask =
tasks.register(
"jacoco${unitTestTaskName.capitalize()}Report",
JacocoReport::class,
) {
dependsOn(unitTestTaskName)
// Dont bother with teh instrumented test if there isnt any.
if (file("${projectDir}/src/androidTest").walk().any { it.isFile }) {
dependsOn(instrumentedTaskName)
}
dependsOn(lintTaskName)
dependsOn(detektTaskName)
reports {
xml.required.set(true)
html.required.set(true)
}
classDirectories.setFrom(
fileTree("$buildDir/tmp/kotlin-classes/${variant.name}") {
exclude(coverageExclusions)
},
)
sourceDirectories.setFrom(
files(
"$projectDir/src/main/java",
"$projectDir/src/main/kotlin",
),
)
executionData.setFrom(
fileTree("$buildDir") {
include("outputs/unit_test_code_coverage/devDebugUnitTest/*.exec")
include("outputs/managed_device_code_coverage/debug/flavors/dev/**/*.ec")
},
)
}
jacocoTestReport.dependsOn(reportTask)
}
}
tasks.withType<Test>().configureEach {
configure<JacocoTaskExtension> {
// Required for JaCoCo + Robolectric
// https://github.com/robolectric/robolectric/issues/2230
isIncludeNoLocationClasses = true
// Required for JDK 11 with the above
// https://github.com/gradle/gradle/issues/5184#issuecomment-391982009
excludes = listOf("jdk.internal.*")
}
}
}
private val coverageExclusions = listOf(
// data binding
"android/databinding/**/*.class",
"**/android/databinding/*Binding.class",
"**/android/databinding/*",
"**/androidx/databinding/*",
"**/BR.*",
// android
"**/R.class",
"**/R$*.class",
"**/BuildConfig.*",
"**/Manifest*.*",
"**/*Test*.*",
"android/**/*.*",
// butterKnife
"**/*\$ViewInjector*.*",
"**/*\$ViewBinder*.*",
// dagger
"**/*_MembersInjector.class",
"**/Dagger*Component.class",
"**/Dagger*Component\$Builder.class",
"**/*Module_*Factory.class",
"**/di/module/*",
"**/*_Factory*.*",
"**/*Module*.*",
"**/*Dagger*.*",
"**/*Hilt*.*",
// kotlin
"**/*MapperImpl*.*",
"**/*\$ViewInjector*.*",
"**/*\$ViewBinder*.*",
"**/BuildConfig.*",
"**/*Component*.*",
"**/*BR*.*",
"**/Manifest*.*",
"**/*\$Lambda$*.*",
"**/*Companion*.*",
"**/*Module*.*",
"**/*Dagger*.*",
"**/*Hilt*.*",
"**/*MembersInjector*.*",
"**/*_MembersInjector.class",
"**/*_Factory*.*",
"**/*_Provide*Factory*.*",
"**/*Extensions*.*",
"**/*JsonAdapter.*",
)
private fun String.capitalize() = replaceFirstChar {
if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()
}

View File

@ -0,0 +1,68 @@
package dev.adriankuta.flights
import com.android.build.api.dsl.CommonExtension
import org.gradle.api.JavaVersion
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.kotlin.dsl.assign
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.provideDelegate
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension
internal fun Project.configureKotlinAndroid(
commonExtension: CommonExtension<*, *, *, *, *, *>,
) {
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
commonExtension.apply {
compileSdk = libs.findVersion("compileSdk").get().toString().toInt()
defaultConfig {
minSdk = libs.findVersion("minSdk").get().toString().toInt()
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
isCoreLibraryDesugaringEnabled = true
}
packaging {
resources.excludes.add("/META-INF/{AL2.0,LGPL2.1}")
resources.excludes.add("/META-INF/LICENSE.md")
resources.excludes.add("/META-INF/LICENSE-notice.md")
resources.excludes.add("/META-INF/INDEX.LIST")
}
}
with(extensions.getByType<KotlinAndroidProjectExtension>()) {
compilerOptions {
// Treat all Kotlin warnings as errors (disabled by default)
// Override by setting warningsAsErrors=true in your ~/.gradle/gradle.properties
val warningsAsErrors: String? by project
allWarningsAsErrors = warningsAsErrors.toBoolean()
freeCompilerArgs.addAll(
listOf(
"-opt-in=kotlin.RequiresOptIn",
"-Xwhen-guards",
// Enable experimental coroutines APIs, including Flow
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-opt-in=kotlinx.coroutines.FlowPreview",
),
)
jvmTarget.set(JvmTarget.JVM_11)
}
}
dependencies {
"implementation"(libs.findLibrary("androidx.core.ktx").get())
"implementation"(libs.findLibrary("kotlinx.coroutines.android").get())
"implementation"(libs.findLibrary("timber").get())
"coreLibraryDesugaring"(libs.findLibrary("android.desugarJdkLibs").get())
}
}

View File

@ -0,0 +1,57 @@
package dev.adriankuta.flights
import com.android.build.gradle.LibraryExtension
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.getByType
import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension
internal fun Project.configureLibrary() {
with(pluginManager) {
apply("com.android.library")
apply("io.gitlab.arturbosch.detekt")
apply("org.jetbrains.kotlin.android")
apply("org.gradle.jacoco")
}
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
val targetSdk = libs.findVersion("targetSdk").get().toString().toInt()
// android block
extensions.configure<LibraryExtension> {
configureKotlinAndroid(this)
defaultConfig.targetSdk = targetSdk
defaultConfig.consumerProguardFiles("consumer-rules.pro")
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
}
debug {
testCoverage {
enableUnitTestCoverage =
false // Test coverage spanks memory on the CI with ye older AGP
enableAndroidTestCoverage = false
}
}
}
configureFlavors(this)
configureAndroidLint(this)
// configureGradleManagedDevices(this)
}
// val extension = extensions.getByType<LibraryAndroidComponentsExtension>()
// configureJacoco(extension)
configureDetektDependencies()
//configureSonar()
configureUnitTests()
extensions.configure<KotlinAndroidProjectExtension> {
//explicitApi()
}
}

View File

@ -0,0 +1,33 @@
package dev.adriankuta.flights
import org.gradle.api.Project
// If have got Sonar running locally you can try it by setting up a project and then running this
// script:
//
// #!/bin/bash -e
//
//./gradlew clean
//./gradlew jacocoTestReport
//./gradlew sonar \
// -Dsonar.projectKey=YourProjectKey \
// -Dsonar.projectName='YourProjectName' \
// -Dsonar.host.url=http://localhost:9000 \
// -Dsonar.token=sqp_sonarqubelkeywhateveritis
//
internal fun Project.configureSonar() {
// configure<SonarExtension> {
// properties {
// val buildDir = layout.buildDirectory.get().toString()
// property("sonar.androidLint.reportPaths", "$buildDir/reports/lint-results-devDebug.xml")
// property("sonar.core.codeCoveragePlugin", "jacoco")
// property("sonar.coverage.jacoco.xmlReportPaths", "$buildDir/reports/jacoco/**/*.xml")
// property("sonar.gradle.skipCompile", true)
// property("sonar.kotlin.detekt.reportPaths", "$buildDir/reports/detekt/devDebug.xml")
// property("sonar.verbose", true)
//
// property("sonar.junit.reportPaths",
// "$buildDir/test-results/testDevDebugUnitTest,$buildDir/outputs/androidTest-results/managedDevice/debug/flavors/dev/pixel2api30")
// }
// }
}

View File

@ -0,0 +1,18 @@
package dev.adriankuta.flights
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.getByType
internal fun Project.configureUnitTests() {
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
dependencies {
"testImplementation"(libs.findLibrary("junit4").get())
"testImplementation"(libs.findLibrary("androidx.test.core").get())
"testImplementation"(libs.findLibrary("kotlinx.coroutines.test").get())
"testImplementation"(libs.findLibrary("truth").get())
"testImplementation"(libs.findLibrary("mockk.android").get())
//"testImplementation"(libs.findLibrary("turbine").get())
}
}

View File

@ -0,0 +1,4 @@
# Gradle properties are not passed to included builds https://github.com/gradle/gradle/issues/2534
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configureondemand=true

View File

@ -0,0 +1,30 @@
/*
* Copyright 2022 The Android Open Source Project
*
* 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.
*/
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}
rootProject.name = "build-logic"
include(":convention")

View File

@ -1,8 +1,22 @@
buildscript {
repositories {
google()
mavenCentral()
}
}
// 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")
alias(libs.plugins.com.android.application) apply false
alias(libs.plugins.com.android.library) apply false
alias(libs.plugins.android.test) apply false
alias(libs.plugins.compose) apply false
alias(libs.plugins.detekt) apply false
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.kotlin.serialization) apply false
alias(libs.plugins.hilt) apply false
alias(libs.plugins.ksp) apply false
alias(libs.plugins.secrets) apply false
//alias(libs.plugins.module.graph) apply true // Plugin applied to allow module graph generation
}

View File

@ -0,0 +1,13 @@
@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
plugins {
alias(libs.plugins.flights.android.library)
alias(libs.plugins.flights.android.library.hilt)
}
android {
namespace = "dev.adriankuta.flights.core.util"
}
dependencies {
}

View File

@ -0,0 +1,10 @@
# Deviations from defaults
formatting:
TrailingCommaOnCallSite:
active: true
autoCorrect: true
useTrailingCommaOnCallSite: true
TrailingCommaOnDeclarationSite:
active: true
autoCorrect: true
useTrailingCommaOnDeclarationSite: true

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.10.1" type="baseline" client="gradle" dependencies="false" name="AGP (8.10.1)" variant="all" version="8.10.1">
<issue
id="Aligned16KB"
message="The native library `arm64-v8a/libmockkjvmtiagent.so` (from `io.mockk:mockk-agent-android:1.14.2`) is not 16 KB aligned">
<location
file="$GRADLE_USER_HOME/caches/8.11.1/transforms/9ee3fe20033b4dd897c7dfcf7c303d16/transformed/mockk-agent-android-1.14.2/jni/arm64-v8a/libmockkjvmtiagent.so"/>
</issue>
</issues>

View File

@ -0,0 +1,44 @@
package dev.adriankuta.flights.core.util
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import javax.inject.Qualifier
import javax.inject.Singleton
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class IoDispatcher
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class DefaultDispatcher
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class ApplicationScope
@Module
@InstallIn(SingletonComponent::class)
object CoroutinesModule {
@Provides
@IoDispatcher
fun providesIODispatcher(): CoroutineDispatcher = Dispatchers.IO
@Provides
@DefaultDispatcher
fun providesDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default
@Provides
@Singleton
@ApplicationScope
fun providesCoroutineScope(
@DefaultDispatcher dispatcher: CoroutineDispatcher,
): CoroutineScope = CoroutineScope(SupervisorJob() + dispatcher)
}

View File

@ -0,0 +1,13 @@
plugins {
alias(libs.plugins.flights.android.library)
alias(libs.plugins.flights.android.library.hilt)
}
android {
namespace = "dev.adriankuta.flights.domain.types"
}
dependencies {
implementation(projects.core.util)
implementation(libs.timber)
}

View File

@ -0,0 +1,10 @@
# Deviations from defaults
formatting:
TrailingCommaOnCallSite:
active: true
autoCorrect: true
useTrailingCommaOnCallSite: true
TrailingCommaOnDeclarationSite:
active: true
autoCorrect: true
useTrailingCommaOnDeclarationSite: true

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.10.1" type="baseline" client="gradle" dependencies="false" name="AGP (8.10.1)" variant="all" version="8.10.1">
<issue
id="Aligned16KB"
message="The native library `arm64-v8a/libmockkjvmtiagent.so` (from `io.mockk:mockk-agent-android:1.14.2`) is not 16 KB aligned">
<location
file="$GRADLE_USER_HOME/caches/8.11.1/transforms/9ee3fe20033b4dd897c7dfcf7c303d16/transformed/mockk-agent-android-1.14.2/jni/arm64-v8a/libmockkjvmtiagent.so"/>
</issue>
</issues>

View File

@ -0,0 +1,6 @@
package dev.adriankuta.flights.domain.types
data class Character(
val name: String,
val category: String? = null,
)

114
gradle/libs.versions.toml Normal file
View File

@ -0,0 +1,114 @@
[versions]
targetSdk = "36"
compileSdk = "36" # Has to be 34 for Weavr
minSdk = "21"
activityKtx = "1.10.1"
androidDesugarJdkLibs = "2.1.5"
androidGradlePlugin = "8.10.1"
androidTools = "31.10.1"
androidxActivity = "1.10.1"
androidxComposeBom = "2025.05.01"
androidxCore = "1.16.0"
androidxCoreSplashscreen = "1.0.1"
androidxJunit = "1.2.1"
androidxLifecycle = "2.9.0"
androidxNavigation = "2.9.0"
appUpdateKtx = "2.1.0"
appcompat = "1.7.0"
coreTest = "1.6.1" # https://developer.android.com/jetpack/androidx/releases/test
detekt = "1.23.8" # https://detekt.dev/changelog
detektCompose = "0.4.22" # https://github.com/mrmans0n/compose-rules/releases
espressoCore = "3.6.1"
gson = "2.13.1"
hilt = "2.56.2"
hiltNavigationCompose = "1.2.0"
immutableCollections = "0.4.0"
junit4 = "4.13.2"
kotlin = "2.1.21"
kotlinxCoroutinesAndroid = "1.10.2"
kotlinxSerializationJson = "1.8.1"
ksp = "2.1.21-2.0.1" # https://github.com/google/ksp/releases
material = "1.12.0"
mockk = "1.14.2" # https://github.com/mockk/mockk/releases
secrets = "2.0.1"
testRules = "1.6.1" # https://developer.android.com/jetpack/androidx/releases/test
testRunner = "1.6.2" # https://developer.android.com/jetpack/androidx/releases/test
timber = "5.0.1"
truth = "1.4.4"
uiAutomator = "2.3.0"
uiTextGoogleFonts = "1.8.2"
[libraries]
android-desugarJdkLibs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "androidDesugarJdkLibs" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidxActivity" }
androidx-activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activityKtx" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "androidxComposeBom" }
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-compose-ui-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-compose-ui-test-junit = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidxCore" }
androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "androidxCoreSplashscreen" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidxLifecycle" }
androidx-lifecycle-runtime-testing = { group = "androidx.lifecycle", name = "lifecycle-runtime-testing", version.ref = "androidxLifecycle" }
androidx-lifecycle-viewModelCompose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "androidxLifecycle" }
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "androidxNavigation" }
androidx-navigation-testing = { group = "androidx.navigation", name = "navigation-testing", version.ref = "androidxNavigation" }
androidx-test-core = { module = "androidx.test:core", version.ref = "coreTest" }
androidx-test-ext = { group = "androidx.test.ext", name = "junit", version.ref = "androidxJunit" }
androidx-test-rules = { module = "androidx.test:rules", version.ref = "testRules" }
androidx-test-runner = { module = "androidx.test:runner", version.ref = "testRunner" }
androidx-test-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version.ref = "uiAutomator" }
androidx-ui-text-google-fonts = { module = "androidx.compose.ui:ui-text-google-fonts", version.ref = "uiTextGoogleFonts" }
app-update-ktx = { module = "com.google.android.play:app-update-ktx", version.ref = "appUpdateKtx" }
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
detekt-compose = { module = "io.nlopez.compose.rules:detekt", version.ref = "detektCompose" }
detekt-ktlint = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" }
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }
hilt-testing = { module = "com.google.dagger:hilt-android-testing", version.ref = "hilt" }
junit4 = { group = "junit", name = "junit", version.ref = "junit4" }
kotlin-test = { group = "org.jetbrains.kotlin", name = "kotlin-test", version.ref = "kotlin" }
kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "immutableCollections" }
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesAndroid" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutinesAndroid" }
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
mockk-android = { module = "io.mockk:mockk-android", version.ref = "mockk" }
timber = { group = "com.jakewharton.timber", name = "timber", version.ref = "timber" }
truth = { module = "com.google.truth:truth", version.ref = "truth" }
# Dependencies of the included build-logic
android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }
android-tools-common = { group = "com.android.tools", name = "common", version.ref = "androidTools" }
compose-gradlePlugin = { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin" }
detekt-gradlePlugin = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "detekt" }
kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" }
ksp-gradlePlugin = { group = "com.google.devtools.ksp", name = "com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" }
[plugins]
com-android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
com-android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" }
android-test = { id = "com.android.test", version.ref = "androidGradlePlugin" }
compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
flights-android-library = { id = "flights.android.library" }
flights-android-library-compose = { id = "flights.android.library.compose" }
flights-android-application = { id = "flights.android.application" }
flights-android-application-compose = { id = "flights.android.application.compose" }
flights-android-application-hilt = { id = "flights.android.application.hilt" }
flights-android-library-hilt = { id = "flights.android.library.hilt" }
secrets = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "secrets" }

View File

@ -1,6 +1,6 @@
#Fri May 10 15:41:43 CEST 2024
#Thu Jun 12 22:42:35 CEST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -0,0 +1,15 @@
plugins {
alias(libs.plugins.flights.android.library)
alias(libs.plugins.flights.android.library.hilt)
}
android {
namespace = "dev.adriankuta.flights.model.data.api"
}
dependencies {
implementation(projects.core.util)
implementation(libs.timber)
implementation(libs.gson)
}

View File

@ -0,0 +1,12 @@
plugins {
alias(libs.plugins.flights.android.library)
alias(libs.plugins.flights.android.library.hilt)
}
android {
namespace = "dev.adriankuta.flights.model.data.room"
}
dependencies {
implementation(libs.timber)
}

View File

@ -0,0 +1,10 @@
# Deviations from defaults
formatting:
TrailingCommaOnCallSite:
active: true
autoCorrect: true
useTrailingCommaOnCallSite: true
TrailingCommaOnDeclarationSite:
active: true
autoCorrect: true
useTrailingCommaOnDeclarationSite: true

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.10.1" type="baseline" client="gradle" dependencies="false" name="AGP (8.10.1)" variant="all" version="8.10.1">
<issue
id="Aligned16KB"
message="The native library `arm64-v8a/libmockkjvmtiagent.so` (from `io.mockk:mockk-agent-android:1.14.2`) is not 16 KB aligned">
<location
file="$GRADLE_USER_HOME/caches/8.11.1/transforms/9ee3fe20033b4dd897c7dfcf7c303d16/transformed/mockk-agent-android-1.14.2/jni/arm64-v8a/libmockkjvmtiagent.so"/>
</issue>
</issues>

View File

@ -0,0 +1,15 @@
plugins {
alias(libs.plugins.flights.android.library)
alias(libs.plugins.flights.android.library.hilt)
}
android {
namespace = "dev.adriankuta.flights.model.data.simple"
}
dependencies {
implementation(projects.core.util)
implementation(libs.timber)
implementation(libs.gson)
}

View File

@ -0,0 +1,10 @@
# Deviations from defaults
formatting:
TrailingCommaOnCallSite:
active: true
autoCorrect: true
useTrailingCommaOnCallSite: true
TrailingCommaOnDeclarationSite:
active: true
autoCorrect: true
useTrailingCommaOnDeclarationSite: true

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.10.1" type="baseline" client="gradle" dependencies="false" name="AGP (8.10.1)" variant="all" version="8.10.1">
<issue
id="Aligned16KB"
message="The native library `arm64-v8a/libmockkjvmtiagent.so` (from `io.mockk:mockk-agent-android:1.14.2`) is not 16 KB aligned">
<location
file="$GRADLE_USER_HOME/caches/8.11.1/transforms/9ee3fe20033b4dd897c7dfcf7c303d16/transformed/mockk-agent-android-1.14.2/jni/arm64-v8a/libmockkjvmtiagent.so"/>
</issue>
</issues>

View File

@ -0,0 +1,59 @@
{
"data": [
{
"name": "Adam Małysz"
}, {
"name": "Harry Potter"
}, {
"name": "SpongeBob"
}, {
"name": "Miś uszatek"
}, {
"name": "Brzydkie kaczątko"
}, {
"name": "Papa Smerf"
}, {
"name": "Smerfetka"
}, {
"name": "Gargamel",
"category": "Smerfy"
}, {
"name": "Ważniak",
"category": "Smerfy"
}, {
"name": "Roszpunka"
}, {
"name": "Myszka Miki"
}, {
"name": "Król Lew"
}, {
"name": "Elsa",
"category": "Kraina lodu"
}, {
"name": "Olaf",
"category": "Kraina lodu"
}, {
"name": "Mała syrenka"
}, {
"name": "Kubuś Puchatek"
}, {
"name": "Prosiaczek",
"category": "Kubuś Puchatek"
}, {
"name": "Kłapouchy",
"category": "Kubuś Puchatek"
}, {
"name": "Królik",
"category": "Kubuś Puchatek"
}, {
"name": "Maleństwo",
"category": "Kubuś Puchatek"
}, {
"name": "Pan Sowa",
"category": "Kubuś Puchatek"
}, {
"name": "Krzyś",
"category": "Kubuś Puchatek"
}
]
}

View File

@ -0,0 +1,14 @@
plugins {
alias(libs.plugins.flights.android.library)
alias(libs.plugins.flights.android.library.hilt)
}
android {
namespace = "dev.adriankuta.flights.model.repository"
}
dependencies {
implementation(libs.timber)
testImplementation("io.mockk:mockk:1.13.8")
}

Some files were not shown because too many files have changed in this diff Show More