diff --git a/.gitignore b/.gitignore
index aa724b7..4832baa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,8 @@
.externalNativeBuild
.cxx
local.properties
+
+/.idea/misc.xml
+/.idea/gradle.xml
+/.idea/compiler.xml
+/.idea/.name
diff --git a/.idea/.gitignore b/.idea/.gitignore
index 26d3352..aff6f92 100644
--- a/.idea/.gitignore
+++ b/.idea/.gitignore
@@ -1,3 +1,4 @@
# Default ignored files
/shelf/
/workspace.xml
+/vcs.xml
diff --git a/.idea/.name b/.idea/.name
deleted file mode 100644
index aa39fe5..0000000
--- a/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-Android Test
\ No newline at end of file
diff --git a/.idea/appInsightsSettings.xml b/.idea/appInsightsSettings.xml
deleted file mode 100644
index 145bb1c..0000000
--- a/.idea/appInsightsSettings.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
deleted file mode 100644
index b589d56..0000000
--- a/.idea/compiler.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
deleted file mode 100644
index 0c0c338..0000000
--- a/.idea/deploymentTargetDropDown.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
deleted file mode 100644
index 9f47dfb..0000000
--- a/.idea/gradle.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
deleted file mode 100644
index 44ca2d9..0000000
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
deleted file mode 100644
index fdf8d99..0000000
--- a/.idea/kotlinc.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/migrations.xml b/.idea/migrations.xml
deleted file mode 100644
index f8051a6..0000000
--- a/.idea/migrations.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 8978d23..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 35eb1dd..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index cee76ae..57271a6 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,19 +1,18 @@
-@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
plugins {
- alias(libs.plugins.androidApplication)
- alias(libs.plugins.kotlinAndroid)
+ id("shindra.application")
+ id("shindra.application.compose")
+ id("shindra.hilt")
}
+
android {
- namespace = "com.example.androidtest"
- compileSdk = 34
+ namespace = "com.shindra.chargemap"
+
defaultConfig {
- applicationId = "com.example.androidtest"
- minSdk = 23
- targetSdk = 34
+ applicationId = "com.shindra.chargemap"
versionCode = 1
- versionName = "1.0"
+ versionName = "1.0.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
@@ -22,49 +21,51 @@ android {
}
buildTypes {
- release {
- isMinifyEnabled = false
+ val debug by getting {
+ applicationIdSuffix = ".debug"
+ }
+
+ val release by getting {
+ isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
+
+ // To publish on the Play store a private signing key is required, but to allow anyone
+ // who clones the code to sign and run the release variant, use the debug signing key.
+ // TODO: Abstract the signing configuration to a separate file to avoid hardcoding this.
+ //signingConfig = signingConfigs.getByName("debug")
}
}
- compileOptions {
- sourceCompatibility = JavaVersion.VERSION_1_8
- targetCompatibility = JavaVersion.VERSION_1_8
- }
- kotlinOptions {
- jvmTarget = "1.8"
- }
- buildFeatures {
- compose = true
- }
- composeOptions {
- kotlinCompilerExtensionVersion = "1.5.1"
- }
+
packaging {
resources {
- excludes += "/META-INF/{AL2.0,LGPL2.1}"
+ excludes.add("/META-INF/{AL2.0,LGPL2.1}")
}
}
}
dependencies {
+ implementation(project(":core:designsystem"))
+ implementation(project(":feature:planes"))
+ implementation(project(":feature:planeDetails"))
+
+ implementation(libs.androidx.activity.compose)
+ implementation(libs.androidx.appcompat)
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.androidx.compose.runtime)
+ implementation(libs.androidx.lifecycle.runtimeCompose)
+ implementation(libs.androidx.compose.material3.windowSizeClass)
+ implementation(libs.androidx.hilt.navigation.compose)
+ implementation(libs.androidx.navigation.compose)
+
+ implementation(libs.coil.kt)
+ implementation(libs.coil.kt.svg)
+
+ testImplementation(libs.junit4)
+ androidTestImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.test.espresso.core)
- implementation(libs.core.ktx)
- implementation(libs.lifecycle.runtime.ktx)
- implementation(libs.activity.compose)
- implementation(platform(libs.compose.bom))
- implementation(libs.ui)
- implementation(libs.ui.graphics)
- implementation(libs.ui.tooling.preview)
- implementation(libs.material3)
- testImplementation(libs.junit)
- androidTestImplementation(libs.androidx.test.ext.junit)
- androidTestImplementation(libs.espresso.core)
- androidTestImplementation(platform(libs.compose.bom))
- androidTestImplementation(libs.ui.test.junit4)
- debugImplementation(libs.ui.tooling)
- debugImplementation(libs.ui.test.manifest)
}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index 481bb43..ff59496 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.
+# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
diff --git a/app/src/androidTest/java/com/example/androidtest/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/shindra/chargemap/ExampleInstrumentedTest.kt
similarity index 84%
rename from app/src/androidTest/java/com/example/androidtest/ExampleInstrumentedTest.kt
rename to app/src/androidTest/java/com/shindra/chargemap/ExampleInstrumentedTest.kt
index faf9720..1320dad 100644
--- a/app/src/androidTest/java/com/example/androidtest/ExampleInstrumentedTest.kt
+++ b/app/src/androidTest/java/com/shindra/chargemap/ExampleInstrumentedTest.kt
@@ -1,4 +1,4 @@
-package com.example.androidtest
+package com.shindra.chargemap
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -19,6 +19,6 @@ class ExampleInstrumentedTest {
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
- assertEquals("com.example.androidtest", appContext.packageName)
+ assertEquals("com.shindra.chargemap", appContext.packageName)
}
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index fd88cc2..37242bc 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,21 +2,23 @@
+
+
+
+ android:theme="@style/Theme.ChargeMap">
diff --git a/app/src/main/java/com/example/androidtest/MainActivity.kt b/app/src/main/java/com/example/androidtest/MainActivity.kt
deleted file mode 100644
index 8fa2b80..0000000
--- a/app/src/main/java/com/example/androidtest/MainActivity.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-package com.example.androidtest
-
-import android.os.Bundle
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.tooling.preview.Preview
-import com.example.androidtest.ui.theme.AndroidTestTheme
-
-class MainActivity : ComponentActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContent {
- AndroidTestTheme {
- // A surface container using the 'background' color from the theme
- Surface(
- modifier = Modifier.fillMaxSize(),
- color = MaterialTheme.colorScheme.background
- ) {
- Greeting("Android")
- }
- }
- }
- }
-}
-
-@Composable
-fun Greeting(name: String, modifier: Modifier = Modifier) {
- Text(
- text = "Hello $name!",
- modifier = modifier
- )
-}
-
-@Preview(showBackground = true)
-@Composable
-fun GreetingPreview() {
- AndroidTestTheme {
- Greeting("Android")
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/androidtest/ui/theme/Color.kt b/app/src/main/java/com/example/androidtest/ui/theme/Color.kt
deleted file mode 100644
index 8a10bf4..0000000
--- a/app/src/main/java/com/example/androidtest/ui/theme/Color.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.example.androidtest.ui.theme
-
-import androidx.compose.ui.graphics.Color
-
-val Purple80 = Color(0xFFD0BCFF)
-val PurpleGrey80 = Color(0xFFCCC2DC)
-val Pink80 = Color(0xFFEFB8C8)
-
-val Purple40 = Color(0xFF6650a4)
-val PurpleGrey40 = Color(0xFF625b71)
-val Pink40 = Color(0xFF7D5260)
\ No newline at end of file
diff --git a/app/src/main/java/com/example/androidtest/ui/theme/Theme.kt b/app/src/main/java/com/example/androidtest/ui/theme/Theme.kt
deleted file mode 100644
index 027c97f..0000000
--- a/app/src/main/java/com/example/androidtest/ui/theme/Theme.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-package com.example.androidtest.ui.theme
-
-import android.app.Activity
-import android.os.Build
-import androidx.compose.foundation.isSystemInDarkTheme
-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.SideEffect
-import androidx.compose.ui.graphics.toArgb
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalView
-import androidx.core.view.WindowCompat
-
-private val DarkColorScheme = darkColorScheme(
- primary = Purple80,
- secondary = PurpleGrey80,
- tertiary = Pink80
-)
-
-private val LightColorScheme = lightColorScheme(
- primary = Purple40,
- secondary = PurpleGrey40,
- tertiary = Pink40
-
- /* Other default colors to override
- background = Color(0xFFFFFBFE),
- surface = Color(0xFFFFFBFE),
- onPrimary = Color.White,
- onSecondary = Color.White,
- onTertiary = Color.White,
- onBackground = Color(0xFF1C1B1F),
- onSurface = Color(0xFF1C1B1F),
- */
-)
-
-@Composable
-fun AndroidTestTheme(
- darkTheme: Boolean = isSystemInDarkTheme(),
- // Dynamic color is available on Android 12+
- dynamicColor: Boolean = true,
- content: @Composable () -> Unit
-) {
- val colorScheme = when {
- dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
- val context = LocalContext.current
- if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
- }
-
- darkTheme -> DarkColorScheme
- else -> LightColorScheme
- }
- val view = LocalView.current
- if (!view.isInEditMode) {
- SideEffect {
- val window = (view.context as Activity).window
- window.statusBarColor = colorScheme.primary.toArgb()
- WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
- }
- }
-
- MaterialTheme(
- colorScheme = colorScheme,
- typography = Typography,
- content = content
- )
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/shindra/chargemap/ChargeMapApplication.kt b/app/src/main/java/com/shindra/chargemap/ChargeMapApplication.kt
new file mode 100644
index 0000000..7c822eb
--- /dev/null
+++ b/app/src/main/java/com/shindra/chargemap/ChargeMapApplication.kt
@@ -0,0 +1,7 @@
+package com.shindra.chargemap
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class ChargeMapApplication : Application()
diff --git a/app/src/main/java/com/shindra/chargemap/MainActivity.kt b/app/src/main/java/com/shindra/chargemap/MainActivity.kt
new file mode 100644
index 0000000..200b1a4
--- /dev/null
+++ b/app/src/main/java/com/shindra/chargemap/MainActivity.kt
@@ -0,0 +1,20 @@
+package com.shindra.chargemap
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import com.shindra.chargemap.ui.ChargeMapApp
+import com.shindra.chargemap.core.designsystem.theme.ChargeMapTheme
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class MainActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ ChargeMapTheme {
+ ChargeMapApp()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/shindra/chargemap/navigation/ChargeMapNavhost.kt b/app/src/main/java/com/shindra/chargemap/navigation/ChargeMapNavhost.kt
new file mode 100644
index 0000000..4e4f7a5
--- /dev/null
+++ b/app/src/main/java/com/shindra/chargemap/navigation/ChargeMapNavhost.kt
@@ -0,0 +1,37 @@
+package com.shindra.chargemap.navigation
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import com.shindra.chargemap.feature.planedetails.navigation.navigateToPlaneDetails
+import com.shindra.chargemap.feature.planedetails.navigation.planeDetails
+import com.shindra.chargemap.feature.planes.navigation.planesScreen
+import com.shindra.chargemap.feature.planes.navigation.planesScreenRouteId
+
+
+@Composable
+fun ChargeMapNavHost(
+ navController: NavHostController,
+ modifier: Modifier = Modifier,
+ startDestination: String = planesScreenRouteId,
+ onTitleChange: (String) -> Unit
+) {
+ NavHost(
+ navController = navController,
+ startDestination = startDestination,
+ modifier = modifier,
+ ) {
+ planesScreen(
+ onTitleChange = onTitleChange,
+ onMassAndBalanceClick = { manufacturer, model ->
+ navController.navigateToPlaneDetails(
+ manufacturer = manufacturer,
+ model = model
+ )
+ }
+ )
+
+ planeDetails(onTitleChange)
+ }
+}
diff --git a/app/src/main/java/com/shindra/chargemap/ui/ChargeMapApp.kt b/app/src/main/java/com/shindra/chargemap/ui/ChargeMapApp.kt
new file mode 100644
index 0000000..6b99e40
--- /dev/null
+++ b/app/src/main/java/com/shindra/chargemap/ui/ChargeMapApp.kt
@@ -0,0 +1,73 @@
+package com.shindra.chargemap.ui
+
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material3.CenterAlignedTopAppBar
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.TopAppBarScrollBehavior
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import com.shindra.chargemap.navigation.ChargeMapNavHost
+
+@OptIn(
+ ExperimentalMaterial3Api::class,
+)
+@Composable
+fun ChargeMapApp(
+ appState: ChargeMapAppState = rememberChargeMapAppState()
+) {
+ var toolbarTitle by remember { mutableStateOf("") }
+
+ Scaffold(
+ topBar = {
+ Toolbar(
+ title = toolbarTitle,
+ hasNavUp = appState.shouldShowNavUp,
+ onNavUpClick = { appState.navigateUp() }
+ )
+
+ }
+ ) {
+ ChargeMapNavHost(
+ modifier = Modifier.padding(it),
+ navController = appState.navController,
+ onTitleChange = { newTitle ->
+ toolbarTitle = newTitle
+ }
+ )
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun Toolbar(
+ title: String,
+ hasNavUp: Boolean,
+ onNavUpClick: () -> Unit,
+ scrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
+) {
+ CenterAlignedTopAppBar(
+ title = { Text(text = title) },
+ navigationIcon = {
+ if (hasNavUp) {
+ IconButton(onClick = { onNavUpClick() }) {
+ Icon(
+ imageVector = Icons.Default.ArrowBack,
+ contentDescription = null
+ )
+ }
+ }
+ },
+ scrollBehavior = scrollBehavior
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/shindra/chargemap/ui/ChargeMapAppState.kt b/app/src/main/java/com/shindra/chargemap/ui/ChargeMapAppState.kt
new file mode 100644
index 0000000..b6f6b57
--- /dev/null
+++ b/app/src/main/java/com/shindra/chargemap/ui/ChargeMapAppState.kt
@@ -0,0 +1,34 @@
+package com.shindra.chargemap.ui
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.navigation.NavDestination
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.currentBackStackEntryAsState
+import androidx.navigation.compose.rememberNavController
+import com.shindra.chargemap.feature.planes.navigation.planesScreenRouteId
+
+@Composable
+fun rememberChargeMapAppState(
+ navController: NavHostController = rememberNavController()
+) = remember(navController) {
+ ChargeMapAppState(navController)
+}
+
+class ChargeMapAppState(val navController: NavHostController) {
+
+ val currentDestination: NavDestination?
+ @Composable get() = navController
+ .currentBackStackEntryAsState().value?.destination
+
+ val shouldShowNavUp: Boolean
+ @Composable get() {
+ val cDestination = currentDestination?.route ?: return false
+ return cDestination != planesScreenRouteId
+ }
+
+ fun navigateUp() {
+ navController.navigateUp()
+ }
+
+}
\ 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 6f3b755..eca70cf 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -2,5 +2,4 @@
-
\ 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 6f3b755..eca70cf 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
@@ -2,5 +2,4 @@
-
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ 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 fa43411..604714e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,3 @@
- Android Test
+ ChargeMap - test
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index b28fc40..7c21160 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/app/src/test/java/com/example/androidtest/ExampleUnitTest.kt b/app/src/test/java/com/shindra/chargemap/ExampleUnitTest.kt
similarity index 90%
rename from app/src/test/java/com/example/androidtest/ExampleUnitTest.kt
rename to app/src/test/java/com/shindra/chargemap/ExampleUnitTest.kt
index f0ca93e..29da69c 100644
--- a/app/src/test/java/com/example/androidtest/ExampleUnitTest.kt
+++ b/app/src/test/java/com/shindra/chargemap/ExampleUnitTest.kt
@@ -1,4 +1,4 @@
-package com.example.androidtest
+package com.chargemap
import org.junit.Test
diff --git a/build-logic/convention/.gitignore b/build-logic/convention/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/build-logic/convention/.gitignore
@@ -0,0 +1 @@
+/build
\ 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..0f54d3b
--- /dev/null
+++ b/build-logic/convention/build.gradle.kts
@@ -0,0 +1,44 @@
+plugins {
+ `kotlin-dsl`
+}
+
+group = "com.shindra.buildlogic"
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+}
+
+dependencies {
+ compileOnly(libs.android.gradlePlugin)
+ compileOnly(libs.kotlin.gradlePlugin)
+}
+
+gradlePlugin {
+ plugins {
+ register("androidApplicationCompose") {
+ id = "shindra.application.compose"
+ implementationClass = "AndroidApplicationComposeConventionPlugin"
+ }
+ register("androidApplication") {
+ id = "shindra.application"
+ implementationClass = "AndroidApplicationConventionPlugin"
+ }
+ register("androidLibraryCompose") {
+ id = "shindra.library.compose"
+ implementationClass = "AndroidLibraryComposeConventionPlugin"
+ }
+ register("androidLibrary") {
+ id = "shindra.library"
+ implementationClass = "AndroidLibraryConventionPlugin"
+ }
+ register("androidFeature") {
+ id = "shindra.feature"
+ implementationClass = "AndroidFeatureConventionPlugin"
+ }
+ register("androidHilt") {
+ id = "shindra.hilt"
+ implementationClass = "AndroidHiltConventionPlugin"
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-logic/convention/src/main/java/AndroidApplicationComposeConventionPlugin.kt b/build-logic/convention/src/main/java/AndroidApplicationComposeConventionPlugin.kt
new file mode 100644
index 0000000..6625a51
--- /dev/null
+++ b/build-logic/convention/src/main/java/AndroidApplicationComposeConventionPlugin.kt
@@ -0,0 +1,15 @@
+import com.android.build.api.dsl.ApplicationExtension
+import com.shindra.convention.configureAndroidCompose
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.kotlin.dsl.getByType
+
+class AndroidApplicationComposeConventionPlugin : Plugin {
+ override fun apply(target: Project) {
+ with(target) {
+ pluginManager.apply("com.android.application")
+ val extension = extensions.getByType()
+ configureAndroidCompose(extension)
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-logic/convention/src/main/java/AndroidApplicationConventionPlugin.kt b/build-logic/convention/src/main/java/AndroidApplicationConventionPlugin.kt
new file mode 100644
index 0000000..3b65cb2
--- /dev/null
+++ b/build-logic/convention/src/main/java/AndroidApplicationConventionPlugin.kt
@@ -0,0 +1,21 @@
+import com.android.build.api.dsl.ApplicationExtension
+import com.shindra.convention.configureKotlinAndroid
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.kotlin.dsl.configure
+
+class AndroidApplicationConventionPlugin : Plugin {
+ override fun apply(target: Project) {
+ with(target) {
+ with(pluginManager) {
+ apply("com.android.application")
+ apply("org.jetbrains.kotlin.android")
+ }
+
+ extensions.configure {
+ configureKotlinAndroid(this)
+ defaultConfig.targetSdk = 34
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-logic/convention/src/main/java/AndroidFeatureConventionPlugin.kt b/build-logic/convention/src/main/java/AndroidFeatureConventionPlugin.kt
new file mode 100644
index 0000000..cd40962
--- /dev/null
+++ b/build-logic/convention/src/main/java/AndroidFeatureConventionPlugin.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.gradle.LibraryExtension
+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
+
+class AndroidFeatureConventionPlugin : Plugin {
+ override fun apply(target: Project) {
+ with(target) {
+ pluginManager.apply {
+ apply("shindra.library")
+ apply("shindra.hilt")
+ }
+ extensions.configure {
+ defaultConfig {
+ testInstrumentationRunner =
+ "com.google.samples.apps.nowinandroid.core.testing.NiaTestRunner"
+ }
+ }
+
+ val libs = extensions.getByType().named("libs")
+
+ dependencies {
+ add("implementation", project(":core:designsystem"))
+ add("implementation", project(":core:model"))
+ add("implementation", project(":core:domain"))
+
+
+
+ add("testImplementation", libs.findLibrary("junit4").get())
+ add("androidTestImplementation", libs.findLibrary("androidx.test.core").get())
+ add(
+ "androidTestImplementation",
+ libs.findLibrary("androidx.test.espresso.core").get()
+ )
+ add("androidTestImplementation", libs.findLibrary("androidx.test.runner").get())
+ add("androidTestImplementation", libs.findLibrary("androidx.test.rules").get())
+ add("androidTestImplementation", libs.findLibrary("androidx.compose.ui.test").get())
+
+ add("implementation", libs.findLibrary("coil.kt").get())
+ add("implementation", libs.findLibrary("coil.kt.compose").get())
+
+ add("implementation", libs.findLibrary("androidx.hilt.navigation.compose").get())
+ add("implementation", libs.findLibrary("androidx.lifecycle.runtimeCompose").get())
+ add("implementation", libs.findLibrary("androidx.lifecycle.viewModelCompose").get())
+
+ add("implementation", libs.findLibrary("kotlinx.coroutines.android").get())
+
+ }
+ }
+ }
+}
diff --git a/build-logic/convention/src/main/java/AndroidHiltConventionPlugin.kt b/build-logic/convention/src/main/java/AndroidHiltConventionPlugin.kt
new file mode 100644
index 0000000..99f6dab
--- /dev/null
+++ b/build-logic/convention/src/main/java/AndroidHiltConventionPlugin.kt
@@ -0,0 +1,24 @@
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.artifacts.VersionCatalogsExtension
+import org.gradle.kotlin.dsl.dependencies
+import org.gradle.kotlin.dsl.getByType
+
+class AndroidHiltConventionPlugin : Plugin {
+ override fun apply(target: Project) {
+ with(target) {
+ with(pluginManager) {
+ apply("org.jetbrains.kotlin.kapt")
+ apply("dagger.hilt.android.plugin")
+ }
+
+ val libs = extensions.getByType().named("libs")
+ dependencies {
+ "implementation"(libs.findLibrary("hilt.android").get())
+ "kapt"(libs.findLibrary("hilt.compiler").get())
+ "kaptAndroidTest"(libs.findLibrary("hilt.compiler").get())
+ "androidTestImplementation"(libs.findLibrary("hilt-android-testing").get())
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-logic/convention/src/main/java/AndroidLibraryComposeConventionPlugin.kt b/build-logic/convention/src/main/java/AndroidLibraryComposeConventionPlugin.kt
new file mode 100644
index 0000000..24deb80
--- /dev/null
+++ b/build-logic/convention/src/main/java/AndroidLibraryComposeConventionPlugin.kt
@@ -0,0 +1,15 @@
+import com.android.build.gradle.LibraryExtension
+import com.shindra.convention.configureAndroidCompose
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.kotlin.dsl.getByType
+
+class AndroidLibraryComposeConventionPlugin : Plugin {
+ override fun apply(target: Project) {
+ with(target) {
+ pluginManager.apply("com.android.library")
+ val extension = extensions.getByType()
+ configureAndroidCompose(extension)
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-logic/convention/src/main/java/AndroidLibraryConventionPlugin.kt b/build-logic/convention/src/main/java/AndroidLibraryConventionPlugin.kt
new file mode 100644
index 0000000..26e89fa
--- /dev/null
+++ b/build-logic/convention/src/main/java/AndroidLibraryConventionPlugin.kt
@@ -0,0 +1,50 @@
+import com.android.build.gradle.LibraryExtension
+import com.shindra.convention.configureKotlinAndroid
+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
+
+class AndroidLibraryConventionPlugin : Plugin {
+ override fun apply(target: Project) {
+ with(target) {
+ with(pluginManager) {
+ apply("com.android.library")
+ apply("org.jetbrains.kotlin.android")
+ }
+
+ extensions.configure {
+ configureKotlinAndroid(this)
+ defaultConfig.targetSdk = 33
+ defaultConfig {
+ testInstrumentationRunner =
+ "com.google.samples.apps.nowinandroid.core.testing.NiaTestRunner"
+ }
+ }
+
+ val libs = extensions.getByType().named("libs")
+
+ configurations.configureEach {
+ resolutionStrategy {
+ force(libs.findLibrary("junit4").get())
+ // Temporary workaround for https://issuetracker.google.com/174733673
+ force("org.objenesis:objenesis:2.6")
+ }
+ }
+
+ dependencies {
+
+ "androidTestImplementation"(libs.findLibrary("androidx.test.core").get())
+ "androidTestImplementation"(libs.findLibrary("androidx.test.espresso.core").get())
+
+ "androidTestImplementation"(libs.findLibrary("androidx.test.runner").get())
+ "androidTestImplementation"(libs.findLibrary("androidx.test.rules").get())
+
+ "testImplementation"(project(":core:tests"))
+ "androidTestImplementation"(project(":core:tests"))
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-logic/convention/src/main/java/com/shindra/convention/AndroidCompose.kt b/build-logic/convention/src/main/java/com/shindra/convention/AndroidCompose.kt
new file mode 100644
index 0000000..d26e868
--- /dev/null
+++ b/build-logic/convention/src/main/java/com/shindra/convention/AndroidCompose.kt
@@ -0,0 +1,39 @@
+package com.shindra.convention
+
+import com.android.build.api.dsl.CommonExtension
+import org.gradle.api.Incubating
+import org.gradle.api.Project
+import org.gradle.api.artifacts.VersionCatalogsExtension
+import org.gradle.kotlin.dsl.dependencies
+import org.gradle.kotlin.dsl.getByType
+
+/**
+ * Configure Compose-specific options
+ */
+@Incubating
+internal fun Project.configureAndroidCompose(
+ commonExtension: CommonExtension<*, *, *, *,*>,
+) {
+ val libs = extensions.getByType().named("libs")
+
+ commonExtension.apply {
+ buildFeatures {
+ compose = true
+ }
+
+ composeOptions {
+ kotlinCompilerExtensionVersion = libs.findVersion("androidxComposeCompiler").get().toString()
+ }
+
+ kotlinOptions {
+ freeCompilerArgs = freeCompilerArgs
+ }
+
+ dependencies {
+ val bom = libs.findLibrary("androidx-compose-bom").get()
+ add("implementation", platform(bom))
+ add("androidTestImplementation", platform(bom))
+ add("implementation", libs.findLibrary("androidx.compose.constraint").get())
+ }
+ }
+}
\ No newline at end of file
diff --git a/build-logic/convention/src/main/java/com/shindra/convention/KotlinAndroid.kt b/build-logic/convention/src/main/java/com/shindra/convention/KotlinAndroid.kt
new file mode 100644
index 0000000..ef1a41d
--- /dev/null
+++ b/build-logic/convention/src/main/java/com/shindra/convention/KotlinAndroid.kt
@@ -0,0 +1,53 @@
+package com.shindra.convention
+
+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.api.plugins.ExtensionAware
+import org.gradle.kotlin.dsl.dependencies
+import org.gradle.kotlin.dsl.getByType
+import org.gradle.kotlin.dsl.provideDelegate
+import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
+
+/**
+ * Configure base Kotlin with Android options
+ */
+internal fun Project.configureKotlinAndroid(
+ commonExtension: CommonExtension<*, *, *, *, *>,
+) {
+ commonExtension.apply {
+ compileSdk = 34
+
+ defaultConfig {
+ minSdk = 26
+ }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+
+ kotlinOptions {
+ // 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 = freeCompilerArgs + listOf(
+ "-opt-in=kotlin.RequiresOptIn",
+ // Enable experimental coroutines APIs, including Flow
+ "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
+ "-opt-in=kotlinx.coroutines.FlowPreview",
+ "-opt-in=kotlin.Experimental",
+ )
+
+ // Set JVM target to 1.8
+ jvmTarget = JavaVersion.VERSION_17.toString()
+ }
+ }
+}
+
+fun CommonExtension<*, *, *, *,*>.kotlinOptions(block: KotlinJvmOptions.() -> Unit) {
+ (this as? ExtensionAware)?.extensions?.configure("kotlinOptions", block)
+}
diff --git a/build-logic/gradle.properties b/build-logic/gradle.properties
new file mode 100644
index 0000000..201d4af
--- /dev/null
+++ b/build-logic/gradle.properties
@@ -0,0 +1,5 @@
+# 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..6225785
--- /dev/null
+++ b/build-logic/settings.gradle.kts
@@ -0,0 +1,14 @@
+dependencyResolutionManagement {
+ repositories {
+ google()
+ mavenCentral()
+ }
+ versionCatalogs {
+ create("libs") {
+ from(files("../gradle/libs.versions.toml"))
+ }
+ }
+}
+
+rootProject.name = "build-logic"
+include(":convention")
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..08fbdba
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,9 @@
+plugins {
+ alias(libs.plugins.android.application) apply false
+ alias(libs.plugins.kotlin.jvm) apply false
+ alias(libs.plugins.kotlin.serialization) apply false
+ alias(libs.plugins.hilt) apply false
+
+ id 'com.android.library' version '7.3.1' apply false
+ id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
+}
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 20d87a7..f0ffaf5 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,7 +1,8 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
plugins {
- alias(libs.plugins.androidApplication) apply false
- alias(libs.plugins.kotlinAndroid) apply false
-}
-true // Needed to make the Suppress annotation work for the plugins block
\ No newline at end of file
+ alias(libs.plugins.android.application) 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.android.library) apply false
+ alias(libs.plugins.org.jetbrains.kotlin.android) apply false
+}
\ No newline at end of file
diff --git a/core/data/.gitignore b/core/data/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/core/data/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/data/build.gradle.kts b/core/data/build.gradle.kts
new file mode 100644
index 0000000..44a6ebf
--- /dev/null
+++ b/core/data/build.gradle.kts
@@ -0,0 +1,21 @@
+plugins {
+ id("shindra.library")
+ id("shindra.hilt")
+ id("kotlinx-serialization")
+}
+
+android {
+ namespace = "com.shindra.chargemap.core.data"
+}
+
+dependencies {
+
+ implementation(project(":core:network"))
+ implementation(project(":core:model"))
+
+ implementation(libs.androidx.core.ktx)
+
+ implementation(libs.kotlinx.datetime)
+ implementation(libs.kotlinx.coroutines.android)
+ implementation(libs.kotlinx.serialization.json)
+}
\ No newline at end of file
diff --git a/core/data/consumer-rules.pro b/core/data/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/core/data/proguard-rules.pro b/core/data/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/core/data/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/core/data/src/androidTest/java/com/shindra/chargemap/core/data/ExampleInstrumentedTest.kt b/core/data/src/androidTest/java/com/shindra/chargemap/core/data/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..42bcc23
--- /dev/null
+++ b/core/data/src/androidTest/java/com/shindra/chargemap/core/data/ExampleInstrumentedTest.kt
@@ -0,0 +1,22 @@
+package com.shindra.chargemap.core.data
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+import org.junit.Assert.*
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.shindra.chargemap.core.data.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/core/data/src/main/AndroidManifest.xml b/core/data/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..44008a4
--- /dev/null
+++ b/core/data/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/core/data/src/main/java/com/shindra/chargemap/core/data/FlowBuilder.kt b/core/data/src/main/java/com/shindra/chargemap/core/data/FlowBuilder.kt
new file mode 100644
index 0000000..f1576fc
--- /dev/null
+++ b/core/data/src/main/java/com/shindra/chargemap/core/data/FlowBuilder.kt
@@ -0,0 +1,8 @@
+package com.shindra.chargemap.core.data
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+
+fun flowAndEmit( block : suspend () -> T) : Flow = flow {
+ emit(block())
+}
\ No newline at end of file
diff --git a/core/data/src/main/java/com/shindra/chargemap/core/data/di/DataModule.kt b/core/data/src/main/java/com/shindra/chargemap/core/data/di/DataModule.kt
new file mode 100644
index 0000000..4800dc4
--- /dev/null
+++ b/core/data/src/main/java/com/shindra/chargemap/core/data/di/DataModule.kt
@@ -0,0 +1,19 @@
+package com.shindra.chargemap.core.data.di
+
+import com.shindra.chargemap.core.data.repositories.NinjaRepository
+import com.shindra.chargemap.core.network.sources.NinjaDataSource
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+
+@Module
+@InstallIn(SingletonComponent::class)
+object DataModule {
+ @Provides
+ @Singleton
+ fun provideNinjaRepository(client: NinjaDataSource) = NinjaRepository(client)
+
+}
\ No newline at end of file
diff --git a/core/data/src/main/java/com/shindra/chargemap/core/data/repositories/NinjaRepository.kt b/core/data/src/main/java/com/shindra/chargemap/core/data/repositories/NinjaRepository.kt
new file mode 100644
index 0000000..0f4c627
--- /dev/null
+++ b/core/data/src/main/java/com/shindra/chargemap/core/data/repositories/NinjaRepository.kt
@@ -0,0 +1,39 @@
+package com.shindra.chargemap.core.data.repositories
+
+import com.shindra.chargemap.core.data.flowAndEmit
+import com.shindra.chargemap.core.model.ModelAirplane
+import com.shindra.chargemap.core.model.ModelDetailAirplane
+import com.shindra.chargemap.core.network.sources.NinjaDataSource
+import kotlinx.coroutines.flow.Flow
+import javax.inject.Inject
+
+open class NinjaRepository @Inject constructor(private val client: NinjaDataSource) {
+
+ fun planesByEngineType(type: String): Flow> = flowAndEmit {
+ client.planeByEngineType(type).map {
+ ModelAirplane(manufacturer = it.manufacturer, model = it.model)
+ }
+ }
+
+ fun planeDetails(manufacturer: String, model: String): Flow = flowAndEmit {
+ val plane = client.planeByManufacturerAndModel(manufacturer = manufacturer, model = model)
+
+ ModelDetailAirplane(
+ plane?.manufacturer.orEmpty(),
+ plane?.model.orEmpty(),
+ plane?.engineType.orEmpty(),
+ plane?.engineThrustLbFt,
+ plane?.maxSpeedKnots,
+ plane?.cruiseSpeedKnots,
+ plane?.ceilingFt,
+ plane?.takeoffGroundRunFt,
+ plane?.landingGroundRollFt,
+ plane?.grossWeightLbs,
+ plane?.emptyWeightLbs,
+ plane?.lengthFt,
+ plane?.heightFt,
+ plane?.wingSpanFt,
+ plane?.rangeNauticalMiles
+ )
+ }
+}
\ No newline at end of file
diff --git a/core/data/src/test/java/com/shindra/chargemap/core/data/ExampleUnitTest.kt b/core/data/src/test/java/com/shindra/chargemap/core/data/ExampleUnitTest.kt
new file mode 100644
index 0000000..4952902
--- /dev/null
+++ b/core/data/src/test/java/com/shindra/chargemap/core/data/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.shindra.chargemap.core.data
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/core/designsystem/.gitignore b/core/designsystem/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/core/designsystem/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/designsystem/build.gradle.kts b/core/designsystem/build.gradle.kts
new file mode 100644
index 0000000..6e65e2f
--- /dev/null
+++ b/core/designsystem/build.gradle.kts
@@ -0,0 +1,28 @@
+plugins {
+ id("shindra.library")
+ id("shindra.library.compose")
+}
+
+android {
+ defaultConfig {
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+ lint {
+ checkDependencies = true
+ }
+ namespace = "com.shindra.chargemap.core.designsystem"
+}
+
+
+dependencies {
+ implementation(libs.androidx.core.ktx)
+ api(libs.androidx.compose.foundation)
+ api(libs.androidx.compose.foundation.layout)
+ api(libs.androidx.compose.material.iconsExtended)
+ api(libs.androidx.compose.material3)
+ debugApi(libs.androidx.compose.ui.tooling)
+ api(libs.androidx.compose.ui.tooling.preview)
+ api(libs.androidx.compose.ui.util)
+ api(libs.androidx.compose.runtime)
+ implementation(libs.coil.kt.compose)
+}
\ No newline at end of file
diff --git a/core/designsystem/src/main/AndroidManifest.xml b/core/designsystem/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..44008a4
--- /dev/null
+++ b/core/designsystem/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/components/CoilAsyncImage.kt b/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/components/CoilAsyncImage.kt
new file mode 100644
index 0000000..aa35ed7
--- /dev/null
+++ b/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/components/CoilAsyncImage.kt
@@ -0,0 +1,43 @@
+package com.shindra.chargemap.core.designsystem.components
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.DefaultAlpha
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.layout.ContentScale
+import coil.compose.SubcomposeAsyncImage
+
+@Composable
+fun CoilNetworkImage(
+ url: String,
+ modifier: Modifier = Modifier,
+ onLoading: @Composable () -> Unit = { ShimmerSurface(Modifier.fillMaxSize()) },
+ onError: @Composable () -> Unit = {},
+ contentDescription: String? = null,
+ alignment: Alignment = Alignment.Center,
+ contentScale: ContentScale = ContentScale.Fit,
+ alpha: Float = DefaultAlpha,
+ colorFilter: ColorFilter? = null,
+ shape: Shape = RectangleShape
+) {
+ Surface(
+ shape = shape,
+ modifier = modifier
+ ) {
+ SubcomposeAsyncImage(
+ model = url,
+ contentDescription = contentDescription,
+ loading = { onLoading() },
+ error = { onError() },
+ contentScale = contentScale,
+ alignment = alignment,
+ alpha = alpha,
+ colorFilter = colorFilter
+ )
+ }
+}
\ No newline at end of file
diff --git a/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/components/EmbeddedTabLayout.kt b/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/components/EmbeddedTabLayout.kt
new file mode 100644
index 0000000..b6333b9
--- /dev/null
+++ b/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/components/EmbeddedTabLayout.kt
@@ -0,0 +1,116 @@
+package com.shindra.chargemap.core.designsystem.components
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.*
+import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
+import androidx.compose.runtime.*
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.zIndex
+
+@Composable
+fun EmbeddedTabLayout(
+ selectedIndex: Int,
+ onIndexChange: (Int) -> Unit,
+ nbOfTabs: Int,
+ modifier: Modifier = Modifier,
+ shape: Shape = RoundedCornerShape(50),
+ border: BorderStroke = BorderStroke(1.dp, Color.Gray),
+ colors: EmbeddedTabLayoutColors = EmbeddedTabLayoutDefaults.colors(),
+ tabContent: @Composable ColumnScope.(index: Int) -> Unit
+) {
+ TabRow(
+ selectedTabIndex = selectedIndex,
+ modifier = modifier
+ .border(
+ border = border,
+ shape = shape
+ )
+ .clip(shape),
+ containerColor = colors.backgroundColor,
+ indicator = { tabPositions: List ->
+ FancyIndicator(
+ Modifier.tabIndicatorOffset(tabPositions[selectedIndex]),
+ indicatorColor = colors.indicatorColor,
+ shape = shape
+ )
+ },
+ divider = {}
+ ) {
+ (0 until nbOfTabs).forEach { index ->
+ Tab(
+ selected = index == selectedIndex,
+ onClick = { onIndexChange(index) },
+ modifier = Modifier
+ .clip(shape)
+ .zIndex(2F)
+ .background(Color.Transparent),
+ content = { tabContent(index) }
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun EmbeddedTabLayoutPreview() {
+ var selectedIndex by remember {
+ mutableStateOf(0)
+ }
+
+ EmbeddedTabLayout(
+ selectedIndex = selectedIndex,
+ onIndexChange = { selectedIndex = it },
+ nbOfTabs = 4,
+ tabContent = { i: Int ->
+ val selected = selectedIndex == i
+ Text(
+ text = "Tab $i",
+ color = if (selected) Color.White else Color.Unspecified
+ )
+ }
+ )
+}
+
+@Composable
+private fun FancyIndicator(
+ modifier: Modifier = Modifier,
+ indicatorColor: Color = MaterialTheme.colorScheme.primary,
+ shape: Shape,
+) {
+ Box(
+ modifier
+ .padding(1.dp)
+ .clip(shape)
+ .fillMaxSize()
+ .background(color = indicatorColor)
+ .zIndex(1f)
+ )
+}
+
+object EmbeddedTabLayoutDefaults {
+ @Composable
+ fun colors(
+ backgroundColor: Color = LocalContentColor.current,
+ indicatorColor: Color = MaterialTheme.colorScheme.primary
+ ) = EmbeddedTabLayoutColors(
+ backgroundColor = backgroundColor,
+ indicatorColor = indicatorColor
+ )
+}
+
+class EmbeddedTabLayoutColors internal constructor(
+ val backgroundColor: Color,
+ val indicatorColor: Color
+)
\ No newline at end of file
diff --git a/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/components/FullLoadingScreen.kt b/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/components/FullLoadingScreen.kt
new file mode 100644
index 0000000..f134c73
--- /dev/null
+++ b/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/components/FullLoadingScreen.kt
@@ -0,0 +1,39 @@
+package com.shindra.chargemap.core.designsystem.components
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.ProgressIndicatorDefaults
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.shindra.chargemap.core.designsystem.theme.ChargeMapTheme
+import com.shindra.chargemap.core.designsystem.R
+
+@Composable
+fun FullLoadingScreen(text: String = stringResource(id = R.string.full_screen_loading_waiting_text)) {
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ CircularProgressIndicator()
+ Text(
+ text = text,
+ color = ProgressIndicatorDefaults.circularColor,
+ modifier = Modifier.padding(16.dp)
+ )
+ }
+}
+
+
+@Composable
+@Preview
+fun FullLoadingScreenPReview() {
+ ChargeMapTheme() {
+ FullLoadingScreen()
+ }
+}
\ No newline at end of file
diff --git a/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/components/FullScreenError.kt b/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/components/FullScreenError.kt
new file mode 100644
index 0000000..98acd68
--- /dev/null
+++ b/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/components/FullScreenError.kt
@@ -0,0 +1,73 @@
+package com.shindra.chargemap.core.designsystem.components
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.WarningAmber
+import androidx.compose.material3.Button
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.shindra.chargemap.core.designsystem.theme.ChargeMapTheme
+
+@Composable
+fun FullScreenErrorScreen(onRetry: () -> Unit) {
+ Scaffold(modifier = Modifier.fillMaxSize(), bottomBar = {
+ Button(
+ onClick = onRetry, modifier = Modifier
+ .fillMaxWidth()
+ .padding(8.dp)
+ ) {
+ Text("Réessayer ")
+ }
+ }) {
+
+ Row(
+ modifier = Modifier
+ .padding(it)
+ .fillMaxWidth(),
+ horizontalArrangement = Arrangement.Center
+ ) {
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+
+ Image(
+ imageVector = Icons.Default.WarningAmber,
+ contentDescription = null,
+ colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.error),
+ modifier = Modifier.size(100.dp)
+ )
+
+ Text(
+ text = "Upssie",
+ fontWeight = FontWeight.Bold,
+ color = MaterialTheme.colorScheme.error
+ )
+ }
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun FullScreenErrorScreenPreview() {
+ ChargeMapTheme {
+ FullScreenErrorScreen({})
+ }
+}
\ No newline at end of file
diff --git a/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/components/KeyValueUnit.kt b/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/components/KeyValueUnit.kt
new file mode 100644
index 0000000..829b093
--- /dev/null
+++ b/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/components/KeyValueUnit.kt
@@ -0,0 +1,117 @@
+package com.shindra.chargemap.core.designsystem.components
+
+import android.content.res.Configuration
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.TextUnit
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun KeyValueUnit(
+ key: String,
+ value: String,
+ unit: String?,
+ modifier: Modifier = Modifier,
+ keyTextConfig: TextConfig = KeyValueTextConfig.defaultText(),
+ valueTextConfig: TextConfig = KeyValueTextConfig.defaultText(),
+ unitTextConfig: TextConfig = KeyValueTextConfig.defaultText()
+) {
+ Row(
+ modifier = modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ with(keyTextConfig) {
+ Text(
+ text = key,
+ color = color,
+ fontSize = fontSize,
+ fontWeight = fontWeight,
+ fontFamily = fontFamily,
+ modifier = Modifier.padding(end = 4.dp)
+ )
+ }
+
+ Row {
+ with(valueTextConfig) {
+ Text(
+ text = value,
+ color = color,
+ fontSize = fontSize,
+ fontWeight = fontWeight,
+ fontFamily = fontFamily,
+ modifier = Modifier.padding(end = 4.dp)
+ )
+ }
+
+ if (unit == null) return
+
+ with(unitTextConfig) {
+ Text(
+ text = unit,
+ color = color,
+ fontSize = fontSize,
+ fontWeight = fontWeight,
+ fontFamily = fontFamily,
+ modifier = Modifier.padding(end = 4.dp)
+ )
+ }
+ }
+
+ }
+}
+
+
+@Preview(showBackground = true)
+@Composable
+fun KeyValueLightPreview() {
+ KeyValueUnit(
+ key = "Clef",
+ value = "Valeur",
+ "Unit"
+ )
+}
+
+
+@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+fun KeyValueDarkPreview() {
+ KeyValueUnit(
+ key = "Clef",
+ value = "Valeur",
+ "Unit"
+ )
+}
+
+object KeyValueTextConfig {
+ fun defaultText(
+ color: Color = Color.Unspecified,
+ fontSize: TextUnit = TextUnit.Unspecified,
+ fontStyle: FontStyle? = null,
+ fontWeight: FontWeight? = null,
+ fontFamily: FontFamily? = null
+ ) = TextConfig(
+ color = color,
+ fontSize = fontSize,
+ fontStyle = fontStyle,
+ fontWeight = fontWeight,
+ fontFamily = fontFamily
+ )
+}
+
+data class TextConfig internal constructor(
+ val color: Color,
+ val fontSize: TextUnit,
+ val fontStyle: FontStyle?,
+ val fontWeight: FontWeight?,
+ val fontFamily: FontFamily?,
+)
\ No newline at end of file
diff --git a/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/components/Shimmer.kt b/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/components/Shimmer.kt
new file mode 100644
index 0000000..21dfa24
--- /dev/null
+++ b/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/components/Shimmer.kt
@@ -0,0 +1,47 @@
+package com.shindra.chargemap.core.designsystem.components
+
+import androidx.compose.animation.core.*
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+
+@Composable
+fun shimmerAnimation(): Brush {
+ val shimmerColors = listOf(
+ Color.LightGray.copy(0.6f),
+ Color.LightGray.copy(0.2f),
+ Color.LightGray.copy(0.6f)
+ )
+
+ val transition = rememberInfiniteTransition()
+ val translateAni = transition.animateFloat(
+ initialValue = 0f,
+ targetValue = 10000f,
+ animationSpec = infiniteRepeatable(
+ animation = tween(
+ durationMillis = 1000,
+ easing = FastOutSlowInEasing
+ ),
+ repeatMode = RepeatMode.Restart
+ ),
+ label = ""
+ )
+
+ return Brush.linearGradient(
+ colors = shimmerColors,
+ start = Offset.Zero,
+ end = Offset(translateAni.value, translateAni.value)
+ )
+}
+
+@Composable
+fun ShimmerSurface(modifier: Modifier) {
+ Spacer(
+ modifier = modifier
+ .background(shimmerAnimation()),
+ )
+}
\ No newline at end of file
diff --git a/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/components/UiStateContent.kt b/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/components/UiStateContent.kt
new file mode 100644
index 0000000..b2e282a
--- /dev/null
+++ b/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/components/UiStateContent.kt
@@ -0,0 +1,39 @@
+package com.shindra.chargemap.core.designsystem.components
+
+import android.util.Log
+import androidx.compose.runtime.Composable
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.map
+
+
+@Composable
+fun UiStateContent(
+ state: UiState,
+ onError: @Composable (Throwable) -> Unit = {},
+ onLoading: @Composable () -> Unit = { FullLoadingScreen() },
+ onSuccess: @Composable (T) -> Unit,
+) {
+ when (state) {
+ is UiState.Error -> onError(state.throwable)
+ UiState.Loading -> onLoading()
+ is UiState.Success -> onSuccess(state.value)
+ }
+}
+
+sealed interface UiState {
+ data class Success(val value: T) : UiState
+ data class Error(val throwable: Throwable) : UiState
+ data object Loading : UiState
+}
+
+fun Flow.asUiState(): Flow> {
+ return this
+ .map> {
+ UiState.Success(it)
+ }
+ .catch {
+ Log.e("Flow", "Error in downstream", it)
+ emit(UiState.Error(it))
+ }
+}
\ No newline at end of file
diff --git a/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/theme/Color.kt b/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/theme/Color.kt
new file mode 100644
index 0000000..9366758
--- /dev/null
+++ b/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/theme/Color.kt
@@ -0,0 +1,64 @@
+package com.shindra.chargemap.core.designsystem.theme
+
+import androidx.compose.ui.graphics.Color
+
+val md_theme_light_primary = Color(0xFF375CA8)
+val md_theme_light_onPrimary = Color(0xFFFFFFFF)
+val md_theme_light_primaryContainer = Color(0xFFD9E2FF)
+val md_theme_light_onPrimaryContainer = Color(0xFF001945)
+val md_theme_light_secondary = Color(0xFF575E71)
+val md_theme_light_onSecondary = Color(0xFFFFFFFF)
+val md_theme_light_secondaryContainer = Color(0xFFDCE2F9)
+val md_theme_light_onSecondaryContainer = Color(0xFF141B2C)
+val md_theme_light_tertiary = Color(0xFF725572)
+val md_theme_light_onTertiary = Color(0xFFFFFFFF)
+val md_theme_light_tertiaryContainer = Color(0xFFFDD7FA)
+val md_theme_light_onTertiaryContainer = Color(0xFF2A132C)
+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(0xFFFEFBFF)
+val md_theme_light_onBackground = Color(0xFF1B1B1F)
+val md_theme_light_outline = Color(0xFF757780)
+val md_theme_light_inverseOnSurface = Color(0xFFF2F0F4)
+val md_theme_light_inverseSurface = Color(0xFF303034)
+val md_theme_light_inversePrimary = Color(0xFFB0C6FF)
+val md_theme_light_surfaceTint = Color(0xFF375CA8)
+val md_theme_light_outlineVariant = Color(0xFFC5C6D0)
+val md_theme_light_scrim = Color(0xFF000000)
+val md_theme_light_surface = Color(0xFFFBF8FD)
+val md_theme_light_onSurface = Color(0xFF1B1B1F)
+val md_theme_light_surfaceVariant = Color(0xFFE1E2EC)
+val md_theme_light_onSurfaceVariant = Color(0xFF44464F)
+
+
+val md_theme_dark_primary = Color(0xFFB0C6FF)
+val md_theme_dark_onPrimary = Color(0xFF002D6F)
+val md_theme_dark_primaryContainer = Color(0xFF19438F)
+val md_theme_dark_onPrimaryContainer = Color(0xFFD9E2FF)
+val md_theme_dark_secondary = Color(0xFFC0C6DC)
+val md_theme_dark_onSecondary = Color(0xFF293042)
+val md_theme_dark_secondaryContainer = Color(0xFF404659)
+val md_theme_dark_onSecondaryContainer = Color(0xFFDCE2F9)
+val md_theme_dark_tertiary = Color(0xFFE0BBDD)
+val md_theme_dark_onTertiary = Color(0xFF412742)
+val md_theme_dark_tertiaryContainer = Color(0xFF593D5A)
+val md_theme_dark_onTertiaryContainer = Color(0xFFFDD7FA)
+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(0xFF1B1B1F)
+val md_theme_dark_onBackground = Color(0xFFE3E2E6)
+val md_theme_dark_outline = Color(0xFF8F9099)
+val md_theme_dark_inverseOnSurface = Color(0xFF1B1B1F)
+val md_theme_dark_inverseSurface = Color(0xFFE3E2E6)
+val md_theme_dark_inversePrimary = Color(0xFF375CA8)
+val md_theme_dark_surfaceTint = Color(0xFFB0C6FF)
+val md_theme_dark_outlineVariant = Color(0xFF44464F)
+val md_theme_dark_scrim = Color(0xFF000000)
+val md_theme_dark_surface = Color(0xFF121316)
+val md_theme_dark_onSurface = Color(0xFFC7C6CA)
+val md_theme_dark_surfaceVariant = Color(0xFF44464F)
+val md_theme_dark_onSurfaceVariant = Color(0xFFC5C6D0)
\ No newline at end of file
diff --git a/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/theme/Theme.kt b/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/theme/Theme.kt
new file mode 100644
index 0000000..d9f548a
--- /dev/null
+++ b/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/theme/Theme.kt
@@ -0,0 +1,110 @@
+package com.shindra.chargemap.core.designsystem.theme
+
+import android.app.Activity
+import android.os.Build
+import androidx.compose.foundation.isSystemInDarkTheme
+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.SideEffect
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalView
+
+private val LightColorScheme = 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,
+ 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,
+ surface = md_theme_light_surface,
+ onSurface = md_theme_light_onSurface,
+ surfaceVariant = md_theme_light_surfaceVariant,
+ onSurfaceVariant = md_theme_light_onSurfaceVariant,
+)
+
+
+private val DarkColorScheme = 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,
+ 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,
+ surface = md_theme_dark_surface,
+ onSurface = md_theme_dark_onSurface,
+ surfaceVariant = md_theme_dark_surfaceVariant,
+ onSurfaceVariant = md_theme_dark_onSurfaceVariant,
+)
+
+@Composable
+fun ChargeMapTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
+ // Dynamic color is available on Android 12+
+ dynamicColor: Boolean = false,
+ content: @Composable () -> Unit
+) {
+ val colorScheme = when {
+ dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
+ val context = LocalContext.current
+ if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
+ }
+ darkTheme -> DarkColorScheme
+ else -> LightColorScheme
+ }
+ val view = LocalView.current
+ if (!view.isInEditMode) {
+ SideEffect {
+ (view.context as Activity).window.statusBarColor = colorScheme.primary.toArgb()
+ //ViewCompat.getWindowInsetsController(view)?.isAppearanceLightStatusBars = darkTheme
+ }
+ }
+
+ MaterialTheme(
+ colorScheme = colorScheme,
+ typography = Typography,
+ content = content
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/androidtest/ui/theme/Type.kt b/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/theme/Type.kt
similarity index 94%
rename from app/src/main/java/com/example/androidtest/ui/theme/Type.kt
rename to core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/theme/Type.kt
index f45254a..2fbbb94 100644
--- a/app/src/main/java/com/example/androidtest/ui/theme/Type.kt
+++ b/core/designsystem/src/main/java/com/shindra/chargemap/core/designsystem/theme/Type.kt
@@ -1,4 +1,4 @@
-package com.example.androidtest.ui.theme
+package com.shindra.chargemap.core.designsystem.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
diff --git a/core/designsystem/src/main/res/values/strings.xml b/core/designsystem/src/main/res/values/strings.xml
new file mode 100644
index 0000000..47e9a9a
--- /dev/null
+++ b/core/designsystem/src/main/res/values/strings.xml
@@ -0,0 +1,6 @@
+
+
+
+ Chargement des données. Veuillez patientez
+
+
\ No newline at end of file
diff --git a/core/domain/.gitignore b/core/domain/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/core/domain/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/domain/build.gradle.kts b/core/domain/build.gradle.kts
new file mode 100644
index 0000000..2a290d3
--- /dev/null
+++ b/core/domain/build.gradle.kts
@@ -0,0 +1,16 @@
+plugins {
+ id("shindra.library")
+ id("shindra.hilt")
+}
+
+android {
+ namespace = "com.chargemap.core.domain"
+
+}
+
+dependencies {
+
+ implementation(project(":core:data"))
+ implementation(project(":core:model"))
+
+}
\ No newline at end of file
diff --git a/core/domain/consumer-rules.pro b/core/domain/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/core/domain/proguard-rules.pro b/core/domain/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/core/domain/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/core/domain/src/androidTest/java/com/chargemap/core/domain/ExampleInstrumentedTest.kt b/core/domain/src/androidTest/java/com/chargemap/core/domain/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..4ee9fd4
--- /dev/null
+++ b/core/domain/src/androidTest/java/com/chargemap/core/domain/ExampleInstrumentedTest.kt
@@ -0,0 +1,22 @@
+package com.chargemap.core.domain
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+import org.junit.Assert.*
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.chargemap.core.domain.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/core/domain/src/main/AndroidManifest.xml b/core/domain/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a5918e6
--- /dev/null
+++ b/core/domain/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/core/domain/src/main/java/com/chargemap/core/domain/di/DomainDiModule.kt b/core/domain/src/main/java/com/chargemap/core/domain/di/DomainDiModule.kt
new file mode 100644
index 0000000..4786f1a
--- /dev/null
+++ b/core/domain/src/main/java/com/chargemap/core/domain/di/DomainDiModule.kt
@@ -0,0 +1,24 @@
+package com.chargemap.core.domain.di
+
+import com.chargemap.core.domain.usecases.PlaneDetailsUseCase
+import com.chargemap.core.domain.usecases.PlaneUseCase
+import com.shindra.chargemap.core.data.repositories.NinjaRepository
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+
+@Module
+@InstallIn(SingletonComponent::class)
+object DomainDiModule {
+ @Provides
+ @Singleton
+ fun providePlaneUsesCase(repository: NinjaRepository) = PlaneUseCase(repository)
+
+ @Provides
+ @Singleton
+ fun providePlaneDetailsUsesCase(repository: NinjaRepository) = PlaneDetailsUseCase(repository)
+
+}
\ No newline at end of file
diff --git a/core/domain/src/main/java/com/chargemap/core/domain/usecases/PlaneDetailsUseCase.kt b/core/domain/src/main/java/com/chargemap/core/domain/usecases/PlaneDetailsUseCase.kt
new file mode 100644
index 0000000..25d331a
--- /dev/null
+++ b/core/domain/src/main/java/com/chargemap/core/domain/usecases/PlaneDetailsUseCase.kt
@@ -0,0 +1,11 @@
+package com.chargemap.core.domain.usecases
+
+import com.shindra.chargemap.core.data.repositories.NinjaRepository
+import com.shindra.chargemap.core.model.ModelDetailAirplane
+import kotlinx.coroutines.flow.Flow
+import javax.inject.Inject
+
+class PlaneDetailsUseCase @Inject constructor(private val ninjaRepository: NinjaRepository) {
+ operator fun invoke(manufacturer: String, model: String): Flow =
+ ninjaRepository.planeDetails(manufacturer, model)
+}
\ No newline at end of file
diff --git a/core/domain/src/main/java/com/chargemap/core/domain/usecases/PlaneUseCase.kt b/core/domain/src/main/java/com/chargemap/core/domain/usecases/PlaneUseCase.kt
new file mode 100644
index 0000000..1e8efa9
--- /dev/null
+++ b/core/domain/src/main/java/com/chargemap/core/domain/usecases/PlaneUseCase.kt
@@ -0,0 +1,42 @@
+package com.chargemap.core.domain.usecases
+
+import com.shindra.chargemap.core.data.repositories.NinjaRepository
+import com.shindra.chargemap.core.model.ModelAirplane
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import javax.inject.Inject
+
+private typealias Airplanes = List
+
+sealed interface PlaneCategoryType {
+ data object Jet : PlaneCategoryType
+ data object Piston : PlaneCategoryType
+ data object PropJet : PlaneCategoryType
+}
+
+class PlaneUseCase @Inject constructor(private val ninjaRepository: NinjaRepository) {
+
+ operator fun invoke(): Flow> {
+ val jetFlow = ninjaRepository.planesByEngineType("jet")
+ val pistonFlow = ninjaRepository.planesByEngineType("piston")
+ val propFlow = ninjaRepository.planesByEngineType("propjet")
+
+ return combine(
+ flow = jetFlow,
+ flow2 = pistonFlow,
+ flow3 = propFlow
+ ) { jet, piston, prop ->
+
+ listOf(
+ jet.toPlaneCategory(PlaneCategoryType.Jet),
+ piston.toPlaneCategory(PlaneCategoryType.Piston),
+ prop.toPlaneCategory(PlaneCategoryType.PropJet)
+ )
+ }
+ }
+}
+
+
+data class PlaneCategory(val category: PlaneCategoryType, val planes: List)
+
+private fun Airplanes.toPlaneCategory(category: PlaneCategoryType) = PlaneCategory(category, this)
\ No newline at end of file
diff --git a/core/domain/src/test/java/com/chargemap/core/domain/ExampleUnitTest.kt b/core/domain/src/test/java/com/chargemap/core/domain/ExampleUnitTest.kt
new file mode 100644
index 0000000..99a8b83
--- /dev/null
+++ b/core/domain/src/test/java/com/chargemap/core/domain/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.chargemap.core.domain
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/core/model/.gitignore b/core/model/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/core/model/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/model/build.gradle.kts b/core/model/build.gradle.kts
new file mode 100644
index 0000000..8e40115
--- /dev/null
+++ b/core/model/build.gradle.kts
@@ -0,0 +1,3 @@
+plugins {
+ id("kotlin")
+}
\ No newline at end of file
diff --git a/core/model/src/main/java/com/shindra/chargemap/core/model/ModelAirplane.kt b/core/model/src/main/java/com/shindra/chargemap/core/model/ModelAirplane.kt
new file mode 100644
index 0000000..4cd4556
--- /dev/null
+++ b/core/model/src/main/java/com/shindra/chargemap/core/model/ModelAirplane.kt
@@ -0,0 +1,6 @@
+package com.shindra.chargemap.core.model
+
+data class ModelAirplane(
+ val manufacturer: String,
+ val model : String
+)
\ No newline at end of file
diff --git a/core/model/src/main/java/com/shindra/chargemap/core/model/ModelDetailAirplane.kt b/core/model/src/main/java/com/shindra/chargemap/core/model/ModelDetailAirplane.kt
new file mode 100644
index 0000000..a21a08e
--- /dev/null
+++ b/core/model/src/main/java/com/shindra/chargemap/core/model/ModelDetailAirplane.kt
@@ -0,0 +1,19 @@
+package com.shindra.chargemap.core.model
+
+data class ModelDetailAirplane(
+ val manufacturer: String,
+ val model: String,
+ val engineType: String,
+ val engineThrustLbFt: String?,
+ val maxSpeedKt: String?,
+ val cruiseSpeedKt: String?,
+ val ceilingFt: String?,
+ val takeoffGroundRunFt: String?,
+ val landingGroundRollFt: String?,
+ val grossWeightLbs: String?,
+ val emptyWeightLbs: String?,
+ val lengthFt: String?,
+ val heightFt: String?,
+ val wingspanFt: String?,
+ val rangeNauticalMiles: String?
+)
\ No newline at end of file
diff --git a/core/network/.gitignore b/core/network/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/core/network/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts
new file mode 100644
index 0000000..034c8e9
--- /dev/null
+++ b/core/network/build.gradle.kts
@@ -0,0 +1,21 @@
+plugins {
+ id("shindra.library")
+ id("shindra.hilt")
+ id("kotlinx-serialization")
+}
+
+android {
+ namespace = "com.shindra.chargemap.core.network"
+}
+
+dependencies {
+ implementation(libs.ktor.client.core)
+ implementation(libs.ktor.client.android)
+ implementation(libs.ktor.client.okhttp)
+ implementation(libs.ktor.client.serialization)
+ implementation(libs.ktor.client.content.negotiation)
+ implementation(libs.ktor.serialization.json)
+ testImplementation(libs.ktor.client.mock)
+ debugImplementation(libs.chucker.interceptor.debug)
+ releaseImplementation(libs.chucker.interceptor.release)
+}
\ No newline at end of file
diff --git a/core/network/consumer-rules.pro b/core/network/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/core/network/proguard-rules.pro b/core/network/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/core/network/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/core/network/src/androidTest/java/com/shindra/chargemap/core/network/ExampleInstrumentedTest.kt b/core/network/src/androidTest/java/com/shindra/chargemap/core/network/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..6daa1c8
--- /dev/null
+++ b/core/network/src/androidTest/java/com/shindra/chargemap/core/network/ExampleInstrumentedTest.kt
@@ -0,0 +1,19 @@
+package com.shindra.chargemap.core.network
+
+import androidx.test.runner.AndroidJUnit4
+import org.junit.Assert.*
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+
+ }
+}
\ No newline at end of file
diff --git a/core/network/src/main/AndroidManifest.xml b/core/network/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..44008a4
--- /dev/null
+++ b/core/network/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/core/network/src/main/java/com/shindra/chargemap/core/network/Client.kt b/core/network/src/main/java/com/shindra/chargemap/core/network/Client.kt
new file mode 100644
index 0000000..1f2ea3e
--- /dev/null
+++ b/core/network/src/main/java/com/shindra/chargemap/core/network/Client.kt
@@ -0,0 +1,32 @@
+package com.shindra.chargemap.core.network
+
+import io.ktor.client.engine.HttpClientEngineConfig
+import io.ktor.client.engine.HttpClientEngineFactory
+import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
+import io.ktor.client.plugins.defaultRequest
+import io.ktor.http.URLProtocol
+import io.ktor.serialization.kotlinx.json.json
+import kotlinx.serialization.json.Json
+
+
+internal fun > HttpClientFactory(
+ engine: T,
+ json: Json,
+ engineConfiguration: C.() -> Unit
+) = io.ktor.client.HttpClient(engine) {
+
+ engine { engineConfiguration() }
+
+ install(ContentNegotiation) {
+ json(json)
+ }
+
+ defaultRequest {
+ url {
+ host = "api.api-ninjas.com/v1/aircraft"
+ protocol = URLProtocol.HTTPS
+ }
+ headers.append("X-Api-Key", "HmRVesSiuT4RSHic4u+KPg==TttcNlfX2Yo3YYBL")
+ }
+}
+
diff --git a/core/network/src/main/java/com/shindra/chargemap/core/network/bo/PlanesNetwork.kt b/core/network/src/main/java/com/shindra/chargemap/core/network/bo/PlanesNetwork.kt
new file mode 100644
index 0000000..2d9c497
--- /dev/null
+++ b/core/network/src/main/java/com/shindra/chargemap/core/network/bo/PlanesNetwork.kt
@@ -0,0 +1,36 @@
+package com.shindra.chargemap.core.network.bo
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class NetworkAirplane(
+ val manufacturer: String,
+ val model: String,
+ @SerialName("engine_type")
+ val engineType: String,
+ @SerialName("engine_thrust_lb_ft")
+ val engineThrustLbFt: String? = null,
+ @SerialName("max_speed_knots")
+ val maxSpeedKnots: String? = null,
+ @SerialName("cruise_speed_knots")
+ val cruiseSpeedKnots: String? = null,
+ @SerialName("ceiling_ft")
+ val ceilingFt: String? = null,
+ @SerialName("takeoff_ground_run_ft")
+ val takeoffGroundRunFt: String? = null,
+ @SerialName("landing_ground_roll_ft")
+ val landingGroundRollFt: String? = null,
+ @SerialName("gross_weight_lbs")
+ val grossWeightLbs: String? = null,
+ @SerialName("empty_weight_lbs")
+ val emptyWeightLbs: String? = null,
+ @SerialName("length_ft")
+ val lengthFt: String? = null,
+ @SerialName("height_ft")
+ val heightFt: String? = null,
+ @SerialName("wing_span_ft")
+ val wingSpanFt: String? = null,
+ @SerialName("range_nautical_miles")
+ val rangeNauticalMiles: String? = null
+)
\ No newline at end of file
diff --git a/core/network/src/main/java/com/shindra/chargemap/core/network/di/NetworkDi.kt b/core/network/src/main/java/com/shindra/chargemap/core/network/di/NetworkDi.kt
new file mode 100644
index 0000000..aaedd45
--- /dev/null
+++ b/core/network/src/main/java/com/shindra/chargemap/core/network/di/NetworkDi.kt
@@ -0,0 +1,57 @@
+package com.shindra.chargemap.core.network.di
+
+import android.content.Context
+import com.chuckerteam.chucker.api.ChuckerInterceptor
+import com.shindra.chargemap.core.network.HttpClientFactory
+import com.shindra.chargemap.core.network.sources.NinjaDataSource
+import com.shindra.chargemap.core.network.sources.NinjaDataSourceNetwork
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import io.ktor.client.*
+import io.ktor.client.engine.okhttp.*
+import io.ktor.client.plugins.*
+import io.ktor.client.plugins.contentnegotiation.*
+import io.ktor.http.*
+import io.ktor.serialization.kotlinx.json.*
+import kotlinx.serialization.json.Json
+import javax.inject.Singleton
+
+
+@Module
+@InstallIn(SingletonComponent::class)
+interface DataSourceModule {
+ @Binds
+ fun bindNinjaDataSource(
+ ninjaDataSource: NinjaDataSourceNetwork
+ ): NinjaDataSource
+
+}
+
+@Module
+@InstallIn(SingletonComponent::class)
+object NetworkModule {
+
+ @Provides
+ @Singleton
+ fun provideChuckerInterceptor(@ApplicationContext context: Context): ChuckerInterceptor {
+ return ChuckerInterceptor.Builder(context).build()
+ }
+
+ @Provides
+ @Singleton
+ fun providesNetworkJson(): Json = Json { ignoreUnknownKeys = true }
+
+ @Provides
+ @Singleton
+ fun provideOkHttpClient(
+ chuckerInterceptor: ChuckerInterceptor,
+ json: Json
+ ) = HttpClientFactory(engine = OkHttp, json = json) {
+ addInterceptor(chuckerInterceptor)
+ }
+
+}
\ No newline at end of file
diff --git a/core/network/src/main/java/com/shindra/chargemap/core/network/sources/NinjaDataSource.kt b/core/network/src/main/java/com/shindra/chargemap/core/network/sources/NinjaDataSource.kt
new file mode 100644
index 0000000..e3d31ce
--- /dev/null
+++ b/core/network/src/main/java/com/shindra/chargemap/core/network/sources/NinjaDataSource.kt
@@ -0,0 +1,41 @@
+package com.shindra.chargemap.core.network.sources
+
+import com.shindra.chargemap.core.network.bo.NetworkAirplane
+import io.ktor.client.HttpClient
+import io.ktor.client.call.body
+import io.ktor.client.request.get
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import javax.inject.Inject
+
+interface NinjaDataSource {
+ suspend fun planeByEngineType(type: String): List
+ suspend fun planeByManufacturerAndModel(manufacturer: String, model: String): NetworkAirplane?
+}
+
+class NinjaDataSourceNetwork @Inject constructor(
+ private val client: HttpClient
+) : NinjaDataSource {
+
+ override suspend fun planeByEngineType(type: String): List =
+ withContext(Dispatchers.IO) {
+ client.get {
+ url {
+ parameters.append("engine_type", type)
+ parameters.append("limit", "30")
+ }
+ }.body()
+ }
+
+ override suspend fun planeByManufacturerAndModel(
+ manufacturer: String,
+ model: String
+ ): NetworkAirplane? = withContext(Dispatchers.IO) {
+ client.get {
+ url {
+ parameters.append("manufacturer", manufacturer)
+ parameters.append("model", model)
+ }
+ }.body>().firstOrNull()
+ }
+}
\ No newline at end of file
diff --git a/core/network/src/main/res/values/urls.xml b/core/network/src/main/res/values/urls.xml
new file mode 100644
index 0000000..2ec06b7
--- /dev/null
+++ b/core/network/src/main/res/values/urls.xml
@@ -0,0 +1,7 @@
+
+
+
+ api.api-ninjas.com/v1/aircraft
+
+
+
\ No newline at end of file
diff --git a/core/network/src/test/java/com/shindra/chargemap/core/network/NetworkUnitTest.kt b/core/network/src/test/java/com/shindra/chargemap/core/network/NetworkUnitTest.kt
new file mode 100644
index 0000000..ec9635e
--- /dev/null
+++ b/core/network/src/test/java/com/shindra/chargemap/core/network/NetworkUnitTest.kt
@@ -0,0 +1,144 @@
+package com.shindra.chargemap.core.network
+
+import com.shindra.chargemap.core.network.sources.NinjaDataSourceNetwork
+import io.ktor.client.engine.HttpClientEngineFactory
+import io.ktor.client.engine.mock.MockEngine
+import io.ktor.client.engine.mock.MockEngineConfig
+import io.ktor.client.engine.mock.respond
+import io.ktor.http.HttpHeaders
+import io.ktor.http.HttpStatusCode
+import io.ktor.http.headersOf
+import io.ktor.utils.io.ByteReadChannel
+import kotlinx.coroutines.test.runTest
+import kotlinx.serialization.json.Json
+import org.junit.Test
+
+import org.junit.Assert.*
+
+class NetworkUnitTest {
+
+ private val testData by lazy { TestData() }
+
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+
+
+ @Test
+ fun `test aircraft fetching`() = runTest {
+ val mockEngine = object : HttpClientEngineFactory {
+ override fun create(block: MockEngineConfig.() -> Unit) = MockEngine {
+ //Check AuthCookie
+ assert(it.headers.contains("X-Api-Key"))
+
+ with(it.url.parameters) {
+ assert(contains("engine_type"))
+ assert(contains("limit"))
+ assert(get("limit") == "30")
+ assert(get("engine_type") == "jet")
+ }
+
+ respond(
+ content = ByteReadChannel(testData.planesJson),
+ status = HttpStatusCode.OK,
+ headers = headersOf(HttpHeaders.ContentType, "application/json")
+ )
+
+ }
+
+ }
+
+ val dataSource = NinjaDataSourceNetwork(HttpClientFactory(mockEngine, Json, {}))
+ val r = dataSource.planeByEngineType("jet")
+ assert(r.size == 3)
+
+ }
+
+ @Test
+ fun `test aircraft details fetching`() = runTest {
+ val mockEngine = object : HttpClientEngineFactory {
+ override fun create(block: MockEngineConfig.() -> Unit) = MockEngine {
+ //Check AuthCookie
+ assert(it.headers.contains("X-Api-Key"))
+
+ with(it.url.parameters) {
+ assert(contains("manufacturer"))
+ assert(contains("model"))
+ assert(get("manufacturer") == "airbus")
+ assert(get("model") == "a350")
+ }
+ respond(
+ content = ByteReadChannel(testData.detailPlaneJson),
+ status = HttpStatusCode.OK,
+ headers = headersOf(HttpHeaders.ContentType, "application/json")
+ )
+ }
+ }
+
+ val dataSource = NinjaDataSourceNetwork(HttpClientFactory(mockEngine, Json, {}))
+ val r = dataSource.planeByManufacturerAndModel("airbus", "a350")
+ assert(r != null)
+ if (r != null) {
+ assert(r.manufacturer == "Boeing")
+ assert(r.model == "737 Max 7")
+ }
+ }
+}
+
+private class TestData {
+ val planesJson = "[\n" +
+ " {\n" +
+ " \"manufacturer\": \"Boeing\",\n" +
+ " \"model\": \"737 Max 7\",\n" +
+ " \"engine_type\": \"Jet\",\n" +
+ " \"max_speed_knots\": \"547\",\n" +
+ " \"ceiling_ft\": \"41000\",\n" +
+ " \"gross_weight_lbs\": \"177000\",\n" +
+ " \"length_ft\": \"116.7\",\n" +
+ " \"height_ft\": \"40.3\",\n" +
+ " \"wing_span_ft\": \"117.8\",\n" +
+ " \"range_nautical_miles\": \"3850\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"manufacturer\": \"Airbus\",\n" +
+ " \"model\": \"A318\",\n" +
+ " \"engine_type\": \"Jet\",\n" +
+ " \"max_speed_knots\": \"547\",\n" +
+ " \"ceiling_ft\": \"41000\",\n" +
+ " \"gross_weight_lbs\": \"149900\",\n" +
+ " \"length_ft\": \"103.0\",\n" +
+ " \"height_ft\": \"41.3\",\n" +
+ " \"wing_span_ft\": \"111.9\",\n" +
+ " \"range_nautical_miles\": \"3100\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"manufacturer\": \"Airbus\",\n" +
+ " \"model\": \"A319\",\n" +
+ " \"engine_type\": \"Jet\",\n" +
+ " \"max_speed_knots\": \"547\",\n" +
+ " \"ceiling_ft\": \"41000\",\n" +
+ " \"gross_weight_lbs\": \"166400\",\n" +
+ " \"length_ft\": \"110.9\",\n" +
+ " \"height_ft\": \"38.7\",\n" +
+ " \"wing_span_ft\": \"117.5\",\n" +
+ " \"range_nautical_miles\": \"3700\"\n" +
+ " }\n" +
+ "]"
+
+ val detailPlaneJson = "\n" +
+ "[\n" +
+ " {\n" +
+ " \"manufacturer\": \"Boeing\",\n" +
+ " \"model\": \"737 Max 7\",\n" +
+ " \"engine_type\": \"Jet\",\n" +
+ " \"max_speed_knots\": \"547\",\n" +
+ " \"ceiling_ft\": \"41000\",\n" +
+ " \"gross_weight_lbs\": \"177000\",\n" +
+ " \"length_ft\": \"116.7\",\n" +
+ " \"height_ft\": \"40.3\",\n" +
+ " \"wing_span_ft\": \"117.8\",\n" +
+ " \"range_nautical_miles\": \"3850\"\n" +
+ " }\n" +
+ "]"
+}
\ No newline at end of file
diff --git a/core/tests/.gitignore b/core/tests/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/core/tests/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/tests/build.gradle.kts b/core/tests/build.gradle.kts
new file mode 100644
index 0000000..c3d898b
--- /dev/null
+++ b/core/tests/build.gradle.kts
@@ -0,0 +1,19 @@
+plugins {
+ id("shindra.library")
+}
+
+android {
+ namespace = "com.shindra.chargemap.core.tests"
+}
+
+dependencies {
+
+ api(libs.junit4)
+ api(libs.mockk)
+ api(libs.kotlinx.coroutines.test)
+
+ api(libs.junit)
+ api(libs.androidx.test.espresso.core)
+ api(libs.mockk.instrumental)
+ api(libs.kotlinx.coroutines.test)
+}
\ No newline at end of file
diff --git a/core/tests/consumer-rules.pro b/core/tests/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/core/tests/proguard-rules.pro b/core/tests/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/core/tests/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/core/tests/src/androidTest/java/com/shindra/chargemap/core/tests/ExampleInstrumentedTest.kt b/core/tests/src/androidTest/java/com/shindra/chargemap/core/tests/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..c86a82c
--- /dev/null
+++ b/core/tests/src/androidTest/java/com/shindra/chargemap/core/tests/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.shindra.chargemap.core.tests
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.shindra.chargemap.core.tests.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/core/tests/src/main/AndroidManifest.xml b/core/tests/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a5918e6
--- /dev/null
+++ b/core/tests/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/core/tests/src/main/java/com/shindra/chargemap/core/tests/DispatcherUnitRules.kt b/core/tests/src/main/java/com/shindra/chargemap/core/tests/DispatcherUnitRules.kt
new file mode 100644
index 0000000..13cd9c8
--- /dev/null
+++ b/core/tests/src/main/java/com/shindra/chargemap/core/tests/DispatcherUnitRules.kt
@@ -0,0 +1,16 @@
+package com.shindra.chargemap.core.tests
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.setMain
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class MainDispatcherRule(private val dispatcher: TestDispatcher = StandardTestDispatcher()): TestWatcher() {
+ override fun starting(description: Description) = Dispatchers.setMain(dispatcher)
+ override fun finished(description: Description) = Dispatchers.resetMain()
+}
\ No newline at end of file
diff --git a/core/tests/src/test/java/com/shindra/chargemap/core/tests/ExampleUnitTest.kt b/core/tests/src/test/java/com/shindra/chargemap/core/tests/ExampleUnitTest.kt
new file mode 100644
index 0000000..dfedd3c
--- /dev/null
+++ b/core/tests/src/test/java/com/shindra/chargemap/core/tests/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.shindra.chargemap.core.tests
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/feature/planeDetails/.gitignore b/feature/planeDetails/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/feature/planeDetails/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/feature/planeDetails/build.gradle.kts b/feature/planeDetails/build.gradle.kts
new file mode 100644
index 0000000..5b8a74d
--- /dev/null
+++ b/feature/planeDetails/build.gradle.kts
@@ -0,0 +1,9 @@
+plugins {
+ id("shindra.feature")
+ id("shindra.library.compose")
+}
+
+android {
+ namespace = "com.shindra.chargemap.feature.planedetails"
+}
+
diff --git a/feature/planeDetails/consumer-rules.pro b/feature/planeDetails/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/feature/planeDetails/proguard-rules.pro b/feature/planeDetails/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/feature/planeDetails/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/feature/planeDetails/src/androidTest/java/com/shindra/chargemap/feature/planedetails/ExampleInstrumentedTest.kt b/feature/planeDetails/src/androidTest/java/com/shindra/chargemap/feature/planedetails/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..5a42a11
--- /dev/null
+++ b/feature/planeDetails/src/androidTest/java/com/shindra/chargemap/feature/planedetails/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.shindra.chargemap.feature.planedetails
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.shindra.chargemap.feature.planedetails.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/feature/planeDetails/src/main/AndroidManifest.xml b/feature/planeDetails/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..44008a4
--- /dev/null
+++ b/feature/planeDetails/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/feature/planeDetails/src/main/java/com/shindra/chargemap/feature/planedetails/PlaneDetailScreen.kt b/feature/planeDetails/src/main/java/com/shindra/chargemap/feature/planedetails/PlaneDetailScreen.kt
new file mode 100644
index 0000000..3642804
--- /dev/null
+++ b/feature/planeDetails/src/main/java/com/shindra/chargemap/feature/planedetails/PlaneDetailScreen.kt
@@ -0,0 +1,61 @@
+package com.shindra.chargemap.feature.planedetails
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.shindra.chargemap.core.designsystem.components.FullScreenErrorScreen
+import com.shindra.chargemap.core.designsystem.components.KeyValueTextConfig
+import com.shindra.chargemap.core.designsystem.components.KeyValueUnit
+import com.shindra.chargemap.core.designsystem.components.UiStateContent
+
+@Composable
+internal fun PlaneDetailScreenRoute(
+ onTitleChange: (String) -> Unit,
+ manufacturer: String,
+ model: String,
+ viewModel: PlaneDetailsViewModel = hiltViewModel()
+) {
+
+ val planesUiState by viewModel.planeDetailsState.collectAsStateWithLifecycle()
+
+ LaunchedEffect(key1 = "$manufacturer$model") {
+ onTitleChange("$manufacturer $model")
+ }
+
+ PlaneDetailScreen(
+ planeDetail = planesUiState,
+ onRetry = { viewModel.planeDetails() }
+ )
+}
+
+
+@Composable
+private fun PlaneDetailScreen(
+ planeDetail: PlaneDetailUiState,
+ onRetry: () -> Unit
+) {
+ UiStateContent(
+ onError = { FullScreenErrorScreen(onRetry) },
+ state = planeDetail
+ ) {
+ Column(modifier = Modifier.padding(horizontal = 8.dp)) {
+ it.forEach { d ->
+ KeyValueUnit(
+ key = stringResource(id = d.key),
+ value = d.value,
+ unit = d.unit?.let { stringResource(it) },
+ keyTextConfig = KeyValueTextConfig.defaultText(fontWeight = FontWeight.ExtraBold),
+ modifier = Modifier.padding(vertical = 8.dp)
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/feature/planeDetails/src/main/java/com/shindra/chargemap/feature/planedetails/PlaneDetailsViewModel.kt b/feature/planeDetails/src/main/java/com/shindra/chargemap/feature/planedetails/PlaneDetailsViewModel.kt
new file mode 100644
index 0000000..080ffef
--- /dev/null
+++ b/feature/planeDetails/src/main/java/com/shindra/chargemap/feature/planedetails/PlaneDetailsViewModel.kt
@@ -0,0 +1,118 @@
+package com.shindra.chargemap.feature.planedetails
+
+import androidx.annotation.StringRes
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.chargemap.core.domain.usecases.PlaneDetailsUseCase
+import com.shindra.chargemap.core.designsystem.components.UiState
+import com.shindra.chargemap.core.designsystem.components.asUiState
+import com.shindra.chargemap.core.model.ModelDetailAirplane
+import com.shindra.chargemap.feature.planedetails.navigation.manufacturerArgumentKey
+import com.shindra.chargemap.feature.planedetails.navigation.modelArgumentKey
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+internal typealias PlaneDetailUiState = UiState>
+
+@HiltViewModel
+internal class PlaneDetailsViewModel @Inject constructor(
+ private val useCase: PlaneDetailsUseCase,
+ savedStateHandle: SavedStateHandle
+) : ViewModel() {
+
+ private val manufacturer = savedStateHandle.get(manufacturerArgumentKey).orEmpty()
+ private val model = savedStateHandle.get(modelArgumentKey).orEmpty()
+
+ private var _planeDetailsState = MutableStateFlow(UiState.Loading)
+
+ val planeDetailsState = _planeDetailsState.stateIn(
+ scope = viewModelScope,
+ started = SharingStarted.WhileSubscribed(5_000),
+ initialValue = UiState.Loading
+ )
+
+ private var planeDetailJob: Job? = null
+ init {
+ planeDetails()
+ }
+
+ fun planeDetails() {
+ planeDetailJob?.cancel()
+ _planeDetailsState.value = UiState.Loading
+ planeDetailJob = viewModelScope.launch {
+ network().collect {
+ _planeDetailsState.value = it
+ }
+ }
+ }
+
+
+ private fun network() = useCase(manufacturer, model).map {
+ it.toUiModel()
+ }.asUiState()
+
+}
+
+internal data class PlaneDetailUi(
+ @StringRes val key: Int,
+ val value: String,
+ @StringRes val unit: Int? = null
+)
+
+
+private fun ModelDetailAirplane.toUiModel(): List = buildList {
+ add(
+ PlaneDetailUi(
+ key = R.string.plane_detail_manufacturer_title,
+ value = manufacturer
+ )
+ )
+ add(
+ PlaneDetailUi(
+ key = R.string.plane_detail_model_title,
+ value = model
+ )
+ )
+ add(
+ PlaneDetailUi(
+ key = R.string.plane_detail_engine_title,
+ value = engineType
+ )
+ )
+
+ engineThrustLbFt?.let {
+ add(
+ PlaneDetailUi(
+ key = R.string.plane_detail_engine_power_title,
+ value = it,
+ unit = R.string.power_unit
+ )
+ )
+ }
+
+ maxSpeedKt?.let {
+ add(
+ PlaneDetailUi(
+ key = R.string.plane_detail_vmax_title,
+ value = it,
+ unit = R.string.speed_limit
+ )
+ )
+ }
+ ceilingFt?.let {
+ add(
+ PlaneDetailUi(
+ key = R.string.plane_detail_plafond_max_title,
+ value = it,
+ unit = R.string.ft_unit
+ )
+ )
+ }
+}
\ No newline at end of file
diff --git a/feature/planeDetails/src/main/java/com/shindra/chargemap/feature/planedetails/navigation/Navigation.kt b/feature/planeDetails/src/main/java/com/shindra/chargemap/feature/planedetails/navigation/Navigation.kt
new file mode 100644
index 0000000..8fa8a3f
--- /dev/null
+++ b/feature/planeDetails/src/main/java/com/shindra/chargemap/feature/planedetails/navigation/Navigation.kt
@@ -0,0 +1,42 @@
+package com.shindra.chargemap.feature.planedetails.navigation
+
+import androidx.navigation.NavController
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavOptions
+import androidx.navigation.NavType
+import androidx.navigation.compose.composable
+import androidx.navigation.navArgument
+import com.shindra.chargemap.feature.planedetails.PlaneDetailScreenRoute
+
+
+internal const val manufacturerArgumentKey = "manufacturer"
+internal const val modelArgumentKey = "model"
+const val planesDetailScreenRouteId = "planes_detail_screen_route/{$manufacturerArgumentKey}/{$modelArgumentKey}"
+private const val planesDetailScreenRouteIdNoArg = "planes_detail_screen_route"
+
+
+fun NavController.navigateToPlaneDetails(
+ manufacturer: String,
+ model: String,
+ navOptions: NavOptions? = null
+) {
+ this.navigate("$planesDetailScreenRouteIdNoArg/$manufacturer/$model", navOptions)
+}
+
+fun NavGraphBuilder.planeDetails(
+ onTitleChange: (String) -> Unit
+) {
+ composable(
+ route = planesDetailScreenRouteId,
+ arguments = listOf(
+ navArgument(manufacturerArgumentKey) { type = NavType.StringType },
+ navArgument(modelArgumentKey) { type = NavType.StringType }
+ )
+ ) { backStackEntry ->
+ PlaneDetailScreenRoute(
+ onTitleChange = onTitleChange,
+ manufacturer = backStackEntry.arguments?.getString(manufacturerArgumentKey).orEmpty(),
+ model = backStackEntry.arguments?.getString(modelArgumentKey).orEmpty()
+ )
+ }
+}
\ No newline at end of file
diff --git a/feature/planeDetails/src/main/res/values/strings.xml b/feature/planeDetails/src/main/res/values/strings.xml
new file mode 100644
index 0000000..2d8b66d
--- /dev/null
+++ b/feature/planeDetails/src/main/res/values/strings.xml
@@ -0,0 +1,22 @@
+
+
+
+ Avions
+
+ Constructeur
+ Model
+ Type de moteur
+ Puissance moteur
+ Vitesse maximale
+ Plafond de croissière
+ Masse à vide
+
+
+ ft
+ lb/ft
+ lbs
+ knots
+
+
+
+
\ No newline at end of file
diff --git a/feature/planeDetails/src/test/java/com/shindra/chargemap/feature/planedetails/ViewModelTest.kt b/feature/planeDetails/src/test/java/com/shindra/chargemap/feature/planedetails/ViewModelTest.kt
new file mode 100644
index 0000000..99c5e70
--- /dev/null
+++ b/feature/planeDetails/src/test/java/com/shindra/chargemap/feature/planedetails/ViewModelTest.kt
@@ -0,0 +1,79 @@
+package com.shindra.chargemap.feature.planedetails
+
+import androidx.lifecycle.SavedStateHandle
+import com.chargemap.core.domain.usecases.PlaneDetailsUseCase
+import com.shindra.chargemap.core.designsystem.components.UiState
+import com.shindra.chargemap.core.model.ModelDetailAirplane
+import com.shindra.chargemap.feature.planedetails.navigation.manufacturerArgumentKey
+import com.shindra.chargemap.feature.planedetails.navigation.modelArgumentKey
+import com.shindra.chargemap.core.tests.MainDispatcherRule
+import io.mockk.every
+import io.mockk.mockk
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+import org.junit.Rule
+import kotlin.time.Duration.Companion.seconds
+
+class ViewModelTest {
+
+ @get:Rule
+ val mainDispatcherRule = MainDispatcherRule(UnconfinedTestDispatcher())
+ private var mockedUc: PlaneDetailsUseCase = mockk()
+
+ @Test
+ fun success() = runTest {
+ every { mockedUc.invoke("Test", "Test1") } returns flowOf(
+ ModelDetailAirplane(
+ manufacturer = "Airbus",
+ model = "A350",
+ engineType = "",
+ engineThrustLbFt = null,
+ maxSpeedKt = null,
+ cruiseSpeedKt = null,
+ ceilingFt = null,
+ takeoffGroundRunFt = null,
+ landingGroundRollFt = null,
+ grossWeightLbs = null,
+ emptyWeightLbs = null,
+ lengthFt = null,
+ heightFt = null,
+ wingspanFt = null,
+ rangeNauticalMiles = null
+ )
+ )
+
+ val saveHandler = mockk()
+ every { saveHandler.get(manufacturerArgumentKey) } returns "Test"
+ every { saveHandler.get(modelArgumentKey) } returns "Test1"
+
+ val vm = PlaneDetailsViewModel(mockedUc, saveHandler)
+
+ val j = launch() {
+ vm.planeDetailsState.collect()
+ }
+
+ assert(vm.planeDetailsState.value is UiState.Loading)
+ delay(2.seconds)
+ assert(vm.planeDetailsState.value is UiState.Success)
+
+ val value = vm.planeDetailsState.value
+
+ if (value is UiState.Success>) {
+ assert(value.value.size == 3)
+
+ with(value.value.first()) {
+ assert(key == R.string.plane_detail_manufacturer_title)
+ assert(this.value == "Airbus")
+ assert(this.unit == null)
+ }
+ }
+ j.cancel()
+ }
+
+}
\ No newline at end of file
diff --git a/feature/planes/.gitignore b/feature/planes/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/feature/planes/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/feature/planes/build.gradle.kts b/feature/planes/build.gradle.kts
new file mode 100644
index 0000000..ac4a67f
--- /dev/null
+++ b/feature/planes/build.gradle.kts
@@ -0,0 +1,8 @@
+plugins {
+ id("shindra.feature")
+ id("shindra.library.compose")
+}
+
+android {
+ namespace = "com.shindra.chargemap.feature.planes"
+}
\ No newline at end of file
diff --git a/feature/planes/consumer-rules.pro b/feature/planes/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/feature/planes/proguard-rules.pro b/feature/planes/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/feature/planes/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/feature/planes/src/androidTest/java/com/shindra/chargemap/feature/planes/ExampleInstrumentedTest.kt b/feature/planes/src/androidTest/java/com/shindra/chargemap/feature/planes/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..72313e0
--- /dev/null
+++ b/feature/planes/src/androidTest/java/com/shindra/chargemap/feature/planes/ExampleInstrumentedTest.kt
@@ -0,0 +1,25 @@
+package com.shindra.chargemap.feature.planes
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.shindra.chargemap.feature.planes.test", appContext.packageName)
+ }
+}
+
diff --git a/feature/planes/src/main/AndroidManifest.xml b/feature/planes/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a5918e6
--- /dev/null
+++ b/feature/planes/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/feature/planes/src/main/java/com/shindra/chargemap/feature/planes/PlanesScreen.kt b/feature/planes/src/main/java/com/shindra/chargemap/feature/planes/PlanesScreen.kt
new file mode 100644
index 0000000..868010f
--- /dev/null
+++ b/feature/planes/src/main/java/com/shindra/chargemap/feature/planes/PlanesScreen.kt
@@ -0,0 +1,76 @@
+package com.shindra.chargemap.feature.planes
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.shindra.chargemap.core.designsystem.components.FullScreenErrorScreen
+import com.shindra.chargemap.core.designsystem.components.UiStateContent
+import com.shindra.chargemap.feature.planes.compose.PlaneCard
+import com.shindra.chargemap.feature.planes.compose.PlaneHeader
+
+typealias OnPlaneClick = (manufacturer: String, model: String) -> Unit
+
+@Composable
+internal fun PlanesScreenRoute(
+ onTitleChange: (String) -> Unit,
+ onPlaneClick: OnPlaneClick,
+ viewModel: PlanesViewModel = hiltViewModel()
+) {
+
+ val planesUiState by viewModel.planesByCategoryState.collectAsStateWithLifecycle()
+
+ val title = stringResource(id = R.string.plane_toolbar_title)
+ LaunchedEffect(key1 = title) {
+ onTitleChange(title)
+ }
+
+ PlanesScreen(
+ planesByCategory = planesUiState,
+ onPlaneClick = onPlaneClick,
+ onRetry = { viewModel.planes() }
+ )
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+internal fun PlanesScreen(
+ planesByCategory: PlaneUi,
+ onPlaneClick: OnPlaneClick,
+ onRetry: () -> Unit
+) {
+ UiStateContent(
+ onError = { FullScreenErrorScreen(onRetry) },
+ state = planesByCategory
+ ) { pC ->
+ LazyColumn(
+ contentPadding = PaddingValues(horizontal = 8.dp, vertical = 8.dp),
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+
+ pC.forEach {
+ stickyHeader {
+ PlaneHeader(it.title)
+ }
+ items(it.items) {
+ PlaneCard(
+ manufacturer = it.manufacturer,
+ model = it.model,
+ imageUrl = it.url,
+ onClick = {
+ onPlaneClick(it.manufacturer, it.model)
+ }
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/feature/planes/src/main/java/com/shindra/chargemap/feature/planes/PlanesViewModel.kt b/feature/planes/src/main/java/com/shindra/chargemap/feature/planes/PlanesViewModel.kt
new file mode 100644
index 0000000..7618458
--- /dev/null
+++ b/feature/planes/src/main/java/com/shindra/chargemap/feature/planes/PlanesViewModel.kt
@@ -0,0 +1,97 @@
+package com.shindra.chargemap.feature.planes
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.chargemap.core.domain.usecases.PlaneCategory
+import com.chargemap.core.domain.usecases.PlaneCategoryType
+import com.chargemap.core.domain.usecases.PlaneUseCase
+import com.shindra.chargemap.core.designsystem.components.UiState
+import com.shindra.chargemap.core.designsystem.components.asUiState
+import com.shindra.chargemap.feature.planes.bo.ListModel
+import com.shindra.chargemap.feature.planes.bo.UiAirplane
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+internal typealias PlaneUi = UiState>
+
+@HiltViewModel
+internal class PlanesViewModel @Inject constructor(
+ private val useCase: PlaneUseCase
+) : ViewModel() {
+
+ private var _planesByCategoryState = MutableStateFlow(UiState.Loading)
+ val planesByCategoryState: StateFlow = _planesByCategoryState
+ .stateIn(
+ scope = viewModelScope,
+ started = SharingStarted.WhileSubscribed(5_000),
+ initialValue = UiState.Loading
+ )
+
+ private var planeJob: Job? = null
+
+ init {
+ planes()
+ }
+
+ fun planes() {
+ planeJob?.cancel()
+
+ _planesByCategoryState.value = UiState.Loading
+ viewModelScope.launch {
+ network().collect {
+ _planesByCategoryState.value = it
+ }
+ }
+ }
+
+ private fun network() = useCase()
+ .map {
+ it.toUiModel()
+ }
+ .asUiState()
+}
+
+private fun List.toUiModel(): List {
+ return map {
+ val title: Int
+ val url: String
+
+ when (it.category) {
+ PlaneCategoryType.Jet -> {
+ title = R.string.plane_jet_category_title
+ url =
+ "https://firebasestorage.googleapis.com/v0/b/acaf-sxb-3dc74.appspot.com/o/AirFranceA350-5.jpg?alt=media&token=6799c2fb-df68-446e-95bc-3898abb901e7"
+ }
+
+ PlaneCategoryType.Piston -> {
+ title = R.string.plane_piston_category_title
+ url =
+ "https://firebasestorage.googleapis.com/v0/b/acaf-sxb-3dc74.appspot.com/o/AirFranceA350-1.jpg?alt=media&token=42e442cf-0163-4ecb-b01e-38a15794031e"
+ }
+
+ PlaneCategoryType.PropJet -> {
+ title = R.string.plane_propjet_category_title
+ url =
+ "https://firebasestorage.googleapis.com/v0/b/acaf-sxb-3dc74.appspot.com/o/AirFranceA350-3.jpg?alt=media&token=bf458a38-ede6-43e6-94d5-54fda555a856"
+ }
+ }
+
+ ListModel(
+ title = title,
+ items = it.planes.map {
+ UiAirplane(
+ url = url,
+ model = it.model,
+ manufacturer = it.manufacturer
+ )
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/feature/planes/src/main/java/com/shindra/chargemap/feature/planes/bo/SectionItem.kt b/feature/planes/src/main/java/com/shindra/chargemap/feature/planes/bo/SectionItem.kt
new file mode 100644
index 0000000..044a90c
--- /dev/null
+++ b/feature/planes/src/main/java/com/shindra/chargemap/feature/planes/bo/SectionItem.kt
@@ -0,0 +1,11 @@
+package com.shindra.chargemap.feature.planes.bo
+
+import androidx.annotation.StringRes
+
+internal data class ListModel(@StringRes val title: Int, val items: List)
+
+internal data class UiAirplane(
+ val url: String,
+ val model: String,
+ val manufacturer: String
+)
\ No newline at end of file
diff --git a/feature/planes/src/main/java/com/shindra/chargemap/feature/planes/compose/PlaneCard.kt b/feature/planes/src/main/java/com/shindra/chargemap/feature/planes/compose/PlaneCard.kt
new file mode 100644
index 0000000..cd98cf9
--- /dev/null
+++ b/feature/planes/src/main/java/com/shindra/chargemap/feature/planes/compose/PlaneCard.kt
@@ -0,0 +1,101 @@
+package com.shindra.chargemap.feature.planes.compose
+
+import android.content.res.Configuration
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Card
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.shindra.chargemap.core.designsystem.components.CoilNetworkImage
+import com.shindra.chargemap.core.designsystem.theme.ChargeMapTheme
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+internal fun PlaneCard(
+ manufacturer: String,
+ model: String,
+ imageUrl: String,
+ onClick: () -> Unit
+) {
+
+ Card(onClick = onClick) {
+ CoilNetworkImage(
+ url = imageUrl,
+ modifier = Modifier
+ .height(150.dp)
+ .fillMaxWidth()
+ .padding(bottom = 8.dp),
+ contentScale = ContentScale.Crop,
+ shape = MaterialTheme.shapes.medium
+ )
+ PlaneBanner(
+ modifier = Modifier
+ .padding(horizontal = 8.dp)
+ .padding(bottom = 8.dp),
+ manufacturer = manufacturer,
+ model = model
+ )
+
+ }
+}
+
+@Composable
+private fun PlaneBanner(
+ modifier: Modifier = Modifier,
+ manufacturer: String,
+ model: String
+) {
+ Column(
+ modifier = modifier.fillMaxWidth()
+ ) {
+
+ Text(
+ text = manufacturer,
+ style = MaterialTheme.typography.titleLarge,
+ fontWeight = FontWeight.Bold
+ )
+ Text(
+ text = model,
+ style = MaterialTheme.typography.bodySmall
+ )
+ }
+}
+
+@Composable
+@Preview
+private fun PlaneCardLightPreview() {
+ ChargeMapTheme {
+ Column {
+ PlaneCard(
+ manufacturer = "Airbus",
+ model = "A320",
+ imageUrl = "",
+ onClick = {}
+ )
+ }
+ }
+}
+
+@Composable
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
+private fun PlaneCardDarkPreview() {
+ ChargeMapTheme {
+ Column {
+ PlaneCard(
+ manufacturer = "Airbus",
+ model = "A350",
+ imageUrl = "",
+ onClick = {},
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/feature/planes/src/main/java/com/shindra/chargemap/feature/planes/compose/PlaneCategoryType.kt b/feature/planes/src/main/java/com/shindra/chargemap/feature/planes/compose/PlaneCategoryType.kt
new file mode 100644
index 0000000..297c461
--- /dev/null
+++ b/feature/planes/src/main/java/com/shindra/chargemap/feature/planes/compose/PlaneCategoryType.kt
@@ -0,0 +1,46 @@
+package com.shindra.chargemap.feature.planes.compose
+
+import android.content.res.Configuration
+import androidx.annotation.StringRes
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.shindra.chargemap.core.designsystem.theme.ChargeMapTheme
+import com.shindra.chargemap.feature.planes.R
+
+@Composable
+fun PlaneHeader(@StringRes title : Int) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(MaterialTheme.colorScheme.background)
+ .padding(vertical = 8.dp)
+ ) {
+ Text(text = stringResource(id = title), Modifier.padding(horizontal = 8.dp) )
+ }
+}
+
+@Preview
+@Composable
+fun PlaneHeaderLigthPreview(){
+ ChargeMapTheme {
+ PlaneHeader(title = R.string.plane_propjet_category_title)
+ }
+
+}
+
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+fun PlaneHeaderDarkPreview(){
+ ChargeMapTheme {
+ PlaneHeader(title = R.string.plane_propjet_category_title)
+ }
+}
\ No newline at end of file
diff --git a/feature/planes/src/main/java/com/shindra/chargemap/feature/planes/navigation/PlanesScreenNavigation.kt b/feature/planes/src/main/java/com/shindra/chargemap/feature/planes/navigation/PlanesScreenNavigation.kt
new file mode 100644
index 0000000..d74aadd
--- /dev/null
+++ b/feature/planes/src/main/java/com/shindra/chargemap/feature/planes/navigation/PlanesScreenNavigation.kt
@@ -0,0 +1,28 @@
+package com.shindra.chargemap.feature.planes.navigation
+
+import androidx.navigation.NavController
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavOptions
+import androidx.navigation.compose.composable
+import com.shindra.chargemap.feature.planes.OnPlaneClick
+import com.shindra.chargemap.feature.planes.PlanesScreenRoute
+
+const val planesScreenRouteId = "planes_screen_route"
+
+fun NavController.navigateToPlanesFeature(navOptions: NavOptions? = null) {
+ this.navigate(planesScreenRouteId, navOptions)
+}
+
+fun NavGraphBuilder.planesScreen(
+ onTitleChange: (String) -> Unit,
+ onMassAndBalanceClick: OnPlaneClick,
+) {
+ composable(
+ route = planesScreenRouteId
+ ) {
+ PlanesScreenRoute(
+ onTitleChange = onTitleChange,
+ onPlaneClick = onMassAndBalanceClick
+ )
+ }
+}
\ No newline at end of file
diff --git a/feature/planes/src/main/res/values/strings.xml b/feature/planes/src/main/res/values/strings.xml
new file mode 100644
index 0000000..12ec9b7
--- /dev/null
+++ b/feature/planes/src/main/res/values/strings.xml
@@ -0,0 +1,10 @@
+
+
+
+ Avions
+
+ Turboréacteur
+ Moteur à piston
+ Turbopropulseur
+
+
\ No newline at end of file
diff --git a/feature/planes/src/test/java/com/shindra/chargemap/feature/planes/ViewModelTest.kt b/feature/planes/src/test/java/com/shindra/chargemap/feature/planes/ViewModelTest.kt
new file mode 100644
index 0000000..f025db3
--- /dev/null
+++ b/feature/planes/src/test/java/com/shindra/chargemap/feature/planes/ViewModelTest.kt
@@ -0,0 +1,58 @@
+package com.shindra.chargemap.feature.planes
+
+import com.chargemap.core.domain.usecases.PlaneCategory
+import com.chargemap.core.domain.usecases.PlaneCategoryType
+import com.chargemap.core.domain.usecases.PlaneUseCase
+import com.shindra.chargemap.core.designsystem.components.UiState
+import com.shindra.chargemap.feature.planes.bo.ListModel
+import com.shindra.chargemap.core.tests.MainDispatcherRule
+import io.mockk.every
+import io.mockk.mockk
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Rule
+import org.junit.Test
+import kotlin.time.Duration.Companion.seconds
+
+class ViewModelTest {
+
+ @get:Rule
+ val mainDispatcherRule = MainDispatcherRule(UnconfinedTestDispatcher())
+ private var mockedUc: PlaneUseCase = mockk()
+
+ @Test
+ fun success() = runTest {
+ every { mockedUc.invoke() } returns flowOf(
+ listOf(
+ PlaneCategory(
+ category = PlaneCategoryType.Jet,
+ planes = emptyList()
+ )
+ )
+ )
+
+ val vm = PlanesViewModel(mockedUc)
+
+ val j = launch() {
+ vm.planesByCategoryState.collect()
+ }
+
+
+ assert(vm.planesByCategoryState.value is UiState.Loading)
+ delay(2.seconds)
+ assert(vm.planesByCategoryState.value is UiState.Success)
+
+ val value = vm.planesByCategoryState.value
+
+ if(value is UiState.Success>){
+ assert(value.value.size == 1)
+ assert(value.value.first().title == R.string.plane_jet_category_title)
+ }
+
+ j.cancel()
+ }
+}
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index f28319e..383815b 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,31 +1,115 @@
[versions]
-agp = "8.1.1"
-kotlin = "1.9.0"
-core-ktx = "1.12.0"
-junit = "4.13.2"
-androidx-test-ext-junit = "1.1.5"
-espresso-core = "3.5.1"
-lifecycle-runtime-ktx = "2.6.2"
-activity-compose = "1.7.2"
-compose-bom = "2023.09.00"
+androidGradlePlugin = "8.1.2"
+androidxActivity = "1.8.0"
+androidxAppCompat = "1.6.1"
+androidxComposeBom = "2023.10.01"
+androidxComposeCompiler = "1.5.3"
+androidxComposeRuntimeTracing = "1.0.0-alpha01"
+androidxCore = "1.12.0"
+androidxEspresso = "3.5.1"
+androidxHiltNavigationCompose = "1.0.0"
+androidxLifecycle = "2.6.2"
+androidxMacroBenchmark = "1.2.0"
+androidxNavigation = "2.7.4"
+androidxWindowManager = "1.1.0"
+androidxTestCore = "1.6.0-alpha02"
+androidxTestExt = "1.1.5"
+androidxTestRunner = "1.5.2"
+androidxTestRules = "1.5.0"
+androidxTracing = "1.1.0"
+androidxUiAutomator = "2.2.0"
+androidxConstraintCompose = "1.0.1"
+
+coil = "2.2.2"
+hilt = "2.48"
+hiltExt = "1.0.0"
+junit4 = "4.13.2"
+kotlin = "1.9.10"
+kotlinxCoroutines = "1.7.3"
+kotlinxDatetime = "0.4.0"
+kotlinxSerializationJson = "1.4.1"
+lottie = "5.2.0"
+ktor = "2.1.2"
+chucker = "3.5.2"
+org-jetbrains-kotlin-android = "1.9.10"
+junit = "1.1.5"
+mockk = "1.13.9"
+material = "1.11.0"
[libraries]
-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" }
-junit = { group = "junit", name = "junit", version.ref = "junit" }
-androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" }
-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" }
-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle-runtime-ktx" }
-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity-compose" }
-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
-ui = { group = "androidx.compose.ui", name = "ui" }
-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
-material3 = { group = "androidx.compose.material3", name = "material3" }
+androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidxActivity" }
+androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidxAppCompat" }
+androidx-benchmark-macro = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version.ref = "androidxMacroBenchmark" }
+androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "androidxComposeBom" }
+androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation" }
+androidx-compose-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout" }
+androidx-compose-material-iconsExtended = { group = "androidx.compose.material", name = "material-icons-extended" }
+androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
+androidx-compose-material = { group = "androidx.compose.material", name = "material" }
+androidx-compose-material3-windowSizeClass = { group = "androidx.compose.material3", name = "material3-window-size-class" }
+androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime" }
+androidx-compose-runtime-livedata = { group = "androidx.compose.runtime", name = "runtime-livedata" }
+androidx-compose-constraint = { group = "androidx.constraintlayout", name = "constraintlayout-compose", version.ref = "androidxConstraintCompose" }
+androidx-compose-ui-test = { group = "androidx.compose.ui", name = "ui-test-junit4" }
+androidx-compose-ui-testManifest = { 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-compose-ui-util = { group = "androidx.compose.ui", name = "ui-util" }
+androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidxCore" }
+androidx-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "androidxHiltNavigationCompose" }
+androidx-lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "androidxLifecycle" }
+androidx-lifecycle-runtimeCompose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", 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-window-manager = { module = "androidx.window:window", version.ref = "androidxWindowManager" }
+androidx-test-core = { group = "androidx.test", name = "core", version.ref = "androidxTestCore" }
+androidx-test-ext = { group = "androidx.test.ext", name = "junit-ktx", version.ref = "androidxTestExt" }
+androidx-test-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "androidxEspresso" }
+androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "androidxTestRunner" }
+androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = "androidxTestRules" }
+androidx-test-uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "androidxUiAutomator" }
+androidx-tracing-ktx = { group = "androidx.tracing", name = "tracing-ktx", version.ref = "androidxTracing" }
+
+coil-kt = { group = "io.coil-kt", name = "coil", version.ref = "coil" }
+coil-kt-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" }
+coil-kt-svg = { group = "io.coil-kt", name = "coil-svg", version.ref = "coil" }
+airbnb-lottie = { group = 'com.airbnb.android', name = "lottie-compose", version.ref = "lottie" }
+hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
+hilt-ext-work = { group = "androidx.hilt", name = "hilt-work", version.ref = "hiltExt" }
+hilt-ext-compiler = { group = "androidx.hilt", name = "hilt-compiler", version.ref = "hiltExt" }
+hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }
+hilt-android-testing = { group = "com.google.dagger", name = "hilt-android-testing", version.ref = "hilt" }
+junit4 = { group = "junit", name = "junit", version.ref = "junit4" }
+kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib-jdk8", version.ref = "kotlin" }
+kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" }
+kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" }
+kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinxDatetime" }
+kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
+ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" }
+ktor-client-android = { group = "io.ktor", name = "ktor-client-android", version.ref = "ktor" }
+ktor-client-serialization = { group = "io.ktor", name = "ktor-client-serialization", version.ref = "ktor" }
+ktor-serialization-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" }
+ktor-client-okhttp = { group = "io.ktor", name = "ktor-client-okhttp", version.ref = "ktor" }
+ktor-client-mock = { group = "io.ktor", name = "ktor-client-mock", version.ref = "ktor" }
+ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktor" }
+chucker-interceptor-debug = { group = "com.github.chuckerteam.chucker", name = "library", version.ref = "chucker" }
+chucker-interceptor-release = { group = "com.github.chuckerteam.chucker", name = "library-no-op", version.ref = "chucker" }
+mockk = {group = "io.mockk", name = "mockk-android", version.ref ="mockk"}
+mockk-instrumental = {group = "io.mockk", name = "mockk-agent", version.ref ="mockk"}
-[plugins]
-androidApplication = { id = "com.android.application", version.ref = "agp" }
-kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
+# Dependencies of the included build-logic
+android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }
+kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" }
+junit = { group = "androidx.test.ext", name = "junit", version.ref = "junit" }
+material = { group = "com.google.android.material", name = "material", version.ref = "material" }
+
+[plugins]
+android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
+android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" }
+android-test = { id = "com.android.test", version.ref = "androidGradlePlugin" }
+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" }
+org-jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "org-jetbrains-kotlin-android" }
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 7322a9f..f349475 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Thu Sep 14 17:06:43 CEST 2023
+#Sat Jan 14 17:33:24 CET 2023
distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
-zipStoreBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
diff --git a/settings.gradle.kts b/settings.gradle.kts
index a8d3dfd..1f92c07 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,4 +1,5 @@
pluginManagement {
+ includeBuild("build-logic")
repositories {
google()
mavenCentral()
@@ -13,6 +14,14 @@ dependencyResolutionManagement {
}
}
-rootProject.name = "Android Test"
+rootProject.name = "ChargeMap"
+
include(":app")
-
\ No newline at end of file
+include(":core:designsystem")
+include(":core:network")
+include(":core:data")
+include(":core:model")
+include(":feature:planes")
+include(":core:domain")
+include(":feature:planeDetails")
+include(":core:tests")