diff --git a/.gitignore b/.gitignore
index cee9f91..213dfad 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
diff --git a/app/.gitignore b/app/.gitignore
deleted file mode 100644
index 42afabf..0000000
--- a/app/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 88a3501..39af1db 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -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(projects.ui.designsystem)
+ implementation(projects.ui.home)
- implementation("androidx.core:core-ktx:1.16.0")
- implementation("androidx.appcompat:appcompat:1.7.0")
- implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.9.0")
-
- // Dependency Injection
- implementation("com.google.dagger:hilt-android:2.56.2")
- ksp("com.google.dagger:hilt-compiler:2.56.2")
-
- // Network
- ksp("com.squareup.moshi:moshi-kotlin-codegen:1.15.2")
- implementation("com.squareup.moshi:moshi:1.15.2")
- implementation("com.squareup.retrofit2:retrofit:2.11.0")
- implementation("com.squareup.retrofit2:converter-moshi:2.11.0")
- implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
-
-}
\ No newline at end of file
+ implementation(libs.androidx.activity.compose)
+ implementation(libs.androidx.core.splashscreen)
+ implementation(libs.androidx.hilt.navigation.compose)
+ implementation(libs.app.update.ktx)
+}
diff --git a/app/config/detekt/detekt.yml b/app/config/detekt/detekt.yml
new file mode 100644
index 0000000..ab95a32
--- /dev/null
+++ b/app/config/detekt/detekt.yml
@@ -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
\ No newline at end of file
diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml
new file mode 100644
index 0000000..3acbfa8
--- /dev/null
+++ b/app/lint-baseline.xml
@@ -0,0 +1,205 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index ff59496..44a4007 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -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
@@ -18,4 +18,7 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
-#-renamesourcefileattribute SourceFile
\ No newline at end of file
+#-renamesourcefileattribute SourceFile
+
+-keep class dev.adriankuta.flights.data.dto.* { *; }
+-keep class dev.adriankuta.flights.data.model.* { *; }
\ No newline at end of file
diff --git a/app/src/debug/kotlin/dev/adriankuta/flights/MyApplication.kt b/app/src/debug/kotlin/dev/adriankuta/flights/MyApplication.kt
new file mode 100644
index 0000000..1548e75
--- /dev/null
+++ b/app/src/debug/kotlin/dev/adriankuta/flights/MyApplication.kt
@@ -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())
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d7fc4a9..79a2989 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,21 +1,29 @@
+ xmlns:tools="http://schemas.android.com/tools">
+ android:theme="@style/Theme.Flights"
+ tools:targetApi="33">
+ android:name=".MainActivity"
+ android:exported="true"
+ android:theme="@style/Theme.Flights">
+
-
\ No newline at end of file
+
+
diff --git a/app/src/main/assets/famous-characters.txt b/app/src/main/assets/famous-characters.txt
new file mode 100644
index 0000000..a84feff
--- /dev/null
+++ b/app/src/main/assets/famous-characters.txt
@@ -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)
\ No newline at end of file
diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png
new file mode 100644
index 0000000..b716707
Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ
diff --git a/app/src/main/java/com/ryanair/androidchallenge/di/NetworkModule.kt b/app/src/main/java/com/ryanair/androidchallenge/di/NetworkModule.kt
deleted file mode 100644
index 0e2fc4f..0000000
--- a/app/src/main/java/com/ryanair/androidchallenge/di/NetworkModule.kt
+++ /dev/null
@@ -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)
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/ryanair/androidchallenge/repository/airports/api/AirportService.kt b/app/src/main/java/com/ryanair/androidchallenge/repository/airports/api/AirportService.kt
deleted file mode 100644
index de28d6f..0000000
--- a/app/src/main/java/com/ryanair/androidchallenge/repository/airports/api/AirportService.kt
+++ /dev/null
@@ -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
-
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/ryanair/androidchallenge/repository/airports/api/RoutesService.kt b/app/src/main/java/com/ryanair/androidchallenge/repository/airports/api/RoutesService.kt
deleted file mode 100644
index c5c497a..0000000
--- a/app/src/main/java/com/ryanair/androidchallenge/repository/airports/api/RoutesService.kt
+++ /dev/null
@@ -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
-
- @GET("/views/locate/5/routes/{language}/airport/{departure}")
- suspend fun getRoutes(
- @Path("language") languageCode: String,
- @Path("departure") departureAirportCode: String,
- ): List
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/ryanair/androidchallenge/repository/airports/model/AirportResponse.kt b/app/src/main/java/com/ryanair/androidchallenge/repository/airports/model/AirportResponse.kt
deleted file mode 100644
index 4c96339..0000000
--- a/app/src/main/java/com/ryanair/androidchallenge/repository/airports/model/AirportResponse.kt
+++ /dev/null
@@ -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?
- )
-}
diff --git a/app/src/main/java/com/ryanair/androidchallenge/repository/airports/model/RouteResponse.kt b/app/src/main/java/com/ryanair/androidchallenge/repository/airports/model/RouteResponse.kt
deleted file mode 100644
index 22c128f..0000000
--- a/app/src/main/java/com/ryanair/androidchallenge/repository/airports/model/RouteResponse.kt
+++ /dev/null
@@ -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?
- )
- }
-}
diff --git a/app/src/main/java/com/ryanair/androidchallenge/repository/flights/api/FlightService.kt b/app/src/main/java/com/ryanair/androidchallenge/repository/flights/api/FlightService.kt
deleted file mode 100644
index 5c08faa..0000000
--- a/app/src/main/java/com/ryanair/androidchallenge/repository/flights/api/FlightService.kt
+++ /dev/null
@@ -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
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/ryanair/androidchallenge/repository/flights/model/FlightResponse.kt b/app/src/main/java/com/ryanair/androidchallenge/repository/flights/model/FlightResponse.kt
deleted file mode 100644
index 3dd4887..0000000
--- a/app/src/main/java/com/ryanair/androidchallenge/repository/flights/model/FlightResponse.kt
+++ /dev/null
@@ -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?
-)
-
-@JsonClass(generateAdapter = true)
-data class Trip(
- @Json(name = "dates") val dates: List?,
- @Json(name = "origin") val origin: String?,
- @Json(name = "destination") val destination: String?
-)
-
-@JsonClass(generateAdapter = true)
-data class TripDate(
- @Json(name = "dateOut") val dateOut: String?,
- @Json(name = "flights") val flights: List?
-)
-
-@JsonClass(generateAdapter = true)
-data class TripFlight(
- @Json(name = "faresLeft") val faresLeft: Int?,
- @Json(name = "regularFare") val regularFare: RegularFare?,
- @Json(name = "flightNumber") val flightNumber: String?,
- @Json(name = "time") val dateTimes: List?,
- @Json(name = "duration") val duration: String?,
- @Json(name = "segments") val segments: List?,
- @Json(name = "operatedBy") val operatedBy: String?,
-)
-
-@JsonClass(generateAdapter = true)
-data class RegularFare(
- @Json(name = "fares") val fares: List?
-)
-
-@JsonClass(generateAdapter = true)
-data class TripFare(
- @Json(name = "type") val type: String?,
- @Json(name = "amount") val amount: Double?,
- @Json(name = "count") val count: Int?
-)
-
-@JsonClass(generateAdapter = true)
-data class Segment(
- @Json(name = "origin") val origin: String?,
- @Json(name = "destination") val destination: String?,
- @Json(name = "flightNumber") val flightNumber: String?,
- @Json(name = "time") val dateTimes: List?,
- @Json(name = "duration") val duration: String?
-)
\ No newline at end of file
diff --git a/app/src/main/java/com/ryanair/androidchallenge/ui/MainActivity.kt b/app/src/main/java/com/ryanair/androidchallenge/ui/MainActivity.kt
deleted file mode 100644
index 476825c..0000000
--- a/app/src/main/java/com/ryanair/androidchallenge/ui/MainActivity.kt
+++ /dev/null
@@ -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()
- }
- }
- }
-}
diff --git a/app/src/main/java/com/ryanair/androidchallenge/ui/search/SearchScreen.kt b/app/src/main/java/com/ryanair/androidchallenge/ui/search/SearchScreen.kt
deleted file mode 100644
index 3427947..0000000
--- a/app/src/main/java/com/ryanair/androidchallenge/ui/search/SearchScreen.kt
+++ /dev/null
@@ -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")
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/ryanair/androidchallenge/ui/theme/Color.kt b/app/src/main/java/com/ryanair/androidchallenge/ui/theme/Color.kt
deleted file mode 100644
index 5443dfb..0000000
--- a/app/src/main/java/com/ryanair/androidchallenge/ui/theme/Color.kt
+++ /dev/null
@@ -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() }
\ No newline at end of file
diff --git a/app/src/main/java/com/ryanair/androidchallenge/ui/theme/Spacing.kt b/app/src/main/java/com/ryanair/androidchallenge/ui/theme/Spacing.kt
deleted file mode 100644
index cd9a8a3..0000000
--- a/app/src/main/java/com/ryanair/androidchallenge/ui/theme/Spacing.kt
+++ /dev/null
@@ -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() }
diff --git a/app/src/main/java/com/ryanair/androidchallenge/ui/theme/Theme.kt b/app/src/main/java/com/ryanair/androidchallenge/ui/theme/Theme.kt
deleted file mode 100644
index 5b3086c..0000000
--- a/app/src/main/java/com/ryanair/androidchallenge/ui/theme/Theme.kt
+++ /dev/null
@@ -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
-}
diff --git a/app/src/main/java/com/ryanair/androidchallenge/ui/theme/Typography.kt b/app/src/main/java/com/ryanair/androidchallenge/ui/theme/Typography.kt
deleted file mode 100644
index a5f009b..0000000
--- a/app/src/main/java/com/ryanair/androidchallenge/ui/theme/Typography.kt
+++ /dev/null
@@ -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() }
\ No newline at end of file
diff --git a/app/src/main/kotlin/dev/adriankuta/flights/FlightsNavGraph.kt b/app/src/main/kotlin/dev/adriankuta/flights/FlightsNavGraph.kt
new file mode 100644
index 0000000..e6027eb
--- /dev/null
+++ b/app/src/main/kotlin/dev/adriankuta/flights/FlightsNavGraph.kt
@@ -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()
+ }
+}
diff --git a/app/src/main/kotlin/dev/adriankuta/flights/MainActivity.kt b/app/src/main/kotlin/dev/adriankuta/flights/MainActivity.kt
new file mode 100644
index 0000000..46ec3a9
--- /dev/null
+++ b/app/src/main/kotlin/dev/adriankuta/flights/MainActivity.kt
@@ -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)
diff --git a/app/src/main/kotlin/dev/adriankuta/flights/ui/FlightsApp.kt b/app/src/main/kotlin/dev/adriankuta/flights/ui/FlightsApp.kt
new file mode 100644
index 0000000..da24fda
--- /dev/null
+++ b/app/src/main/kotlin/dev/adriankuta/flights/ui/FlightsApp.kt
@@ -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))
+ }
+ }
+}
diff --git a/app/src/main/kotlin/dev/adriankuta/flights/ui/InAppUpdates.kt b/app/src/main/kotlin/dev/adriankuta/flights/ui/InAppUpdates.kt
new file mode 100644
index 0000000..f9bb975
--- /dev/null
+++ b/app/src/main/kotlin/dev/adriankuta/flights/ui/InAppUpdates.kt
@@ -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 { continuation ->
+ appUpdateInfo.addOnSuccessListener {
+ continuation.resumeWith(Result.success(it))
+ }.addOnFailureListener {
+ continuation.resumeWith(Result.failure(it))
+ }
+ }
diff --git a/app/src/main/res/drawable/ic_baseline_flight_land.xml b/app/src/main/res/drawable/ic_baseline_flight_land.xml
deleted file mode 100644
index a47afda..0000000
--- a/app/src/main/res/drawable/ic_baseline_flight_land.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/ic_baseline_flight_takeoff.xml b/app/src/main/res/drawable/ic_baseline_flight_takeoff.xml
deleted file mode 100644
index e74cffd..0000000
--- a/app/src/main/res/drawable/ic_baseline_flight_takeoff.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
index b03f0f5..ca3826a 100644
--- a/app/src/main/res/drawable/ic_launcher_background.xml
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -1,170 +1,74 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ xmlns:android="http://schemas.android.com/apk/res/android">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
similarity index 95%
rename from app/src/main/res/drawable-v24/ic_launcher_foreground.xml
rename to app/src/main/res/drawable/ic_launcher_foreground.xml
index 41ea3f9..2b068d1 100644
--- a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
+++ b/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -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" />
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
index eca70cf..c4a603d 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -1,5 +1,5 @@
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
index eca70cf..c4a603d 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -1,5 +1,5 @@
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
index c209e78..9f092de 100644
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.webp and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..9b3d81d
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
index b2dfe3d..80b31b2 100644
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
index 4f0f1d6..4b95d06 100644
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.webp and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..1d77e79
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
index 62b611d..1bf668c 100644
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
index 948a307..5dd3b1e 100644
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..8c3cffa
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
index 1b9a695..e384f1c 100644
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
index 28d4b77..a0b0a03 100644
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..d314663
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
index 9287f50..3332ef1 100644
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
index aa7d642..bfb7b18 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..b4e7599
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
index 9126ae3..d6ab7c6 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
new file mode 100644
index 0000000..55344e5
--- /dev/null
+++ b/app/src/main/res/values-pl/strings.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 3a02f80..f8c6127 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,12 +1,10 @@
- #1f60f5
- #0037c1
- #1A6F8DFF
- @color/white
- #f1c931
- #ba9900
- @color/black
- #ff000000
- #ffffffff
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
deleted file mode 100644
index 4b0eea0..0000000
--- a/app/src/main/res/values/dimens.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
- 0dp
- 2dp
- 4dp
- 6dp
- 8dp
- 10dp
- 12dp
- 16dp
- 18dp
- 20dp
- 26dp
- 32dp
- 36dp
- 42dp
- 64dp
-
\ No newline at end of file
diff --git a/app/src/main/res/values/plurals.xml b/app/src/main/res/values/plurals.xml
deleted file mode 100644
index 08f24d6..0000000
--- a/app/src/main/res/values/plurals.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
- - %d Adult
- - %d Adults
-
-
- - %d Teen
- - %d Teens
-
-
- - %d Child
- - %d Children
-
-
- - %d Stop
- - %d Stops
-
-
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 355a891..f5810f2 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,32 +1,3 @@
- AndroidChallenge
- From
- To
- Departure date
- Passengers
- Search
- You need to select departure airport first
- Select departure date
- Adult
- Teen
- Child
- Cancel
- Submit
- Unable to connect to server
- No internet connection
- You need to fill all data first.
- Unfortunately there is no flight on particular date.\nChoose different one.
- Go back
- Total price:
- Adult: %d x %.2f %s
- Teen: %d x %.2f %s
- Child: %d x %.2f %s
- Search airport
- Find airport
- Flights
- Search origin airport
- Search destination airport
- Flight details
- Price range:
- There are no flights in selected price range
+ Flights
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..e577542
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..fa0f996
--- /dev/null
+++ b/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..9ee9997
--- /dev/null
+++ b/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/ryanair/androidchallenge/core/App.kt b/app/src/release/kotlin/dev/adriankuta/flights/MyApplication.kt
similarity index 56%
rename from app/src/main/java/com/ryanair/androidchallenge/core/App.kt
rename to app/src/release/kotlin/dev/adriankuta/flights/MyApplication.kt
index 076e10c..798837d 100644
--- a/app/src/main/java/com/ryanair/androidchallenge/core/App.kt
+++ b/app/src/release/kotlin/dev/adriankuta/flights/MyApplication.kt
@@ -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()
\ No newline at end of file
+class MyApplication : Application() {}
\ No newline at end of file
diff --git a/build-logic/convention/build.gradle.kts b/build-logic/convention/build.gradle.kts
new file mode 100644
index 0000000..b1f158d
--- /dev/null
+++ b/build-logic/convention/build.gradle.kts
@@ -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"
+ }
+ }
+}
diff --git a/build-logic/convention/src/main/kotlin/AndroidApplicationComposeConvention.kt b/build-logic/convention/src/main/kotlin/AndroidApplicationComposeConvention.kt
new file mode 100644
index 0000000..6e64bed
--- /dev/null
+++ b/build-logic/convention/src/main/kotlin/AndroidApplicationComposeConvention.kt
@@ -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 {
+ override fun apply(target: Project) {
+ with(target) {
+ pluginManager.apply("flights.android.application")
+ val extension = extensions.getByType()
+
+ extensions.configure {
+ defaultConfig {
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+ }
+
+ val libs = extensions.getByType().named("libs")
+
+ configureInstrumentation()
+ configureCompose(extension)
+ configureDetektForComposeModuleExceptions()
+
+ dependencies {
+ "androidTestImplementation"(libs.findLibrary("androidx.test.core").get())
+ }
+ }
+ }
+}
diff --git a/build-logic/convention/src/main/kotlin/AndroidApplicationConvention.kt b/build-logic/convention/src/main/kotlin/AndroidApplicationConvention.kt
new file mode 100644
index 0000000..5138ffb
--- /dev/null
+++ b/build-logic/convention/src/main/kotlin/AndroidApplicationConvention.kt
@@ -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 {
+ 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().named("libs")
+
+ // android block
+ extensions.configure {
+ 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()
+// configureJacoco(extension)
+ configureDetektDependencies()
+ //configureSonar()
+ configureUnitTests()
+ }
+
+ }
+
+}
diff --git a/build-logic/convention/src/main/kotlin/AndroidApplicationHiltConvention.kt b/build-logic/convention/src/main/kotlin/AndroidApplicationHiltConvention.kt
new file mode 100644
index 0000000..b715bf0
--- /dev/null
+++ b/build-logic/convention/src/main/kotlin/AndroidApplicationHiltConvention.kt
@@ -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 {
+ override fun apply(target: Project) {
+ with(target) {
+ with(pluginManager) {
+ apply("com.google.devtools.ksp")
+ apply("dagger.hilt.android.plugin")
+ }
+
+ // android block
+ extensions.configure {
+ configureHilt()
+ }
+ }
+ }
+}
diff --git a/build-logic/convention/src/main/kotlin/AndroidDetektComposeConvention.kt b/build-logic/convention/src/main/kotlin/AndroidDetektComposeConvention.kt
new file mode 100644
index 0000000..0dca848
--- /dev/null
+++ b/build-logic/convention/src/main/kotlin/AndroidDetektComposeConvention.kt
@@ -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 {
+ override fun apply(target: Project) {
+ with(target) {
+ configureDetektForComposeModuleExceptions()
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-logic/convention/src/main/kotlin/AndroidDetektConvention.kt b/build-logic/convention/src/main/kotlin/AndroidDetektConvention.kt
new file mode 100644
index 0000000..22b947a
--- /dev/null
+++ b/build-logic/convention/src/main/kotlin/AndroidDetektConvention.kt
@@ -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 {
+ override fun apply(target: Project) {
+ with(target) {
+ configureDetektForNonUiModule()
+ }
+ }
+}
+
diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryComposeConvention.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryComposeConvention.kt
new file mode 100644
index 0000000..fc6ccc8
--- /dev/null
+++ b/build-logic/convention/src/main/kotlin/AndroidLibraryComposeConvention.kt
@@ -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 {
+ override fun apply(target: Project) {
+ with(target) {
+ configureLibrary()
+
+ extensions.configure {
+ defaultConfig {
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+ }
+
+ val extension = extensions.getByType()
+ configureCompose(extension)
+ configureInstrumentation()
+ configureDetektForComposeModuleExceptions()
+ }
+ }
+}
+
diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryConvention.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryConvention.kt
new file mode 100644
index 0000000..d3ab3ef
--- /dev/null
+++ b/build-logic/convention/src/main/kotlin/AndroidLibraryConvention.kt
@@ -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 {
+ override fun apply(target: Project) {
+ target.configureLibrary()
+ target.configureDetektForNonUiModule()
+ }
+}
diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryHiltConvention.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryHiltConvention.kt
new file mode 100644
index 0000000..22f98ba
--- /dev/null
+++ b/build-logic/convention/src/main/kotlin/AndroidLibraryHiltConvention.kt
@@ -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 {
+ override fun apply(target: Project) {
+ with(target) {
+ with(pluginManager) {
+ apply("com.google.devtools.ksp")
+ apply("dagger.hilt.android.plugin")
+ }
+
+ // android block
+ extensions.configure {
+ configureHilt()
+ }
+ }
+ }
+}
diff --git a/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureAndroidLint.kt b/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureAndroidLint.kt
new file mode 100644
index 0000000..207d943
--- /dev/null
+++ b/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureAndroidLint.kt
@@ -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
+ }
+ }
+}
diff --git a/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureCompose.kt b/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureCompose.kt
new file mode 100644
index 0000000..b26fe63
--- /dev/null
+++ b/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureCompose.kt
@@ -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().named("libs")
+
+ commonExtension.apply {
+ buildFeatures {
+ compose = true
+ }
+ }
+
+ extensions.configure {
+ 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())
+ }
+}
diff --git a/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureDetekt.kt b/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureDetekt.kt
new file mode 100644
index 0000000..1765f1b
--- /dev/null
+++ b/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureDetekt.kt
@@ -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().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().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 {
+ 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
diff --git a/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureFlavors.kt b/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureFlavors.kt
new file mode 100644
index 0000000..2c1e149
--- /dev/null
+++ b/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureFlavors.kt
@@ -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
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureGradleManagedDevices.kt b/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureGradleManagedDevices.kt
new file mode 100644
index 0000000..1a342cc
--- /dev/null
+++ b/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureGradleManagedDevices.kt
@@ -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")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureHilt.kt b/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureHilt.kt
new file mode 100644
index 0000000..50e7aa6
--- /dev/null
+++ b/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureHilt.kt
@@ -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().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())
+ }
+}
diff --git a/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureInstrumentation.kt b/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureInstrumentation.kt
new file mode 100644
index 0000000..d2b53c3
--- /dev/null
+++ b/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureInstrumentation.kt
@@ -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().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())
+ }
+}
diff --git a/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureJacoco.kt b/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureJacoco.kt
new file mode 100644
index 0000000..00d41f0
--- /dev/null
+++ b/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureJacoco.kt
@@ -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().named("libs")
+
+ configure {
+ 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().configureEach {
+ configure {
+ // 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()
+}
diff --git a/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureKotlinAndroid.kt b/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureKotlinAndroid.kt
new file mode 100644
index 0000000..97902d1
--- /dev/null
+++ b/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureKotlinAndroid.kt
@@ -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().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()) {
+ 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())
+ }
+}
diff --git a/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureLibrary.kt b/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureLibrary.kt
new file mode 100644
index 0000000..0f3b000
--- /dev/null
+++ b/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureLibrary.kt
@@ -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().named("libs")
+ val targetSdk = libs.findVersion("targetSdk").get().toString().toInt()
+
+ // android block
+ extensions.configure {
+ 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()
+// configureJacoco(extension)
+ configureDetektDependencies()
+ //configureSonar()
+ configureUnitTests()
+
+ extensions.configure {
+ //explicitApi()
+ }
+}
\ No newline at end of file
diff --git a/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureSonar.kt b/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureSonar.kt
new file mode 100644
index 0000000..d4abe5e
--- /dev/null
+++ b/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureSonar.kt
@@ -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 {
+// 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")
+// }
+// }
+}
diff --git a/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureUnitTests.kt b/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureUnitTests.kt
new file mode 100644
index 0000000..3d03ac4
--- /dev/null
+++ b/build-logic/convention/src/main/kotlin/dev/adriankuta/partymania/ConfigureUnitTests.kt
@@ -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().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())
+ }
+}
diff --git a/build-logic/gradle.properties b/build-logic/gradle.properties
new file mode 100644
index 0000000..1c9073e
--- /dev/null
+++ b/build-logic/gradle.properties
@@ -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
diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts
new file mode 100644
index 0000000..de9224e
--- /dev/null
+++ b/build-logic/settings.gradle.kts
@@ -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")
diff --git a/build.gradle.kts b/build.gradle.kts
index 2e9a2d1..c816b51 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -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
}
\ No newline at end of file
diff --git a/core/util/build.gradle.kts b/core/util/build.gradle.kts
new file mode 100644
index 0000000..f0a2ff2
--- /dev/null
+++ b/core/util/build.gradle.kts
@@ -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 {
+
+}
\ No newline at end of file
diff --git a/core/util/config/detekt/detekt.yml b/core/util/config/detekt/detekt.yml
new file mode 100644
index 0000000..809b757
--- /dev/null
+++ b/core/util/config/detekt/detekt.yml
@@ -0,0 +1,10 @@
+# Deviations from defaults
+formatting:
+ TrailingCommaOnCallSite:
+ active: true
+ autoCorrect: true
+ useTrailingCommaOnCallSite: true
+ TrailingCommaOnDeclarationSite:
+ active: true
+ autoCorrect: true
+ useTrailingCommaOnDeclarationSite: true
\ No newline at end of file
diff --git a/core/util/lint-baseline.xml b/core/util/lint-baseline.xml
new file mode 100644
index 0000000..5e74404
--- /dev/null
+++ b/core/util/lint-baseline.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
diff --git a/core/util/src/main/kotlin/dev/adriankuta/flights/core/util/CoroutinesModule.kt b/core/util/src/main/kotlin/dev/adriankuta/flights/core/util/CoroutinesModule.kt
new file mode 100644
index 0000000..ba3b84b
--- /dev/null
+++ b/core/util/src/main/kotlin/dev/adriankuta/flights/core/util/CoroutinesModule.kt
@@ -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)
+}
diff --git a/domain/types/build.gradle.kts b/domain/types/build.gradle.kts
new file mode 100644
index 0000000..39262e7
--- /dev/null
+++ b/domain/types/build.gradle.kts
@@ -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)
+}
\ No newline at end of file
diff --git a/domain/types/config/detekt/detekt.yml b/domain/types/config/detekt/detekt.yml
new file mode 100644
index 0000000..809b757
--- /dev/null
+++ b/domain/types/config/detekt/detekt.yml
@@ -0,0 +1,10 @@
+# Deviations from defaults
+formatting:
+ TrailingCommaOnCallSite:
+ active: true
+ autoCorrect: true
+ useTrailingCommaOnCallSite: true
+ TrailingCommaOnDeclarationSite:
+ active: true
+ autoCorrect: true
+ useTrailingCommaOnDeclarationSite: true
\ No newline at end of file
diff --git a/domain/types/lint-baseline.xml b/domain/types/lint-baseline.xml
new file mode 100644
index 0000000..5e74404
--- /dev/null
+++ b/domain/types/lint-baseline.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
diff --git a/domain/types/src/main/kotlin/dev/adriankuta/flights/domain/types/Character.kt b/domain/types/src/main/kotlin/dev/adriankuta/flights/domain/types/Character.kt
new file mode 100644
index 0000000..0b60237
--- /dev/null
+++ b/domain/types/src/main/kotlin/dev/adriankuta/flights/domain/types/Character.kt
@@ -0,0 +1,6 @@
+package dev.adriankuta.flights.domain.types
+
+data class Character(
+ val name: String,
+ val category: String? = null,
+)
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 0000000..6b11753
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -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" }
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 5c145cd..6d3ad2c 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -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
diff --git a/model/data/api/build.gradle.kts b/model/data/api/build.gradle.kts
new file mode 100644
index 0000000..e4f20f4
--- /dev/null
+++ b/model/data/api/build.gradle.kts
@@ -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)
+}
diff --git a/model/data/room/build.gradle.kts b/model/data/room/build.gradle.kts
new file mode 100644
index 0000000..4114277
--- /dev/null
+++ b/model/data/room/build.gradle.kts
@@ -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)
+}
\ No newline at end of file
diff --git a/model/data/room/config/detekt/detekt.yml b/model/data/room/config/detekt/detekt.yml
new file mode 100644
index 0000000..809b757
--- /dev/null
+++ b/model/data/room/config/detekt/detekt.yml
@@ -0,0 +1,10 @@
+# Deviations from defaults
+formatting:
+ TrailingCommaOnCallSite:
+ active: true
+ autoCorrect: true
+ useTrailingCommaOnCallSite: true
+ TrailingCommaOnDeclarationSite:
+ active: true
+ autoCorrect: true
+ useTrailingCommaOnDeclarationSite: true
\ No newline at end of file
diff --git a/model/data/room/lint-baseline.xml b/model/data/room/lint-baseline.xml
new file mode 100644
index 0000000..5e74404
--- /dev/null
+++ b/model/data/room/lint-baseline.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
diff --git a/model/data/simple/build.gradle.kts b/model/data/simple/build.gradle.kts
new file mode 100644
index 0000000..8f55f64
--- /dev/null
+++ b/model/data/simple/build.gradle.kts
@@ -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)
+}
diff --git a/model/data/simple/config/detekt/detekt.yml b/model/data/simple/config/detekt/detekt.yml
new file mode 100644
index 0000000..809b757
--- /dev/null
+++ b/model/data/simple/config/detekt/detekt.yml
@@ -0,0 +1,10 @@
+# Deviations from defaults
+formatting:
+ TrailingCommaOnCallSite:
+ active: true
+ autoCorrect: true
+ useTrailingCommaOnCallSite: true
+ TrailingCommaOnDeclarationSite:
+ active: true
+ autoCorrect: true
+ useTrailingCommaOnDeclarationSite: true
\ No newline at end of file
diff --git a/model/data/simple/lint-baseline.xml b/model/data/simple/lint-baseline.xml
new file mode 100644
index 0000000..5e74404
--- /dev/null
+++ b/model/data/simple/lint-baseline.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
diff --git a/model/data/simple/src/main/assets/characters.json b/model/data/simple/src/main/assets/characters.json
new file mode 100644
index 0000000..0da9f6b
--- /dev/null
+++ b/model/data/simple/src/main/assets/characters.json
@@ -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"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/model/repository/build.gradle.kts b/model/repository/build.gradle.kts
new file mode 100644
index 0000000..f339b47
--- /dev/null
+++ b/model/repository/build.gradle.kts
@@ -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")
+}
diff --git a/model/repository/config/detekt/detekt.yml b/model/repository/config/detekt/detekt.yml
new file mode 100644
index 0000000..809b757
--- /dev/null
+++ b/model/repository/config/detekt/detekt.yml
@@ -0,0 +1,10 @@
+# Deviations from defaults
+formatting:
+ TrailingCommaOnCallSite:
+ active: true
+ autoCorrect: true
+ useTrailingCommaOnCallSite: true
+ TrailingCommaOnDeclarationSite:
+ active: true
+ autoCorrect: true
+ useTrailingCommaOnDeclarationSite: true
\ No newline at end of file
diff --git a/model/repository/lint-baseline.xml b/model/repository/lint-baseline.xml
new file mode 100644
index 0000000..5e74404
--- /dev/null
+++ b/model/repository/lint-baseline.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 5b2965b..f2496d9 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,6 +1,5 @@
-rootProject.name = "AndroidChallenge"
-
pluginManagement {
+ includeBuild("build-logic")
repositories {
google()
mavenCentral()
@@ -8,10 +7,29 @@ pluginManagement {
}
}
dependencyResolutionManagement {
+ // If the issue below gets fixed then the repositories blocks can come out the project modules and this can go back
+ // to being RepositoriesMode.FAIL_ON_PROJECT_REPOS
+ // https://github.com/GoogleCloudPlatform/artifact-registry-maven-tools/issues/71
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
+
+gradle.startParameter.excludedTaskNames.addAll(listOf(":build-logic:convention:testClasses"))
+
+rootProject.name = "Flights"
+
+enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
include(":app")
+include(":core:util")
+include(":domain:types")
+include(":model:data:api")
+include(":model:data:room")
+include(":model:data:simple")
+include(":model:datasource:characters")
+include(":model:repository")
+include(":ui:designsystem")
+include(":ui:home")
+include(":ui:sharedui")
diff --git a/ui/designsystem/build.gradle.kts b/ui/designsystem/build.gradle.kts
new file mode 100644
index 0000000..e3bb600
--- /dev/null
+++ b/ui/designsystem/build.gradle.kts
@@ -0,0 +1,29 @@
+/*
+ * 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 {
+ alias(libs.plugins.flights.android.library.compose)
+}
+
+android {
+ defaultConfig {
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+ namespace = "dev.adriankuta.flights.ui.designsystem"
+}
+
+dependencies {
+ implementation(libs.androidx.ui.text.google.fonts)
+}
diff --git a/ui/designsystem/config/detekt/detekt.yml b/ui/designsystem/config/detekt/detekt.yml
new file mode 100644
index 0000000..ab95a32
--- /dev/null
+++ b/ui/designsystem/config/detekt/detekt.yml
@@ -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
\ No newline at end of file
diff --git a/ui/designsystem/lint-baseline.xml b/ui/designsystem/lint-baseline.xml
new file mode 100644
index 0000000..cb01929
--- /dev/null
+++ b/ui/designsystem/lint-baseline.xml
@@ -0,0 +1,403 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/designsystem/src/main/kotlin/dev/adriankuta/flights/ui/designsystem/theme/Color.kt b/ui/designsystem/src/main/kotlin/dev/adriankuta/flights/ui/designsystem/theme/Color.kt
new file mode 100644
index 0000000..1378ca5
--- /dev/null
+++ b/ui/designsystem/src/main/kotlin/dev/adriankuta/flights/ui/designsystem/theme/Color.kt
@@ -0,0 +1,76 @@
+package dev.adriankuta.flights.ui.designsystem.theme
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.luminance
+
+@Composable
+fun contrastiveTo(color: Color): Color = if (color.luminance() < 0.5) {
+ Color.White
+} else {
+ Color.Black
+}
+
+val md_theme_light_primary = Color(0xFF825500)
+val md_theme_light_onPrimary = Color(0xFFFFFFFF)
+val md_theme_light_primaryContainer = Color(0xFFFFDDB3)
+val md_theme_light_onPrimaryContainer = Color(0xFF291800)
+val md_theme_light_secondary = Color(0xFF6F5B40)
+val md_theme_light_onSecondary = Color(0xFFFFFFFF)
+val md_theme_light_secondaryContainer = Color(0xFFFBDEBC)
+val md_theme_light_onSecondaryContainer = Color(0xFF271904)
+val md_theme_light_tertiary = Color(0xFF51643F)
+val md_theme_light_onTertiary = Color(0xFFFFFFFF)
+val md_theme_light_tertiaryContainer = Color(0xFFD4EABB)
+val md_theme_light_onTertiaryContainer = Color(0xFF102004)
+val md_theme_light_error = Color(0xFFBA1A1A)
+val md_theme_light_errorContainer = Color(0xFFFFDAD6)
+val md_theme_light_onError = Color(0xFFFFFFFF)
+val md_theme_light_onErrorContainer = Color(0xFF410002)
+val md_theme_light_background = Color(0xFFFFFBFF)
+val md_theme_light_onBackground = Color(0xFF1F1B16)
+val md_theme_light_surface = Color(0xFFFFFBFF)
+val md_theme_light_onSurface = Color(0xFF1F1B16)
+val md_theme_light_surfaceVariant = Color(0xFFF0E0CF)
+val md_theme_light_onSurfaceVariant = Color(0xFF4F4539)
+val md_theme_light_outline = Color(0xFF817567)
+val md_theme_light_inverseOnSurface = Color(0xFFF9EFE7)
+val md_theme_light_inverseSurface = Color(0xFF34302A)
+val md_theme_light_inversePrimary = Color(0xFFFFB951)
+val md_theme_light_shadow = Color(0xFF000000)
+val md_theme_light_surfaceTint = Color(0xFF825500)
+val md_theme_light_outlineVariant = Color(0xFFD3C4B4)
+val md_theme_light_scrim = Color(0xFF000000)
+
+val md_theme_dark_primary = Color(0xFFFFB951)
+val md_theme_dark_onPrimary = Color(0xFF452B00)
+val md_theme_dark_primaryContainer = Color(0xFF633F00)
+val md_theme_dark_onPrimaryContainer = Color(0xFFFFDDB3)
+val md_theme_dark_secondary = Color(0xFFDDC2A1)
+val md_theme_dark_onSecondary = Color(0xFF3E2D16)
+val md_theme_dark_secondaryContainer = Color(0xFF56442A)
+val md_theme_dark_onSecondaryContainer = Color(0xFFFBDEBC)
+val md_theme_dark_tertiary = Color(0xFFB8CEA1)
+val md_theme_dark_onTertiary = Color(0xFF243515)
+val md_theme_dark_tertiaryContainer = Color(0xFF3A4C2A)
+val md_theme_dark_onTertiaryContainer = Color(0xFFD4EABB)
+val md_theme_dark_error = Color(0xFFFFB4AB)
+val md_theme_dark_errorContainer = Color(0xFF93000A)
+val md_theme_dark_onError = Color(0xFF690005)
+val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
+val md_theme_dark_background = Color(0xFF1F1B16)
+val md_theme_dark_onBackground = Color(0xFFEAE1D9)
+val md_theme_dark_surface = Color(0xFF1F1B16)
+val md_theme_dark_onSurface = Color(0xFFEAE1D9)
+val md_theme_dark_surfaceVariant = Color(0xFF4F4539)
+val md_theme_dark_onSurfaceVariant = Color(0xFFD3C4B4)
+val md_theme_dark_outline = Color(0xFF9C8F80)
+val md_theme_dark_inverseOnSurface = Color(0xFF1F1B16)
+val md_theme_dark_inverseSurface = Color(0xFFEAE1D9)
+val md_theme_dark_inversePrimary = Color(0xFF825500)
+val md_theme_dark_shadow = Color(0xFF000000)
+val md_theme_dark_surfaceTint = Color(0xFFFFB951)
+val md_theme_dark_outlineVariant = Color(0xFF4F4539)
+val md_theme_dark_scrim = Color(0xFF000000)
+
+val seed = Color(0xFF825500)
diff --git a/ui/designsystem/src/main/kotlin/dev/adriankuta/flights/ui/designsystem/theme/Elevation.kt b/ui/designsystem/src/main/kotlin/dev/adriankuta/flights/ui/designsystem/theme/Elevation.kt
new file mode 100644
index 0000000..9326de1
--- /dev/null
+++ b/ui/designsystem/src/main/kotlin/dev/adriankuta/flights/ui/designsystem/theme/Elevation.kt
@@ -0,0 +1,18 @@
+package dev.adriankuta.flights.ui.designsystem.theme
+
+import androidx.compose.ui.unit.dp
+
+object Elevation {
+ val AppBar = ElevationTokens.Level3
+ val Surface = ElevationTokens.Level2
+ val Card = ElevationTokens.Level2
+}
+
+object ElevationTokens {
+ val Level0 = 0.0.dp
+ val Level1 = 1.0.dp
+ val Level2 = 3.0.dp
+ val Level3 = 6.0.dp
+ val Level4 = 8.0.dp
+ val Level5 = 12.0.dp
+}
diff --git a/ui/designsystem/src/main/kotlin/dev/adriankuta/flights/ui/designsystem/theme/PreviewDevices.kt b/ui/designsystem/src/main/kotlin/dev/adriankuta/flights/ui/designsystem/theme/PreviewDevices.kt
new file mode 100644
index 0000000..4af3c03
--- /dev/null
+++ b/ui/designsystem/src/main/kotlin/dev/adriankuta/flights/ui/designsystem/theme/PreviewDevices.kt
@@ -0,0 +1,9 @@
+package dev.adriankuta.flights.ui.designsystem.theme
+
+import androidx.compose.ui.tooling.preview.Preview
+
+@Preview(name = "phone", device = "spec:shape=Normal,width=360,height=640,unit=dp,dpi=480")
+@Preview(name = "landscape", device = "spec:shape=Normal,width=640,height=360,unit=dp,dpi=480")
+@Preview(name = "foldable", device = "spec:shape=Normal,width=673,height=841,unit=dp,dpi=480")
+@Preview(name = "tablet", device = "spec:shape=Normal,width=1280,height=800,unit=dp,dpi=480")
+annotation class PreviewDevices
diff --git a/ui/designsystem/src/main/kotlin/dev/adriankuta/flights/ui/designsystem/theme/Theme.kt b/ui/designsystem/src/main/kotlin/dev/adriankuta/flights/ui/designsystem/theme/Theme.kt
new file mode 100644
index 0000000..19330ff
--- /dev/null
+++ b/ui/designsystem/src/main/kotlin/dev/adriankuta/flights/ui/designsystem/theme/Theme.kt
@@ -0,0 +1,110 @@
+package dev.adriankuta.flights.ui.designsystem.theme
+
+import android.os.Build
+import androidx.annotation.ChecksSdkIntAtLeast
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+
+private val LightColors = lightColorScheme(
+ primary = md_theme_light_primary,
+ onPrimary = md_theme_light_onPrimary,
+ primaryContainer = md_theme_light_primaryContainer,
+ onPrimaryContainer = md_theme_light_onPrimaryContainer,
+ secondary = md_theme_light_secondary,
+ onSecondary = md_theme_light_onSecondary,
+ secondaryContainer = md_theme_light_secondaryContainer,
+ onSecondaryContainer = md_theme_light_onSecondaryContainer,
+ tertiary = md_theme_light_tertiary,
+ onTertiary = md_theme_light_onTertiary,
+ tertiaryContainer = md_theme_light_tertiaryContainer,
+ onTertiaryContainer = md_theme_light_onTertiaryContainer,
+ error = md_theme_light_error,
+ errorContainer = md_theme_light_errorContainer,
+ onError = md_theme_light_onError,
+ onErrorContainer = md_theme_light_onErrorContainer,
+ background = md_theme_light_background,
+ onBackground = md_theme_light_onBackground,
+ surface = md_theme_light_surface,
+ onSurface = md_theme_light_onSurface,
+ surfaceVariant = md_theme_light_surfaceVariant,
+ onSurfaceVariant = md_theme_light_onSurfaceVariant,
+ outline = md_theme_light_outline,
+ inverseOnSurface = md_theme_light_inverseOnSurface,
+ inverseSurface = md_theme_light_inverseSurface,
+ inversePrimary = md_theme_light_inversePrimary,
+ surfaceTint = md_theme_light_surfaceTint,
+ outlineVariant = md_theme_light_outlineVariant,
+ scrim = md_theme_light_scrim,
+)
+private val DarkColors = darkColorScheme(
+ primary = md_theme_dark_primary,
+ onPrimary = md_theme_dark_onPrimary,
+ primaryContainer = md_theme_dark_primaryContainer,
+ onPrimaryContainer = md_theme_dark_onPrimaryContainer,
+ secondary = md_theme_dark_secondary,
+ onSecondary = md_theme_dark_onSecondary,
+ secondaryContainer = md_theme_dark_secondaryContainer,
+ onSecondaryContainer = md_theme_dark_onSecondaryContainer,
+ tertiary = md_theme_dark_tertiary,
+ onTertiary = md_theme_dark_onTertiary,
+ tertiaryContainer = md_theme_dark_tertiaryContainer,
+ onTertiaryContainer = md_theme_dark_onTertiaryContainer,
+ error = md_theme_dark_error,
+ errorContainer = md_theme_dark_errorContainer,
+ onError = md_theme_dark_onError,
+ onErrorContainer = md_theme_dark_onErrorContainer,
+ background = md_theme_dark_background,
+ onBackground = md_theme_dark_onBackground,
+ surface = md_theme_dark_surface,
+ onSurface = md_theme_dark_onSurface,
+ surfaceVariant = md_theme_dark_surfaceVariant,
+ onSurfaceVariant = md_theme_dark_onSurfaceVariant,
+ outline = md_theme_dark_outline,
+ inverseOnSurface = md_theme_dark_inverseOnSurface,
+ inverseSurface = md_theme_dark_inverseSurface,
+ inversePrimary = md_theme_dark_inversePrimary,
+ surfaceTint = md_theme_dark_surfaceTint,
+ outlineVariant = md_theme_dark_outlineVariant,
+ scrim = md_theme_dark_scrim,
+)
+
+@Composable
+fun FlightsTheme(
+ useDarkTheme: Boolean = isSystemInDarkTheme(),
+ content: @Composable () -> Unit,
+) {
+ val context = LocalContext.current
+ val colors = when {
+ (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) -> {
+ if (useDarkTheme) {
+ dynamicDarkColorScheme(context)
+ } else {
+ dynamicLightColorScheme(context)
+ }
+ }
+
+ useDarkTheme -> DarkColors
+ else -> LightColors
+ }
+
+ MaterialTheme(
+ colorScheme = colors,
+ typography = Typography,
+ ) {
+ CompositionLocalProvider(
+ LocalContentColor provides MaterialTheme.colorScheme.onBackground,
+ content = content,
+ )
+ }
+}
+
+@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.S)
+fun supportsDynamicTheming() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
diff --git a/ui/designsystem/src/main/kotlin/dev/adriankuta/flights/ui/designsystem/theme/Type.kt b/ui/designsystem/src/main/kotlin/dev/adriankuta/flights/ui/designsystem/theme/Type.kt
new file mode 100644
index 0000000..664af96
--- /dev/null
+++ b/ui/designsystem/src/main/kotlin/dev/adriankuta/flights/ui/designsystem/theme/Type.kt
@@ -0,0 +1,165 @@
+package dev.adriankuta.flights.ui.designsystem.theme
+
+import androidx.compose.material3.Typography
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.googlefonts.Font
+import androidx.compose.ui.text.googlefonts.GoogleFont
+import androidx.compose.ui.text.style.LineHeightStyle
+import androidx.compose.ui.text.style.LineHeightStyle.Alignment
+import androidx.compose.ui.text.style.LineHeightStyle.Trim
+import androidx.compose.ui.unit.sp
+import dev.adriankuta.flights.ui.designsystem.R
+
+val provider = GoogleFont.Provider(
+ providerAuthority = "com.google.android.gms.fonts",
+ providerPackage = "com.google.android.gms",
+ certificates = R.array.com_google_android_gms_fonts_certs,
+)
+val fontName = GoogleFont("Roboto")
+
+val fontFamily = FontFamily(
+ Font(googleFont = fontName, fontProvider = provider),
+ Font(googleFont = fontName, fontProvider = provider, weight = FontWeight.Medium),
+ Font(googleFont = fontName, fontProvider = provider, weight = FontWeight.SemiBold),
+ Font(googleFont = fontName, fontProvider = provider, weight = FontWeight.Bold),
+)
+
+// Set of Material typography styles to start with
+val Typography = Typography(
+
+ displayLarge = TextStyle(
+ fontFamily = fontFamily,
+ fontWeight = FontWeight.Normal,
+ fontSize = 57.sp,
+ lineHeight = 64.sp,
+ letterSpacing = (-0.25).sp,
+ ),
+ displayMedium = TextStyle(
+ fontFamily = fontFamily,
+ fontWeight = FontWeight.Normal,
+ fontSize = 45.sp,
+ lineHeight = 52.sp,
+ letterSpacing = 0.sp,
+ ),
+ displaySmall = TextStyle(
+ fontFamily = fontFamily,
+ fontWeight = FontWeight.Normal,
+ fontSize = 36.sp,
+ lineHeight = 44.sp,
+ letterSpacing = 0.sp,
+ ),
+ headlineLarge = TextStyle(
+ fontFamily = fontFamily,
+ fontWeight = FontWeight.Normal,
+ fontSize = 32.sp,
+ lineHeight = 40.sp,
+ letterSpacing = 0.sp,
+ ),
+ headlineMedium = TextStyle(
+ fontFamily = fontFamily,
+ fontWeight = FontWeight.Normal,
+ fontSize = 28.sp,
+ lineHeight = 36.sp,
+ letterSpacing = 0.sp,
+ ),
+ headlineSmall = TextStyle(
+ fontFamily = fontFamily,
+ fontWeight = FontWeight.Normal,
+ fontSize = 24.sp,
+ lineHeight = 32.sp,
+ letterSpacing = 0.sp,
+ lineHeightStyle = LineHeightStyle(
+ alignment = Alignment.Bottom,
+ trim = Trim.None,
+ ),
+ ),
+ titleLarge = TextStyle(
+ fontFamily = fontFamily,
+ fontWeight = FontWeight.Bold,
+ fontSize = 22.sp,
+ lineHeight = 28.sp,
+ letterSpacing = 0.sp,
+ lineHeightStyle = LineHeightStyle(
+ alignment = Alignment.Bottom,
+ trim = Trim.LastLineBottom,
+ ),
+ ),
+ titleMedium = TextStyle(
+ fontFamily = fontFamily,
+ fontWeight = FontWeight.Bold,
+ fontSize = 18.sp,
+ lineHeight = 24.sp,
+ letterSpacing = 0.1.sp,
+ ),
+ titleSmall = TextStyle(
+ fontFamily = fontFamily,
+ fontWeight = FontWeight.Medium,
+ fontSize = 14.sp,
+ lineHeight = 20.sp,
+ letterSpacing = 0.1.sp,
+ ),
+ // Default text style
+ bodyLarge = TextStyle(
+ fontFamily = fontFamily,
+ fontWeight = FontWeight.Normal,
+ fontSize = 16.sp,
+ lineHeight = 24.sp,
+ letterSpacing = 0.5.sp,
+ lineHeightStyle = LineHeightStyle(
+ alignment = Alignment.Center,
+ trim = Trim.None,
+ ),
+ ),
+ bodyMedium = TextStyle(
+ fontFamily = fontFamily,
+ fontWeight = FontWeight.Normal,
+ fontSize = 14.sp,
+ lineHeight = 20.sp,
+ letterSpacing = 0.25.sp,
+ ),
+ bodySmall = TextStyle(
+ fontFamily = fontFamily,
+ fontWeight = FontWeight.Normal,
+ fontSize = 12.sp,
+ lineHeight = 16.sp,
+ letterSpacing = 0.4.sp,
+ ),
+ // Used for Button
+ labelLarge = TextStyle(
+ fontFamily = fontFamily,
+ fontWeight = FontWeight.Medium,
+ fontSize = 14.sp,
+ lineHeight = 20.sp,
+ letterSpacing = 0.1.sp,
+ lineHeightStyle = LineHeightStyle(
+ alignment = Alignment.Center,
+ trim = Trim.LastLineBottom,
+ ),
+ ),
+ // Used for Navigation items
+ labelMedium = TextStyle(
+ fontFamily = fontFamily,
+ fontWeight = FontWeight.Medium,
+ fontSize = 12.sp,
+ lineHeight = 16.sp,
+ letterSpacing = 0.5.sp,
+ lineHeightStyle = LineHeightStyle(
+ alignment = Alignment.Center,
+ trim = Trim.LastLineBottom,
+ ),
+ ),
+ // Used for Tag
+ labelSmall = TextStyle(
+ fontFamily = fontFamily,
+ fontWeight = FontWeight.Medium,
+ fontSize = 10.sp,
+ lineHeight = 14.sp,
+ letterSpacing = 0.sp,
+ lineHeightStyle = LineHeightStyle(
+ alignment = Alignment.Center,
+ trim = Trim.LastLineBottom,
+ ),
+ ),
+)
diff --git a/ui/designsystem/src/main/res/values/font_certs.xml b/ui/designsystem/src/main/res/values/font_certs.xml
new file mode 100644
index 0000000..5df6c0f
--- /dev/null
+++ b/ui/designsystem/src/main/res/values/font_certs.xml
@@ -0,0 +1,31 @@
+
+
+
+ - @array/com_google_android_gms_fonts_certs_dev
+ - @array/com_google_android_gms_fonts_certs_prod
+
+
+ -
+ MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs=
+
+
+
+ -
+ MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK
+
+
+
diff --git a/ui/home/build.gradle.kts b/ui/home/build.gradle.kts
new file mode 100644
index 0000000..5bc10dc
--- /dev/null
+++ b/ui/home/build.gradle.kts
@@ -0,0 +1,16 @@
+plugins {
+ alias(libs.plugins.flights.android.library.compose)
+ alias(libs.plugins.flights.android.library.hilt)
+ alias(libs.plugins.kotlin.serialization)
+}
+
+android {
+ namespace = "dev.adriankuta.flights.ui.home"
+}
+
+dependencies {
+ implementation(projects.ui.designsystem)
+
+ implementation(libs.androidx.hilt.navigation.compose)
+ implementation(libs.timber)
+}
\ No newline at end of file
diff --git a/ui/home/config/detekt/detekt.yml b/ui/home/config/detekt/detekt.yml
new file mode 100644
index 0000000..1eff3a4
--- /dev/null
+++ b/ui/home/config/detekt/detekt.yml
@@ -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
diff --git a/ui/home/lint-baseline.xml b/ui/home/lint-baseline.xml
new file mode 100644
index 0000000..16c6906
--- /dev/null
+++ b/ui/home/lint-baseline.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/home/src/main/kotlin/dev/adriankuta/flights/ui/home/HomeScreen.kt b/ui/home/src/main/kotlin/dev/adriankuta/flights/ui/home/HomeScreen.kt
new file mode 100644
index 0000000..b4d2394
--- /dev/null
+++ b/ui/home/src/main/kotlin/dev/adriankuta/flights/ui/home/HomeScreen.kt
@@ -0,0 +1,20 @@
+package dev.adriankuta.flights.ui.home
+
+import androidx.compose.runtime.Composable
+import androidx.hilt.navigation.compose.hiltViewModel
+import dev.adriankuta.flights.ui.designsystem.theme.FlightsTheme
+import dev.adriankuta.flights.ui.designsystem.theme.PreviewDevices
+
+@Composable
+internal fun HomeScreen(
+ viewModel: HomeScreenViewModel = hiltViewModel(),
+) {
+}
+
+@PreviewDevices
+@Composable
+private fun HomeScreenPreview() {
+ FlightsTheme {
+ HomeScreen()
+ }
+}
diff --git a/ui/home/src/main/kotlin/dev/adriankuta/flights/ui/home/HomeScreenViewModel.kt b/ui/home/src/main/kotlin/dev/adriankuta/flights/ui/home/HomeScreenViewModel.kt
new file mode 100644
index 0000000..03a810b
--- /dev/null
+++ b/ui/home/src/main/kotlin/dev/adriankuta/flights/ui/home/HomeScreenViewModel.kt
@@ -0,0 +1,8 @@
+package dev.adriankuta.flights.ui.home
+
+import androidx.lifecycle.ViewModel
+import dagger.hilt.android.lifecycle.HiltViewModel
+import javax.inject.Inject
+
+@HiltViewModel
+class HomeScreenViewModel @Inject constructor() : ViewModel()
diff --git a/ui/home/src/main/kotlin/dev/adriankuta/flights/ui/home/navigation/HomeNavigation.kt b/ui/home/src/main/kotlin/dev/adriankuta/flights/ui/home/navigation/HomeNavigation.kt
new file mode 100644
index 0000000..013371e
--- /dev/null
+++ b/ui/home/src/main/kotlin/dev/adriankuta/flights/ui/home/navigation/HomeNavigation.kt
@@ -0,0 +1,17 @@
+@file:Suppress("MatchingDeclarationName")
+
+package dev.adriankuta.flights.ui.home.navigation
+
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.compose.composable
+import dev.adriankuta.flights.ui.home.HomeScreen
+import kotlinx.serialization.Serializable
+
+@Serializable
+data object HomeRoute
+
+fun NavGraphBuilder.homeScreen() {
+ composable {
+ HomeScreen()
+ }
+}
diff --git a/ui/sharedui/build.gradle.kts b/ui/sharedui/build.gradle.kts
new file mode 100644
index 0000000..110ec88
--- /dev/null
+++ b/ui/sharedui/build.gradle.kts
@@ -0,0 +1,14 @@
+plugins {
+ alias(libs.plugins.flights.android.library.compose)
+ alias(libs.plugins.flights.android.library.hilt)
+}
+
+android {
+ namespace = "dev.adriankuta.flights.ui.sharedui"
+}
+
+dependencies {
+ implementation(projects.ui.designsystem)
+
+ implementation(libs.timber)
+}
\ No newline at end of file
diff --git a/ui/sharedui/config/detekt/detekt.yml b/ui/sharedui/config/detekt/detekt.yml
new file mode 100644
index 0000000..1eff3a4
--- /dev/null
+++ b/ui/sharedui/config/detekt/detekt.yml
@@ -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
diff --git a/ui/sharedui/lint-baseline.xml b/ui/sharedui/lint-baseline.xml
new file mode 100644
index 0000000..16c6906
--- /dev/null
+++ b/ui/sharedui/lint-baseline.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/sharedui/src/main/kotlin/dev/adriankuta/flights/ui/sharedui/Counter.kt b/ui/sharedui/src/main/kotlin/dev/adriankuta/flights/ui/sharedui/Counter.kt
new file mode 100644
index 0000000..6d4d551
--- /dev/null
+++ b/ui/sharedui/src/main/kotlin/dev/adriankuta/flights/ui/sharedui/Counter.kt
@@ -0,0 +1,127 @@
+package dev.adriankuta.flights.ui.sharedui
+
+import android.view.HapticFeedbackConstants
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.SizeTransform
+import androidx.compose.animation.scaleIn
+import androidx.compose.animation.scaleOut
+import androidx.compose.animation.slideInHorizontally
+import androidx.compose.animation.slideOutHorizontally
+import androidx.compose.animation.togetherWith
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.IntrinsicSize
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.Button
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import dev.adriankuta.flights.ui.designsystem.theme.FlightsTheme
+
+@Suppress("LongMethod")
+@Composable
+fun Counter(
+ value: Int,
+ onCountChange: (diff: Int) -> Unit,
+ modifier: Modifier = Modifier,
+ label: String? = null,
+) {
+ val view = LocalView.current
+
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center,
+ modifier = modifier.width(IntrinsicSize.Min),
+ ) {
+ if (label != null) {
+ Text(
+ text = label,
+ style = MaterialTheme.typography.labelLarge,
+ )
+ }
+ AnimatedContent(
+ modifier = Modifier,
+ targetState = value,
+ label = "Questions counter animation",
+ transitionSpec = {
+ if (targetState > initialState) {
+ scaleIn() + slideInHorizontally { it / 2 } togetherWith
+ scaleOut() + slideOutHorizontally { -it / 2 } using
+ SizeTransform(clip = false)
+ } else {
+ scaleIn() + slideInHorizontally { -it / 2 } togetherWith
+ scaleOut() + slideOutHorizontally { it / 2 } using
+ SizeTransform(clip = false)
+ }
+ },
+ ) { state ->
+ Text(
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Center,
+ text = "$state",
+ style = MaterialTheme.typography.displayMedium,
+ )
+ }
+
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ Button(
+ modifier = Modifier.weight(1f),
+ onClick = {
+ view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
+ onCountChange(-1)
+ },
+ shape = MaterialTheme.shapes.medium,
+ enabled = value > 0,
+ ) {
+ Text(
+ text = "-",
+ style = MaterialTheme.typography.labelLarge,
+ )
+ }
+ Button(
+ modifier = Modifier.weight(1f),
+ onClick = {
+ view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
+ onCountChange(1)
+ },
+ shape = MaterialTheme.shapes.medium,
+ enabled = value < 20,
+ ) {
+ Text(
+ text = "+",
+ style = MaterialTheme.typography.labelLarge,
+ )
+ }
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun CounterPreview() {
+ var tapCounter by remember {
+ mutableIntStateOf(20)
+ }
+ FlightsTheme {
+ Counter(
+ value = tapCounter,
+ label = "Taps",
+ onCountChange = { tapCounter += it },
+ )
+ }
+}