test #1
1
.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
||||
coloricam
|
||||
15
.idea/deploymentTargetSelector.xml
generated
@@ -11,7 +11,20 @@
|
||||
</handle>
|
||||
</Target>
|
||||
</DropdownSelection>
|
||||
<DialogSelection />
|
||||
<DialogSelection>
|
||||
<targets>
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="PhysicalDevice" identifier="serial=57191FDCG0001D" />
|
||||
</handle>
|
||||
</Target>
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="PhysicalDevice" identifier="serial=58051FDCG0038S" />
|
||||
</handle>
|
||||
</Target>
|
||||
</targets>
|
||||
</DialogSelection>
|
||||
</SelectionState>
|
||||
</selectionStates>
|
||||
</component>
|
||||
|
||||
13
.idea/deviceManager.xml
generated
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DeviceTable">
|
||||
<option name="columnSorters">
|
||||
<list>
|
||||
<ColumnSorterState>
|
||||
<option name="column" value="Name" />
|
||||
<option name="order" value="ASCENDING" />
|
||||
</ColumnSorterState>
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
@@ -3,14 +3,15 @@ plugins {
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.secrets)
|
||||
alias(libs.plugins.google.services)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.coloricam"
|
||||
namespace = "com.example.scanwich"
|
||||
compileSdk = 35
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.example.coloricam"
|
||||
applicationId = "com.example.scanwich"
|
||||
minSdk = 24
|
||||
targetSdk = 35
|
||||
versionCode = 1
|
||||
@@ -19,9 +20,20 @@ android {
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
// On configure la release pour utiliser la même clé que le debug pour l'instant
|
||||
getByName("debug") {
|
||||
storeFile = file(System.getProperty("user.home") + "/.android/debug.keystore")
|
||||
storePassword = "android"
|
||||
keyAlias = "androiddebugkey"
|
||||
keyPassword = "android"
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
@@ -73,6 +85,10 @@ dependencies {
|
||||
// Google Sign-In
|
||||
implementation(libs.play.services.auth)
|
||||
|
||||
// Firebase
|
||||
implementation(platform(libs.firebase.bom))
|
||||
implementation(libs.firebase.analytics)
|
||||
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"type": "APK",
|
||||
"kind": "Directory"
|
||||
},
|
||||
"applicationId": "com.example.coloricam",
|
||||
"applicationId": "com.example.scanwich",
|
||||
"variantName": "release",
|
||||
"elements": [
|
||||
{
|
||||
|
||||
@@ -1,64 +1 @@
|
||||
package com.example.coloricam
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Entity(tableName = "meals")
|
||||
data class Meal(
|
||||
@PrimaryKey(autoGenerate = true) val id: Int = 0,
|
||||
val date: Long,
|
||||
val name: String = "Repas",
|
||||
val analysisText: String,
|
||||
val totalCalories: Int,
|
||||
val type: String = "Collation"
|
||||
)
|
||||
|
||||
@Entity(tableName = "glycemia")
|
||||
data class Glycemia(
|
||||
@PrimaryKey(autoGenerate = true) val id: Int = 0,
|
||||
val date: Long,
|
||||
val value: Double,
|
||||
val moment: String
|
||||
)
|
||||
|
||||
@Entity(tableName = "sports")
|
||||
data class SportActivity(
|
||||
@PrimaryKey val id: Long,
|
||||
val name: String,
|
||||
val type: String,
|
||||
val distance: Float,
|
||||
val movingTime: Int,
|
||||
val calories: Float?,
|
||||
val date: Long // timestamp
|
||||
)
|
||||
|
||||
@Dao
|
||||
interface AppDao {
|
||||
@Insert suspend fun insertMeal(meal: Meal): Long
|
||||
@Delete suspend fun deleteMeal(meal: Meal)
|
||||
@Query("SELECT * FROM meals ORDER BY date DESC") fun getAllMeals(): Flow<List<Meal>>
|
||||
|
||||
@Insert suspend fun insertGlycemia(glycemia: Glycemia): Long
|
||||
@Delete suspend fun deleteGlycemia(glycemia: Glycemia)
|
||||
@Query("SELECT * FROM glycemia ORDER BY date DESC") fun getAllGlycemia(): Flow<List<Glycemia>>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertSports(sports: List<SportActivity>)
|
||||
@Query("SELECT * FROM sports ORDER BY date DESC") fun getAllSports(): Flow<List<SportActivity>>
|
||||
@Query("SELECT * FROM sports WHERE date >= :startOfDay AND date < :endOfDay ORDER BY date DESC")
|
||||
fun getSportsForDay(startOfDay: Long, endOfDay: Long): Flow<List<SportActivity>>
|
||||
}
|
||||
|
||||
@Database(entities = [Meal::class, Glycemia::class, SportActivity::class], version = 5)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
abstract fun appDao(): AppDao
|
||||
companion object {
|
||||
@Volatile private var INSTANCE: AppDatabase? = null
|
||||
fun getDatabase(context: Context): AppDatabase =
|
||||
INSTANCE ?: synchronized(this) {
|
||||
Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app_db")
|
||||
.fallbackToDestructiveMigration()
|
||||
.build().also { INSTANCE = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
// Ce fichier est obsolète. Utilisez celui dans le package com.example.scanwich.
|
||||
|
||||
@@ -1,381 +1 @@
|
||||
package com.example.coloricam
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.exifinterface.media.ExifInterface
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.example.coloricam.ui.theme.ColoricamTheme
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignIn
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
|
||||
import com.google.android.gms.common.api.ApiException
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.ResponseBody
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import retrofit2.http.*
|
||||
import java.io.ByteArrayOutputStream
|
||||
import org.json.JSONObject
|
||||
import org.json.JSONArray
|
||||
|
||||
// --- API MODELS ---
|
||||
data class N8nMealRequest(
|
||||
val imageBase64: String?,
|
||||
val mealName: String?,
|
||||
val prompt: String
|
||||
)
|
||||
|
||||
interface N8nApi {
|
||||
@POST("webhook/v1/gemini-proxy")
|
||||
suspend fun analyzeMeal(
|
||||
@Header("X-API-KEY") apiKey: String,
|
||||
@Body request: N8nMealRequest
|
||||
): ResponseBody
|
||||
}
|
||||
|
||||
// --- STRAVA API ---
|
||||
data class StravaActivity(
|
||||
val id: Long,
|
||||
val name: String,
|
||||
val type: String,
|
||||
val distance: Float,
|
||||
val moving_time: Int,
|
||||
val elapsed_time: Int,
|
||||
val calories: Float?,
|
||||
val start_date: String,
|
||||
val start_date_local: String
|
||||
)
|
||||
|
||||
data class StravaTokenResponse(
|
||||
val access_token: String,
|
||||
val refresh_token: String,
|
||||
val expires_at: Long
|
||||
)
|
||||
|
||||
interface StravaApi {
|
||||
@GET("athlete/activities")
|
||||
suspend fun getActivities(
|
||||
@Header("Authorization") token: String,
|
||||
@Query("before") before: Long? = null,
|
||||
@Query("after") after: Long? = null,
|
||||
@Query("page") page: Int? = null,
|
||||
@Query("per_page") perPage: Int? = 30
|
||||
): List<StravaActivity>
|
||||
|
||||
@POST("oauth/token")
|
||||
suspend fun exchangeToken(
|
||||
@Query("client_id") clientId: String,
|
||||
@Query("client_secret") clientSecret: String,
|
||||
@Query("code") code: String,
|
||||
@Query("grant_type") grantType: String = "authorization_code"
|
||||
): StravaTokenResponse
|
||||
|
||||
@POST("oauth/token")
|
||||
suspend fun refreshToken(
|
||||
@Query("client_id") clientId: String,
|
||||
@Query("client_secret") clientSecret: String,
|
||||
@Query("refresh_token") refreshToken: String,
|
||||
@Query("grant_type") grantType: String = "refresh_token"
|
||||
): StravaTokenResponse
|
||||
}
|
||||
|
||||
// Helpers
|
||||
object ApiClient {
|
||||
private val loggingInterceptor = HttpLoggingInterceptor().apply {
|
||||
level = HttpLoggingInterceptor.Level.BODY
|
||||
}
|
||||
|
||||
private val okHttpClient = OkHttpClient.Builder()
|
||||
.addInterceptor(loggingInterceptor)
|
||||
.connectTimeout(60, TimeUnit.SECONDS)
|
||||
.readTimeout(60, TimeUnit.SECONDS)
|
||||
.writeTimeout(60, TimeUnit.SECONDS)
|
||||
.build()
|
||||
|
||||
private val retrofitStrava = Retrofit.Builder()
|
||||
.baseUrl("https://www.strava.com/api/v3/")
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build()
|
||||
|
||||
private val retrofitN8n = Retrofit.Builder()
|
||||
.baseUrl("https://n8n.marquis1987.com/")
|
||||
.client(okHttpClient)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build()
|
||||
|
||||
val stravaApi: StravaApi = retrofitStrava.create(StravaApi::class.java)
|
||||
val n8nApi: N8nApi = retrofitN8n.create(N8nApi::class.java)
|
||||
|
||||
suspend fun getValidStravaToken(prefs: SharedPreferences): String? {
|
||||
val stravaToken = prefs.getString("strava_token", null) ?: return null
|
||||
val expiresAt = prefs.getLong("strava_expires_at", 0)
|
||||
val refreshToken = prefs.getString("strava_refresh_token", null)
|
||||
val clientId = prefs.getString("strava_client_id", "") ?: ""
|
||||
val clientSecret = prefs.getString("strava_client_secret", "") ?: ""
|
||||
|
||||
val currentTime = System.currentTimeMillis() / 1000
|
||||
if (currentTime >= expiresAt && refreshToken != null && clientId.isNotBlank()) {
|
||||
try {
|
||||
val refreshResponse = stravaApi.refreshToken(clientId, clientSecret, refreshToken)
|
||||
prefs.edit()
|
||||
.putString("strava_token", refreshResponse.access_token)
|
||||
.putString("strava_refresh_token", refreshResponse.refresh_token)
|
||||
.putLong("strava_expires_at", refreshResponse.expires_at)
|
||||
.apply()
|
||||
return refreshResponse.access_token
|
||||
} catch (e: Exception) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
return stravaToken
|
||||
}
|
||||
|
||||
fun parseStravaDate(dateStr: String): Long {
|
||||
return try {
|
||||
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)
|
||||
inputFormat.timeZone = TimeZone.getTimeZone("UTC")
|
||||
inputFormat.parse(dateStr)?.time ?: 0L
|
||||
} catch (e: Exception) { 0L }
|
||||
}
|
||||
|
||||
fun estimateCaloriesFromDb(activity: SportActivity, weightKg: Double): Int {
|
||||
if (activity.calories != null && activity.calories > 0) return activity.calories.toInt()
|
||||
val met = when (activity.type.lowercase()) {
|
||||
"run" -> 10.0
|
||||
"ride" -> 8.0
|
||||
"walk" -> 3.5
|
||||
"hike" -> 6.0
|
||||
"swim" -> 7.0
|
||||
"weighttraining" -> 5.0
|
||||
"workout" -> 4.5
|
||||
else -> 5.0
|
||||
}
|
||||
val durationHours = activity.movingTime / 3600.0
|
||||
return (met * weightKg * durationHours).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
// --- UI COMPONENTS ---
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
private lateinit var dao: AppDao
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
dao = AppDatabase.getDatabase(this).appDao()
|
||||
handleStravaCallback(intent)
|
||||
setContent {
|
||||
ColoricamTheme {
|
||||
AuthWrapper(dao)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
handleStravaCallback(intent)
|
||||
}
|
||||
|
||||
private fun handleStravaCallback(intent: Intent) {
|
||||
val data: Uri? = intent.data
|
||||
if (data != null && data.toString().startsWith("coloricam://localhost")) {
|
||||
val code = data.getQueryParameter("code")
|
||||
if (code != null) {
|
||||
val prefs = getSharedPreferences("user_prefs", Context.MODE_PRIVATE)
|
||||
prefs.edit().putString("strava_code", code).apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AuthWrapper(dao: AppDao) {
|
||||
val context = LocalContext.current
|
||||
val gso = remember {
|
||||
GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
|
||||
.requestEmail()
|
||||
.build()
|
||||
}
|
||||
val googleSignInClient = remember { GoogleSignIn.getClient(context, gso) }
|
||||
var account by remember { mutableStateOf(GoogleSignIn.getLastSignedInAccount(context)) }
|
||||
|
||||
// Whitelist
|
||||
val allowedEmails = listOf("marcandre.charest@gmail.com")
|
||||
|
||||
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
val task = GoogleSignIn.getSignedInAccountFromIntent(result.data)
|
||||
try {
|
||||
account = task.getResult(ApiException::class.java)
|
||||
} catch (e: ApiException) {
|
||||
Log.e("Auth", "signInResult:failed code=" + e.statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
val onLogout: () -> Unit = {
|
||||
googleSignInClient.signOut().addOnCompleteListener {
|
||||
account = null
|
||||
}
|
||||
}
|
||||
|
||||
if (account == null) {
|
||||
LoginScreen { launcher.launch(googleSignInClient.signInIntent) }
|
||||
} else {
|
||||
if (allowedEmails.contains(account?.email)) {
|
||||
MainApp(dao, onLogout)
|
||||
} else {
|
||||
AccessDeniedScreen(onLogout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LoginScreen(onLoginClick: () -> Unit) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize().padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text("Coloricam", style = MaterialTheme.typography.displayLarge, color = MaterialTheme.colorScheme.primary)
|
||||
Spacer(Modifier.height(32.dp))
|
||||
Button(onClick = onLoginClick, modifier = Modifier.fillMaxWidth().height(56.dp)) {
|
||||
Icon(Icons.Default.AccountCircle, null)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text("Se connecter avec Google")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AccessDeniedScreen(onLogout: () -> Unit) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize().padding(32.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Icon(Icons.Default.Warning, null, modifier = Modifier.size(80.dp), tint = MaterialTheme.colorScheme.error)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Text("Accès Refusé", style = MaterialTheme.typography.headlineMedium, color = MaterialTheme.colorScheme.error)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text("Votre compte n'est pas autorisé à utiliser cette application.", style = MaterialTheme.typography.bodyLarge)
|
||||
Spacer(Modifier.height(32.dp))
|
||||
Button(onClick = onLogout) { Text("Changer de compte") }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MainApp(dao: AppDao, onLogout: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val prefs = remember { context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE) }
|
||||
var showSetup by remember { mutableStateOf(!prefs.contains("target_calories")) }
|
||||
var isDiabetic by remember { mutableStateOf(prefs.getBoolean("is_diabetic", false)) }
|
||||
|
||||
if (showSetup) {
|
||||
SetupScreen(prefs) {
|
||||
showSetup = false
|
||||
isDiabetic = prefs.getBoolean("is_diabetic", false)
|
||||
}
|
||||
} else {
|
||||
val navController = rememberNavController()
|
||||
Scaffold(
|
||||
bottomBar = {
|
||||
NavigationBar {
|
||||
NavigationBarItem(icon = { Icon(Icons.Default.Home, "Repas") }, label = { Text("Repas") }, selected = false, onClick = { navController.navigate("capture") })
|
||||
NavigationBarItem(icon = { Icon(Icons.Default.Add, "Sport") }, label = { Text("Sport") }, selected = false, onClick = { navController.navigate("sport") })
|
||||
NavigationBarItem(icon = { Icon(Icons.Default.Settings, "Paramètres") }, label = { Text("Paramètres") }, selected = false, onClick = { navController.navigate("settings") })
|
||||
}
|
||||
}
|
||||
) { innerPadding ->
|
||||
NavHost(navController, "capture", Modifier.padding(innerPadding)) {
|
||||
composable("capture") { CaptureScreen(dao, prefs, isDiabetic) }
|
||||
composable("sport") { SportScreen(dao, prefs) }
|
||||
composable("settings") { SettingsScreen(prefs, onLogout) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SetupScreen(prefs: SharedPreferences, onComplete: () -> Unit) {
|
||||
var targetCalories by remember { mutableStateOf("2000") }
|
||||
var targetCarbs by remember { mutableStateOf("250") }
|
||||
var weightKg by remember { mutableStateOf("70") }
|
||||
var isDiabetic by remember { mutableStateOf(false) }
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize().padding(24.dp).verticalScroll(rememberScrollState()),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text("Configuration initiale", style = MaterialTheme.typography.headlineLarge)
|
||||
Spacer(Modifier.height(24.dp))
|
||||
OutlinedTextField(value = targetCalories, onValueChange = { targetCalories = it }, label = { Text("Objectif Calories / jour") }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), modifier = Modifier.fillMaxWidth())
|
||||
Spacer(Modifier.height(16.dp))
|
||||
OutlinedTextField(value = targetCarbs, onValueChange = { targetCarbs = it }, label = { Text("Objectif Glucides / jour (g)") }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), modifier = Modifier.fillMaxWidth())
|
||||
Spacer(Modifier.height(16.dp))
|
||||
OutlinedTextField(value = weightKg, onValueChange = { weightKg = it }, label = { Text("Poids (kg)") }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), modifier = Modifier.fillMaxWidth())
|
||||
Spacer(Modifier.height(24.dp))
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
|
||||
Checkbox(checked = isDiabetic, onCheckedChange = { isDiabetic = it })
|
||||
Text("Je suis diabétique (ajoute l'estimation d'insuline)")
|
||||
}
|
||||
Spacer(Modifier.height(32.dp))
|
||||
Button(onClick = {
|
||||
prefs.edit()
|
||||
.putString("target_calories", targetCalories)
|
||||
.putString("target_carbs", targetCarbs)
|
||||
.putString("weight_kg", weightKg)
|
||||
.putBoolean("is_diabetic", isDiabetic)
|
||||
.apply()
|
||||
onComplete()
|
||||
}, modifier = Modifier.fillMaxWidth().height(56.dp)) {
|
||||
Text("Commencer")
|
||||
}
|
||||
}
|
||||
}
|
||||
// Ce fichier est obsolète. Utilisez celui dans le package com.example.scanwich.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.coloricam.ui.theme
|
||||
package com.example.scanwich.ui.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.coloricam.ui.theme
|
||||
package com.example.scanwich.ui.theme
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Build
|
||||
@@ -21,20 +21,10 @@ 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 ColoricamTheme(
|
||||
fun ScanwichTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
// Dynamic color is available on Android 12+
|
||||
dynamicColor: Boolean = true,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.example.coloricam.ui.theme
|
||||
package com.example.scanwich.ui.theme
|
||||
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
@@ -15,20 +15,4 @@ val Typography = Typography(
|
||||
lineHeight = 24.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
/* Other default text styles to override
|
||||
titleLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 22.sp,
|
||||
lineHeight = 28.sp,
|
||||
letterSpacing = 0.sp
|
||||
),
|
||||
labelSmall = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 11.sp,
|
||||
lineHeight = 16.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
*/
|
||||
)
|
||||
68
app/src/main/java/com/example/scanwich/Database.kt
Normal file
@@ -0,0 +1,68 @@
|
||||
package com.example.scanwich
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Entity(tableName = "meals")
|
||||
data class Meal(
|
||||
@PrimaryKey(autoGenerate = true) val id: Int = 0,
|
||||
val date: Long,
|
||||
val name: String = "Repas",
|
||||
val analysisText: String,
|
||||
val totalCalories: Int,
|
||||
val type: String = "Collation"
|
||||
)
|
||||
|
||||
@Entity(tableName = "glycemia")
|
||||
data class Glycemia(
|
||||
@PrimaryKey(autoGenerate = true) val id: Int = 0,
|
||||
val date: Long,
|
||||
val value: Double,
|
||||
val moment: String
|
||||
)
|
||||
|
||||
@Entity(tableName = "sports")
|
||||
data class SportActivity(
|
||||
@PrimaryKey val id: Long,
|
||||
val name: String,
|
||||
val type: String,
|
||||
val distance: Float,
|
||||
val movingTime: Int,
|
||||
val calories: Float?,
|
||||
val date: Long // timestamp
|
||||
)
|
||||
|
||||
@Dao
|
||||
interface AppDao {
|
||||
@Insert suspend fun insertMeal(meal: Meal): Long
|
||||
@Delete suspend fun deleteMeal(meal: Meal)
|
||||
@Query("SELECT * FROM meals ORDER BY date DESC") fun getAllMeals(): Flow<List<Meal>>
|
||||
@Query("SELECT * FROM meals WHERE date >= :startOfDay AND date < :endOfDay ORDER BY date DESC")
|
||||
fun getMealsForDay(startOfDay: Long, endOfDay: Long): Flow<List<Meal>>
|
||||
|
||||
@Insert suspend fun insertGlycemia(glycemia: Glycemia): Long
|
||||
@Delete suspend fun deleteGlycemia(glycemia: Glycemia)
|
||||
@Query("SELECT * FROM glycemia ORDER BY date DESC") fun getAllGlycemia(): Flow<List<Glycemia>>
|
||||
@Query("SELECT * FROM glycemia WHERE date >= :startOfDay AND date < :endOfDay ORDER BY date DESC")
|
||||
fun getGlycemiaForDay(startOfDay: Long, endOfDay: Long): Flow<List<Glycemia>>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertSports(sports: List<SportActivity>)
|
||||
@Query("SELECT * FROM sports ORDER BY date DESC") fun getAllSports(): Flow<List<SportActivity>>
|
||||
@Query("SELECT * FROM sports WHERE date >= :startOfDay AND date < :endOfDay ORDER BY date DESC")
|
||||
fun getSportsForDay(startOfDay: Long, endOfDay: Long): Flow<List<SportActivity>>
|
||||
}
|
||||
|
||||
@Database(entities = [Meal::class, Glycemia::class, SportActivity::class], version = 5)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
abstract fun appDao(): AppDao
|
||||
companion object {
|
||||
@Volatile private var INSTANCE: AppDatabase? = null
|
||||
fun getDatabase(context: Context): AppDatabase =
|
||||
INSTANCE ?: synchronized(this) {
|
||||
Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app_db")
|
||||
.fallbackToDestructiveMigration()
|
||||
.build().also { INSTANCE = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
1224
app/src/main/java/com/example/scanwich/MainActivity.kt
Normal file
@@ -1,170 +1,74 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
<vector
|
||||
android:height="108dp"
|
||||
android:width="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
</vector>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 982 B After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 38 KiB |
@@ -1,3 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">coloricam</string>
|
||||
<string name="app_name">Scan-Wich</string>
|
||||
</resources>
|
||||
@@ -4,4 +4,5 @@ plugins {
|
||||
alias(libs.plugins.kotlin.compose) apply false
|
||||
alias(libs.plugins.ksp) apply false
|
||||
alias(libs.plugins.secrets) apply false
|
||||
alias(libs.plugins.google.services) apply false
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ browser = "1.8.0"
|
||||
exifinterface = "1.3.7"
|
||||
secretsPlugin = "2.0.1"
|
||||
playServicesAuth = "21.2.0"
|
||||
googleServices = "4.4.2"
|
||||
firebaseBom = "34.9.0"
|
||||
|
||||
[libraries]
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
@@ -47,9 +49,12 @@ okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor",
|
||||
androidx-browser = { group = "androidx.browser", name = "browser", version.ref = "browser" }
|
||||
androidx-exifinterface = { group = "androidx.exifinterface", name = "exifinterface", version.ref = "exifinterface" }
|
||||
play-services-auth = { group = "com.google.android.gms", name = "play-services-auth", version.ref = "playServicesAuth" }
|
||||
firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebaseBom" }
|
||||
firebase-analytics = { group = "com.google.firebase", name = "firebase-analytics" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|
||||
secrets = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "secretsPlugin" }
|
||||
google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" }
|
||||
|
||||