From 967be9e750221ab2ab783f95df79bb26d290a45e Mon Sep 17 00:00:00 2001 From: Martial Simon Date: Mon, 15 Sep 2025 01:07:58 +0200 Subject: add: added projects --- benchmark/.gitignore | 152 ++++ benchmark/README.md | 1 + benchmark/app/.gitignore | 1 + benchmark/app/build.gradle.kts | 81 ++ benchmark/app/proguard-rules.pro | 21 + .../benchmark/ExampleInstrumentedTest.kt | 25 + benchmark/app/src/main/AndroidManifest.xml | 48 ++ .../io/trentetroim/benchmark/AddBenchActivity.kt | 274 +++++++ .../java/io/trentetroim/benchmark/CustomAdapter.kt | 90 +++ .../java/io/trentetroim/benchmark/MainActivity.kt | 100 +++ .../java/io/trentetroim/benchmark/MapActivity.kt | 811 +++++++++++++++++++++ .../java/io/trentetroim/benchmark/ReviewAdapter.kt | 37 + .../benchmark/api/models/ApiResponse.kt | 16 + .../io/trentetroim/benchmark/api/models/Point.kt | 24 + .../io/trentetroim/benchmark/api/models/Review.kt | 20 + .../benchmark/api/repository/PointRepository.kt | 164 +++++ .../benchmark/api/repository/ReviewRepository.kt | 69 ++ .../benchmark/api/service/ApiService.kt | 38 + .../benchmark/api/service/RetrofitClient.kt | 43 ++ .../io/trentetroim/benchmark/ui/theme/Color.kt | 17 + .../io/trentetroim/benchmark/ui/theme/Theme.kt | 55 ++ .../java/io/trentetroim/benchmark/ui/theme/Type.kt | 36 + benchmark/app/src/main/res/drawable/card_bg.xml | 12 + .../app/src/main/res/drawable/ic_bench_icon.xml | 93 +++ .../main/res/drawable/ic_launcher_background.xml | 74 ++ .../main/res/drawable/ic_launcher_foreground.xml | 31 + .../src/main/res/drawable/list_handle_layout.xml | 12 + .../app/src/main/res/drawable/plus_5345954.png | Bin 0 -> 384 bytes benchmark/app/src/main/res/drawable/target.xml | 9 + benchmark/app/src/main/res/font/nokia_font.xml | 7 + benchmark/app/src/main/res/font/nokiafc22.ttf | Bin 0 -> 16272 bytes .../src/main/res/layout/bottom_sheet_dialog.xml | 29 + benchmark/app/src/main/res/layout/card_layout.xml | 39 + .../src/main/res/layout/dialog_bench_details.xml | 109 +++ .../app/src/main/res/layout/dialog_review.xml | 40 + .../src/main/res/layout/dialog_reviews_list.xml | 40 + benchmark/app/src/main/res/layout/item_review.xml | 26 + benchmark/app/src/main/res/layout/main_layout.xml | 34 + .../app/src/main/res/layout/new_bench_layout.xml | 44 ++ .../app/src/main/res/mipmap-anydpi/ic_launcher.xml | 6 + .../main/res/mipmap-anydpi/ic_launcher_round.xml | 6 + .../app/src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1404 bytes .../main/res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 2898 bytes .../app/src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 982 bytes .../main/res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 1772 bytes .../app/src/main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1900 bytes .../main/res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 3918 bytes .../src/main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 2884 bytes .../main/res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 5914 bytes .../src/main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 3844 bytes .../main/res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 7778 bytes benchmark/app/src/main/res/values/colors.xml | 13 + benchmark/app/src/main/res/values/strings.xml | 18 + benchmark/app/src/main/res/values/themes.xml | 12 + benchmark/app/src/main/res/xml/backup_rules.xml | 14 + .../app/src/main/res/xml/data_extraction_rules.xml | 20 + .../src/main/res/xml/network_security_config.xml | 7 + .../io/trentetroim/benchmark/ExampleUnitTest.kt | 17 + benchmark/build.gradle.kts | 6 + benchmark/doc/openapi.json | 183 +++++ benchmark/gradle.properties | 25 + benchmark/gradle/libs.versions.toml | 65 ++ benchmark/gradle/wrapper/gradle-wrapper.properties | 6 + benchmark/gradlew | 185 +++++ benchmark/gradlew.bat | 89 +++ benchmark/settings.gradle.kts | 25 + 66 files changed, 3419 insertions(+) create mode 100644 benchmark/.gitignore create mode 100644 benchmark/README.md create mode 100644 benchmark/app/.gitignore create mode 100644 benchmark/app/build.gradle.kts create mode 100644 benchmark/app/proguard-rules.pro create mode 100644 benchmark/app/src/androidTest/java/io/trentetroim/benchmark/ExampleInstrumentedTest.kt create mode 100644 benchmark/app/src/main/AndroidManifest.xml create mode 100644 benchmark/app/src/main/java/io/trentetroim/benchmark/AddBenchActivity.kt create mode 100644 benchmark/app/src/main/java/io/trentetroim/benchmark/CustomAdapter.kt create mode 100644 benchmark/app/src/main/java/io/trentetroim/benchmark/MainActivity.kt create mode 100644 benchmark/app/src/main/java/io/trentetroim/benchmark/MapActivity.kt create mode 100644 benchmark/app/src/main/java/io/trentetroim/benchmark/ReviewAdapter.kt create mode 100644 benchmark/app/src/main/java/io/trentetroim/benchmark/api/models/ApiResponse.kt create mode 100644 benchmark/app/src/main/java/io/trentetroim/benchmark/api/models/Point.kt create mode 100644 benchmark/app/src/main/java/io/trentetroim/benchmark/api/models/Review.kt create mode 100644 benchmark/app/src/main/java/io/trentetroim/benchmark/api/repository/PointRepository.kt create mode 100644 benchmark/app/src/main/java/io/trentetroim/benchmark/api/repository/ReviewRepository.kt create mode 100644 benchmark/app/src/main/java/io/trentetroim/benchmark/api/service/ApiService.kt create mode 100644 benchmark/app/src/main/java/io/trentetroim/benchmark/api/service/RetrofitClient.kt create mode 100644 benchmark/app/src/main/java/io/trentetroim/benchmark/ui/theme/Color.kt create mode 100644 benchmark/app/src/main/java/io/trentetroim/benchmark/ui/theme/Theme.kt create mode 100644 benchmark/app/src/main/java/io/trentetroim/benchmark/ui/theme/Type.kt create mode 100644 benchmark/app/src/main/res/drawable/card_bg.xml create mode 100644 benchmark/app/src/main/res/drawable/ic_bench_icon.xml create mode 100644 benchmark/app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 benchmark/app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 benchmark/app/src/main/res/drawable/list_handle_layout.xml create mode 100644 benchmark/app/src/main/res/drawable/plus_5345954.png create mode 100644 benchmark/app/src/main/res/drawable/target.xml create mode 100644 benchmark/app/src/main/res/font/nokia_font.xml create mode 100644 benchmark/app/src/main/res/font/nokiafc22.ttf create mode 100644 benchmark/app/src/main/res/layout/bottom_sheet_dialog.xml create mode 100644 benchmark/app/src/main/res/layout/card_layout.xml create mode 100644 benchmark/app/src/main/res/layout/dialog_bench_details.xml create mode 100644 benchmark/app/src/main/res/layout/dialog_review.xml create mode 100644 benchmark/app/src/main/res/layout/dialog_reviews_list.xml create mode 100644 benchmark/app/src/main/res/layout/item_review.xml create mode 100644 benchmark/app/src/main/res/layout/main_layout.xml create mode 100644 benchmark/app/src/main/res/layout/new_bench_layout.xml create mode 100644 benchmark/app/src/main/res/mipmap-anydpi/ic_launcher.xml create mode 100644 benchmark/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml create mode 100644 benchmark/app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 benchmark/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 benchmark/app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 benchmark/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 benchmark/app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 benchmark/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 benchmark/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 benchmark/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 benchmark/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 benchmark/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 benchmark/app/src/main/res/values/colors.xml create mode 100644 benchmark/app/src/main/res/values/strings.xml create mode 100644 benchmark/app/src/main/res/values/themes.xml create mode 100644 benchmark/app/src/main/res/xml/backup_rules.xml create mode 100644 benchmark/app/src/main/res/xml/data_extraction_rules.xml create mode 100644 benchmark/app/src/main/res/xml/network_security_config.xml create mode 100644 benchmark/app/src/test/java/io/trentetroim/benchmark/ExampleUnitTest.kt create mode 100644 benchmark/build.gradle.kts create mode 100644 benchmark/doc/openapi.json create mode 100644 benchmark/gradle.properties create mode 100644 benchmark/gradle/libs.versions.toml create mode 100644 benchmark/gradle/wrapper/gradle-wrapper.properties create mode 100755 benchmark/gradlew create mode 100644 benchmark/gradlew.bat create mode 100644 benchmark/settings.gradle.kts (limited to 'benchmark') diff --git a/benchmark/.gitignore b/benchmark/.gitignore new file mode 100644 index 0000000..1c8df90 --- /dev/null +++ b/benchmark/.gitignore @@ -0,0 +1,152 @@ +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Log/OS Files +*.log + +# Android Studio generated files and folders +captures/ +.externalNativeBuild/ +.cxx/ +*.apk +output.json + +# IntelliJ +*.iml +.idea/ +misc.xml +deploymentTargetDropDown.xml +render.experimental.xml + +# Keystore files +*.jks +*.keystore + +# Google Services (e.g. APIs or Firebase) +google-services.json + +# Android Profiling +*.hprof + +# Compiled class file +*.class + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + +# Kotlin Gradle plugin data, see https://kotlinlang.org/docs/whatsnew20.html#new-directory-for-kotlin-data-in-gradle-projects +.kotlin/ + +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ +.idea/sonarlint.xml # see https://community.sonarsource.com/t/is-the-file-idea-idea-idea-sonarlint-xml-intended-to-be-under-source-control/121119 + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser +/doc/API_INTEGRATION.md diff --git a/benchmark/README.md b/benchmark/README.md new file mode 100644 index 0000000..ee09ff5 --- /dev/null +++ b/benchmark/README.md @@ -0,0 +1 @@ +# BenchMark diff --git a/benchmark/app/.gitignore b/benchmark/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/benchmark/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/benchmark/app/build.gradle.kts b/benchmark/app/build.gradle.kts new file mode 100644 index 0000000..7dfb21a --- /dev/null +++ b/benchmark/app/build.gradle.kts @@ -0,0 +1,81 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.compose) +} + +android { + namespace = "io.trentetroim.benchmark" + compileSdk = 35 + + defaultConfig { + applicationId = "io.trentetroim.benchmark" + minSdk = 33 + targetSdk = 35 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + kotlinOptions { + jvmTarget = "11" + } + buildFeatures { + compose = true + } +} + +dependencies { + implementation(libs.osmdroid.android) + implementation(libs.osmbonuspack) + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.activity.compose) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.ui) + implementation(libs.androidx.ui.graphics) + implementation(libs.androidx.ui.tooling.preview) + implementation(libs.androidx.material3) + implementation(libs.androidx.constraintlayout) + implementation(libs.androidx.cardview) + + implementation(libs.retrofit) + implementation(libs.retrofit.gson) + implementation(libs.gson) + + implementation(libs.okhttp) + implementation(libs.okhttp.logging) + + implementation(libs.kotlinx.coroutines.core) + implementation(libs.kotlinx.coroutines.android) + implementation(libs.play.services.location) + implementation(libs.material) + implementation(libs.glide) + implementation(libs.samsung.image.picker) + implementation(libs.coil.compose) + + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(platform(libs.androidx.compose.bom)) + androidTestImplementation(libs.androidx.ui.test.junit4) + debugImplementation(libs.androidx.ui.tooling) + implementation(libs.androidx.core.splashscreen) + debugImplementation(libs.androidx.ui.test.manifest) + implementation("com.github.a914-gowtham:compose-ratingbar:1.3.12") + implementation(libs.slidingUp){exclude(group="com.android.support", module="support-v4")} +} diff --git a/benchmark/app/proguard-rules.pro b/benchmark/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/benchmark/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# 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/benchmark/app/src/androidTest/java/io/trentetroim/benchmark/ExampleInstrumentedTest.kt b/benchmark/app/src/androidTest/java/io/trentetroim/benchmark/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..a883705 --- /dev/null +++ b/benchmark/app/src/androidTest/java/io/trentetroim/benchmark/ExampleInstrumentedTest.kt @@ -0,0 +1,25 @@ +package io.trentetroim.benchmark + +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("io.trentetroim.benchmark", appContext.packageName) + } +} \ No newline at end of file diff --git a/benchmark/app/src/main/AndroidManifest.xml b/benchmark/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..9021931 --- /dev/null +++ b/benchmark/app/src/main/AndroidManifest.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/benchmark/app/src/main/java/io/trentetroim/benchmark/AddBenchActivity.kt b/benchmark/app/src/main/java/io/trentetroim/benchmark/AddBenchActivity.kt new file mode 100644 index 0000000..62432e3 --- /dev/null +++ b/benchmark/app/src/main/java/io/trentetroim/benchmark/AddBenchActivity.kt @@ -0,0 +1,274 @@ +package io.trentetroim.benchmark + +import android.app.Activity +import android.graphics.drawable.shapes.Shape +import android.net.Uri +import android.os.Bundle +import android.util.Log +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.compose.setContent +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Button +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen +import coil.compose.rememberAsyncImagePainter +import com.gowtham.ratingbar.RatingBar +import com.gowtham.ratingbar.RatingBarStyle +import io.trentetroim.benchmark.ui.theme.BenchmarkTheme +import io.trentetroim.benchmark.ui.theme.Grass +import io.trentetroim.benchmark.ui.theme.Typography +import io.trentetroim.benchmark.api.models.ApiResponse +import io.trentetroim.benchmark.api.models.Point +import io.trentetroim.benchmark.api.models.Review +import io.trentetroim.benchmark.api.repository.PointRepository +import io.trentetroim.benchmark.api.repository.ReviewRepository +import kotlinx.coroutines.launch +import androidx.lifecycle.lifecycleScope +import io.trentetroim.benchmark.ui.theme.Lagoon +import io.trentetroim.benchmark.ui.theme.Tildeeth +import io.trentetroim.benchmark.ui.theme.TildeethAlpha +import org.osmdroid.views.overlay.simplefastpoint.SimpleFastPointOverlayOptions + +class AddBenchActivity : ComponentActivity() { + private val defaultImageUrl = "https://guillotinemelody.dev/resources/icon.png" + private val pointRepository = PointRepository() + private val reviewRepository = ReviewRepository() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + installSplashScreen() + val latitude = intent.getDoubleExtra("currentLatitude", 0.0) + val longitude = intent.getDoubleExtra("currentLongitude", 0.0) + + setContent { + BenchmarkTheme { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxSize().background( + color = Grass + ).padding(16.dp) + ) { + Text(text = stringResource(R.string.new_bench), textAlign = TextAlign.Center) + + Spacer(modifier = Modifier.height(16.dp)) + + var description by remember { mutableStateOf("") } + Input(value = description, onValueChange = { description = it }) + + Spacer(modifier = Modifier.height(16.dp)) + + var rating by remember { mutableFloatStateOf(3.0f) } + Rating(value = rating, onValueChange = { rating = it }) + + Spacer(modifier = Modifier.height(16.dp)) + + var imageUrl by remember { mutableStateOf(defaultImageUrl) } + var selectedImageUri by remember { mutableStateOf(null) } + var isUploading by remember { mutableStateOf(false) } + val context = LocalContext.current + + val pickImageLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.GetContent() + ) { uri: Uri? -> + uri?.let { + selectedImageUri = it + isUploading = true + + lifecycleScope.launch { + pointRepository.uploadImage(context, it).collect { response -> + when (response) { + is ApiResponse.Success -> { + imageUrl = response.data + isUploading = false + Toast.makeText(context, "Image uploaded successfully", Toast.LENGTH_SHORT).show() + } + is ApiResponse.Error -> { + isUploading = false + Toast.makeText(context, "Error: ${response.errorMessage}", Toast.LENGTH_SHORT).show() + } + is ApiResponse.Loading -> { + } + } + } + } + } + } + + Button( + onClick = { pickImageLauncher.launch("image/*") }, + enabled = !isUploading + ) { + Text("Ajouter une photo") + } + + Spacer(modifier = Modifier.height(8.dp)) + + if (isUploading) { + CircularProgressIndicator( + modifier = Modifier.size(24.dp), + color = Color.White + ) + Spacer(modifier = Modifier.height(8.dp)) + } + + selectedImageUri?.let { + Image( + painter = rememberAsyncImagePainter(it), + contentDescription = "Selected image", + modifier = Modifier + .size(150.dp) + .padding(8.dp) + ) + } + + Text( + text = "URL de l'image: $imageUrl", + modifier = Modifier.padding(8.dp) + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Row( + horizontalArrangement = Arrangement.SpaceEvenly, + modifier = Modifier.fillMaxWidth() + ) { + Button(onClick = { finish() }) { + Text(stringResource(R.string.cancel)) + } + + Button(onClick = { + val prefixedImageUrl = if (!imageUrl.startsWith("http://la-banquise.fr:5431")) { + "http://la-banquise.fr:5431" + if (imageUrl.startsWith("/")) imageUrl else "/$imageUrl" + } else { + imageUrl + } + + val point = Point( + x = latitude, + y = longitude, + type = "bench", + name = description, + picture = prefixedImageUrl, + id = "" + ) + + lifecycleScope.launch { + pointRepository.createPoint(point).collect { response -> + when (response) { + is ApiResponse.Success -> { + Log.d("AddBenchActivity", "Point created: $point") + + val createdPoint = response.data + val initialReview = Review( + id = "", + pointId = createdPoint.id, + grade = rating.toInt(), + comment = "Initial rating", + pictures = null + ) + + reviewRepository.addReview(createdPoint.id, initialReview).collect { reviewResponse -> + when (reviewResponse) { + is ApiResponse.Success -> { + Log.d("AddBenchActivity", "Initial review added: ${reviewResponse.data}") + Toast.makeText( + this@AddBenchActivity, + "Banc et note initiale ajoutés avec succès!", + Toast.LENGTH_SHORT + ).show() + } + is ApiResponse.Error -> { + Log.e("AddBenchActivity", "Error adding initial review: ${reviewResponse.errorMessage}") + Toast.makeText( + this@AddBenchActivity, + "Banc ajouté mais erreur lors de l'ajout de la note initiale", + Toast.LENGTH_SHORT + ).show() + } + is ApiResponse.Loading -> { + } + } + finish() + } + } + is ApiResponse.Error -> { + Log.e("AddBenchActivity", "Error creating point: ${response.errorMessage}") + Toast.makeText( + this@AddBenchActivity, + "Erreur lors de la création du banc: ${response.errorMessage}", + Toast.LENGTH_SHORT + ).show() + } + is ApiResponse.Loading -> { + } + } + } + } + }) { + Text(stringResource(R.string.save)) + } + } + } + } + } + } +} + +@Composable +fun Input(value: String, onValueChange: (String) -> Unit) { + OutlinedTextField( + value = value, + onValueChange = onValueChange, + label = { Text("Description", color = Tildeeth) }, + placeholder = { Text("Smash", color = TildeethAlpha) }, + maxLines = 3, + textStyle = TextStyle(color = Tildeeth, fontFamily = Typography.bodyLarge.fontFamily), + modifier = Modifier.padding(8.dp).background(Lagoon).border(3.dp, Tildeeth) + ) +} + +@Composable +fun Rating(value: Float, onValueChange: (Float) -> Unit) { + RatingBar( + value = value, + style = RatingBarStyle.Fill(), + onValueChange = onValueChange, + onRatingChanged = { + Log.d("TAG", "onRatingChanged: $it") + } + ) +} diff --git a/benchmark/app/src/main/java/io/trentetroim/benchmark/CustomAdapter.kt b/benchmark/app/src/main/java/io/trentetroim/benchmark/CustomAdapter.kt new file mode 100644 index 0000000..5b77dcc --- /dev/null +++ b/benchmark/app/src/main/java/io/trentetroim/benchmark/CustomAdapter.kt @@ -0,0 +1,90 @@ +package io.trentetroim.benchmark + +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.RatingBar +import android.widget.TextView +import androidx.lifecycle.LifecycleCoroutineScope +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import io.trentetroim.benchmark.api.models.ApiResponse +import io.trentetroim.benchmark.api.models.Point +import io.trentetroim.benchmark.api.repository.ReviewRepository +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch + +interface OnBenchClickListener { + fun onBenchClick(point: Point) +} + +class CustomAdapter( + private val dataSet: List, + private val lifecycleScope: LifecycleCoroutineScope, + private val onBenchClickListener: OnBenchClickListener? = null +) : RecyclerView.Adapter() { + + private val reviewRepository = ReviewRepository() + + class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { + val cardPfp: ImageView = view.findViewById(R.id.benchPfp) + val cardName: TextView + val cardPosition: TextView + val cardRating: RatingBar + val itemView: View = view + + init { + cardName = view.findViewById(R.id.benchName) + cardPosition = view.findViewById(R.id.benchPos) + cardRating = view.findViewById(R.id.benchRating) + } + } + + override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder { + val view = LayoutInflater.from(viewGroup.context) + .inflate(R.layout.card_layout, viewGroup, false) + + return ViewHolder(view) + } + + override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) { + val point = dataSet[position] + viewHolder.cardName.text = point.name + + val decimalFormat = java.text.DecimalFormat("0.000") + viewHolder.cardPosition.text = "Position: ${decimalFormat.format(point.x)}, ${decimalFormat.format(point.y)}" + + // Fetch and display the average rating + lifecycleScope.launch { + reviewRepository.getAverageRating(point.id).collectLatest { response -> + when (response) { + is ApiResponse.Success -> { + viewHolder.cardRating.rating = response.data + } + else -> { + viewHolder.cardRating.rating = 0f + } + } + } + } + + // Set click listener on the card view + viewHolder.itemView.setOnClickListener { + onBenchClickListener?.onBenchClick(point) + } + + if (!point.picture.isNullOrEmpty()) { + Glide.with(viewHolder.itemView.context) + .load(point.picture) + .placeholder(R.drawable.ic_bench_icon) + .error(R.drawable.ic_bench_icon) + .into(viewHolder.cardPfp) + } else { + viewHolder.cardPfp.setImageResource(R.drawable.ic_bench_icon) + } + } + + override fun getItemCount() = dataSet.size +} diff --git a/benchmark/app/src/main/java/io/trentetroim/benchmark/MainActivity.kt b/benchmark/app/src/main/java/io/trentetroim/benchmark/MainActivity.kt new file mode 100644 index 0000000..d99db2e --- /dev/null +++ b/benchmark/app/src/main/java/io/trentetroim/benchmark/MainActivity.kt @@ -0,0 +1,100 @@ +package io.trentetroim.benchmark + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import io.trentetroim.benchmark.ui.theme.BenchmarkTheme +import android.content.Context +import android.content.Intent +import android.os.UserManager +import androidx.activity.enableEdgeToEdge +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.ui.Alignment +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen +import io.trentetroim.benchmark.ui.theme.Lagoon +import io.trentetroim.benchmark.ui.theme.Salmon +import io.trentetroim.benchmark.ui.theme.White + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + installSplashScreen() + enableEdgeToEdge() + setContent { + BenchmarkTheme { + MainContent(Modifier.background(Lagoon)) + } + } + } + + @Composable + @Preview + fun MainContent(modifier : Modifier = Modifier) { + Column( + modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceEvenly + ) { + val user: UserManager = applicationContext.getSystemService(USER_SERVICE) as UserManager + Text("User is a goat: " + user.isUserAGoat().toString()) + Infos() + IntentToSwitch(LocalContext.current) + Footer() + } + } + + @Composable + @Preview + fun Infos() { + Image( + painter = painterResource(R.drawable.ic_bench_icon), + modifier = Modifier.size(200.dp), + contentDescription = "App icon", + contentScale = ContentScale.Fit + ) + Text(text = stringResource(R.string.app_name), fontSize = 50.sp) + } + + // composable with a button + @Composable + fun IntentToSwitch(context: Context) { + Button( + onClick = { + context.startActivity(Intent(context, MapActivity::class.java)) + }, + colors = ButtonDefaults.buttonColors(containerColor = Salmon), + ) { + Text("Démarrer", color = White) + } + } + + @Composable + @Preview + fun Footer() { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text( + text = stringResource(R.string.devs), + ) + Text(stringResource(R.string.quote), fontStyle = FontStyle.Italic, fontFamily = FontFamily.Cursive) + } + } +} \ No newline at end of file diff --git a/benchmark/app/src/main/java/io/trentetroim/benchmark/MapActivity.kt b/benchmark/app/src/main/java/io/trentetroim/benchmark/MapActivity.kt new file mode 100644 index 0000000..09376b2 --- /dev/null +++ b/benchmark/app/src/main/java/io/trentetroim/benchmark/MapActivity.kt @@ -0,0 +1,811 @@ +package io.trentetroim.benchmark + +import android.Manifest +import android.animation.ValueAnimator +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.os.Bundle +import android.preference.PreferenceManager.getDefaultSharedPreferences +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.EditText +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.RatingBar +import android.widget.TextView +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.core.app.ActivityCompat +import androidx.core.animation.doOnEnd +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updateLayoutParams +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition +import com.google.android.gms.location.FusedLocationProviderClient +import com.google.android.gms.location.LocationServices +import com.google.android.material.bottomsheet.BottomSheetBehavior +import io.trentetroim.benchmark.api.models.ApiResponse +import io.trentetroim.benchmark.api.models.Point +import io.trentetroim.benchmark.api.models.Review +import io.trentetroim.benchmark.api.repository.PointRepository +import io.trentetroim.benchmark.api.repository.ReviewRepository +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import org.osmdroid.config.Configuration.getInstance +import org.osmdroid.tileprovider.tilesource.TileSourceFactory +import org.osmdroid.util.GeoPoint +import org.osmdroid.views.MapView +import org.osmdroid.views.overlay.ItemizedIconOverlay +import org.osmdroid.views.overlay.ItemizedOverlayWithFocus +import org.osmdroid.views.overlay.OverlayItem +import org.osmdroid.views.overlay.gestures.RotationGestureOverlay +import org.osmdroid.views.overlay.MapEventsOverlay +import org.osmdroid.events.MapEventsReceiver +import androidx.core.graphics.drawable.toDrawable +import org.osmdroid.bonuspack.routing.OSRMRoadManager +import org.osmdroid.bonuspack.routing.Road +import org.osmdroid.bonuspack.routing.RoadManager +import org.osmdroid.views.overlay.Polyline + +class MapActivity : ComponentActivity(), MapEventsReceiver, OnBenchClickListener { + private lateinit var map: MapView + + private lateinit var locationClient: FusedLocationProviderClient + private val locationPermissionRequest = 1001 + private var latitude = 46.0837 + private var longitude = 6.0452 + + private val pointRepository = PointRepository() + private val reviewRepository = ReviewRepository() + private var points: List = emptyList() + private val TAG = "MapActivity" + private lateinit var bottomText: TextView; + private lateinit var listContainer: CoordinatorLayout; + private lateinit var sheetBehavior: BottomSheetBehavior + + + private var isDialogShowing = false + + override fun onBenchClick(point: Point) { + val startPoint = map.mapCenter as GeoPoint + val endPoint = GeoPoint(point.x, point.y) + val startZoom = map.zoomLevelDouble + var endZoom = map.zoomLevelDouble + if (endZoom < 17.5) + endZoom = 17.5 + + val animator = ValueAnimator.ofFloat(0f, 1f) + animator.duration = 1000 + animator.addUpdateListener { animation -> + val fraction = animation.animatedFraction + val lat = startPoint.latitude + (endPoint.latitude - startPoint.latitude) * fraction + val lon = startPoint.longitude + (endPoint.longitude - startPoint.longitude) * fraction + val zoom = startZoom + (endZoom - startZoom) * fraction + map.controller.setCenter(GeoPoint(lat, lon)) + map.controller.setZoom(zoom) + } + animator.start() + } + + override fun singleTapConfirmedHelper(p0: GeoPoint): Boolean { + return false + } + + override fun longPressHelper(p0: GeoPoint): Boolean { + val startPoint = map.mapCenter as GeoPoint + val endPoint = p0 + val startZoom = map.zoomLevelDouble + var endZoom = map.zoomLevelDouble + if (endZoom < 17.5) + endZoom = 17.5 + val animator = ValueAnimator.ofFloat(0f, 1f) + animator.duration = 1000 + animator.addUpdateListener { animation -> + val fraction = animation.animatedFraction + val lat = startPoint.latitude + (endPoint.latitude - startPoint.latitude) * fraction + val lon = startPoint.longitude + (endPoint.longitude - startPoint.longitude) * fraction + val zoom = startZoom + (endZoom - startZoom) * fraction + map.controller.setCenter(GeoPoint(lat, lon)) + map.controller.setZoom(zoom) + } + animator.doOnEnd { + Thread.sleep(400) + showAddBenchDialog(p0.latitude, p0.longitude) + } + animator.start() + return true + } + + private fun showAddBenchDialog(lat: Double, lon: Double) { + val intent = Intent(baseContext, AddBenchActivity::class.java) + intent.putExtra("currentLatitude", lat) + intent.putExtra("currentLongitude", lon) + startActivity(intent) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + // enableEdgeToEdge() + + getInstance().load(this, getDefaultSharedPreferences(this)) + + setContentView(R.layout.main_layout) + sheetBehavior = BottomSheetBehavior.from(findViewById(R.id.slideUpContainer)) + + sheetBehavior.setBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { + override fun onStateChanged(bottomSheet: View, newState: Int) { + when (newState) { + BottomSheetBehavior.STATE_HIDDEN -> { + bottomText.text = "Steak haché" + } + + BottomSheetBehavior.STATE_EXPANDED -> + bottomText.text = "Bas!" + + BottomSheetBehavior.STATE_COLLAPSED -> + bottomText.text = "Haut!" + + BottomSheetBehavior.STATE_DRAGGING -> { + bottomText.text = "Oh oui tire plus fort" + } + + BottomSheetBehavior.STATE_SETTLING -> { + bottomText.text = "je câble" + } + + BottomSheetBehavior.STATE_HALF_EXPANDED -> { + TODO() + } + } + } + + override fun onSlide(bottomSheet: View, slideOffset: Float) { + bottomText.text = "Oh oui maître tirez encore " + ((1 - slideOffset) * 100) + "% de plus" + } + }) + + + map = findViewById(R.id.map) + map.setTileSource(TileSourceFactory.MAPNIK) + + val mapController = map.controller + mapController.setZoom(15.0) + val startPoint = GeoPoint(latitude, longitude) + mapController.setCenter(startPoint) + + val rotationGestureOverlay = RotationGestureOverlay(map) + rotationGestureOverlay.isEnabled + map.setMultiTouchControls(true) + map.overlays.add(rotationGestureOverlay) + + val mapEventsOverlay = MapEventsOverlay(this) + map.overlays.add(mapEventsOverlay) + + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.greenwashed)) { v, windowInsets -> + val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + v.updateLayoutParams { + leftMargin = insets.left + bottomMargin = insets.bottom + rightMargin = insets.right + } + + WindowInsetsCompat.CONSUMED + } + + val newBenchBtn: ImageButton = findViewById(R.id.plusBtn) + locationClient = LocationServices.getFusedLocationProviderClient(this) + newBenchBtn.setOnClickListener { + val center = map.mapCenter as GeoPoint + val intent = Intent(baseContext, AddBenchActivity::class.java) + intent.putExtra("currentLatitude", center.latitude) + intent.putExtra("currentLongitude", center.longitude) + startActivity(intent) + } + val centerPosBtn: ImageButton = findViewById(R.id.posBtn) + centerPosBtn.setOnClickListener { + sendCurrentLocation(false) + } + + val recyclerView: RecyclerView = findViewById(R.id.greenwashed) + recyclerView.layoutManager = LinearLayoutManager(this) + + bottomText = findViewById(R.id.divider) + listContainer = findViewById(R.id.listWrapper) + + sendCurrentLocation(false) + } + + private fun sendCurrentLocation(launchDialog: Boolean = true) { + if (ActivityCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_FINE_LOCATION + ) != PackageManager.PERMISSION_GRANTED + ) { + ActivityCompat.requestPermissions( + this, + arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), + locationPermissionRequest + ) + return + } + locationClient.lastLocation.addOnSuccessListener { location -> + if (location != null) { + latitude = location.latitude + longitude = location.longitude + + if (!launchDialog) { + val startPoint = map.mapCenter as GeoPoint + val endPoint = GeoPoint(location.latitude, location.longitude) + val startZoom = map.zoomLevelDouble + var endZoom = map.zoomLevelDouble + if (endZoom < 18.0) + endZoom = 18.0 + val animator = ValueAnimator.ofFloat(0f, 1f) + animator.duration = 1000 + animator.addUpdateListener { animation -> + val fraction = animation.animatedFraction + val lat = startPoint.latitude + (endPoint.latitude - startPoint.latitude) * fraction + val lon = startPoint.longitude + (endPoint.longitude - startPoint.longitude) * fraction + val zoom = startZoom + (endZoom - startZoom) * fraction + map.controller.setCenter(GeoPoint(lat, lon)) + map.controller.setZoom(zoom) + } + animator.start() + + val recyclerView: RecyclerView = findViewById(R.id.greenwashed) + fetchPoints(recyclerView) + } else { + val intent = Intent(baseContext, AddBenchActivity::class.java) + intent.putExtra("currentLatitude", location.latitude) + intent.putExtra("currentLongitude", location.longitude) + startActivity(intent) + } + } else { + Toast.makeText( + this@MapActivity, + "Merci d'activer la localisation", + Toast.LENGTH_LONG + ).show() + } + } + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + + if (requestCode == locationPermissionRequest && + grantResults.isNotEmpty() && + grantResults[0] == PackageManager.PERMISSION_GRANTED + ) { + sendCurrentLocation() + } else { + Toast.makeText( + this@MapActivity, + "La permission d'accès à la position est requise pour ajouter un banc", + Toast.LENGTH_LONG + ).show() + } + } + + private fun calculateDistance(lat1: Double, lon1: Double, lat2: Double, lon2: Double): Double { + val r = 6371 + val dLat = Math.toRadians(lat2 - lat1) + val dLon = Math.toRadians(lon2 - lon1) + val a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * + Math.sin(dLon / 2) * Math.sin(dLon / 2) + val c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)) + return r * c + } + + private fun fetchPoints(recyclerView: RecyclerView) { + lifecycleScope.launch { + pointRepository.getPoints().collectLatest { response -> + when (response) { + is ApiResponse.Loading -> { + bottomText.text = "Chargement..." + } + + is ApiResponse.Success -> { + points = response.data.sortedBy { point -> + calculateDistance(latitude, longitude, point.x, point.y) + } + + val customAdapter = CustomAdapter(points, lifecycleScope, this@MapActivity) + recyclerView.adapter = customAdapter + + addMarkersToMap() + bottomText.text = "Up!" + } + + is ApiResponse.Error -> { + Toast.makeText( + this@MapActivity, + "Error loading points: ${response.errorMessage}", + Toast.LENGTH_LONG + ).show() + + /* val fallbackPoints = listOf( + Point(id = "1", x = 48.8566, y = 2.3522, type = "bench", name = "Bench 1"), + Point(id = "2", x = 48.8566, y = 2.3523, type = "bench", name = "Bench 2"), + Point(id = "3", x = 48.8566, y = 2.3524, type = "bench", name = "Bench 3"), + Point(id = "4", x = 48.8566, y = 2.3525, type = "bench", name = "Bench 4"), + Point(id = "5", x = 48.8566, y = 2.3526, type = "bench", name = "Bench 5") + ) + + points = fallbackPoints + val customAdapter = CustomAdapter(fallbackPoints, lifecycleScope, this@MapActivity) + recyclerView.adapter = customAdapter + + addMarkersToMap()*/ + bottomText.text = "Erreur bozo" + } + } + } + } + } + + private fun addMarkersToMap() { + while (map.overlays.size > 2) { + map.overlays.removeAt(map.overlays.size - 1) + } + + val items = ArrayList() + val decimalFormat = java.text.DecimalFormat("0.00") + val decimalFormatPopup = java.text.DecimalFormat("0.000") + + for (point in points) { + val item = OverlayItem( + point.name, + "Type: ${point.type} | Position: ${decimalFormat.format(point.x)}, ${decimalFormat.format(point.y)}", + GeoPoint(point.x, point.y) + ) + items.add(item) + } + + if (items.isNotEmpty()) { + val overlay = ItemizedOverlayWithFocus( + items, + object : ItemizedIconOverlay.OnItemGestureListener { + override fun onItemSingleTapUp(index: Int, item: OverlayItem): Boolean { + val point = points.getOrNull(index) + if (point != null) { + lifecycleScope.launch { + reviewRepository.getAverageRating(point.id).collectLatest { response -> + when (response) { + is ApiResponse.Success -> { + val rating = response.data + if (!point.picture.isNullOrEmpty()) { + showBenchPictureDialog( + point, item, rating, + decimalFormatPopup.format(point.x), + decimalFormatPopup.format(point.y) + ) + } else { + Toast.makeText( + this@MapActivity, + "${item.title}\nType: ${point.type} | Position: ${ + decimalFormatPopup.format( + point.x + ) + }, ${decimalFormatPopup.format(point.y)}\nRating: $rating/5", + Toast.LENGTH_SHORT + ).show() + } + } + + else -> { + if (!point.picture.isNullOrEmpty()) { + showBenchPictureDialog( + point, item, 0f, + decimalFormatPopup.format(point.x), + decimalFormatPopup.format(point.y) + ) + } else { + Toast.makeText( + this@MapActivity, + "${item.title}\nType: ${point.type} | Position: ${ + decimalFormatPopup.format( + point.x + ) + }, ${decimalFormatPopup.format(point.y)}", + Toast.LENGTH_SHORT + ).show() + } + } + } + } + } + } else { + Toast.makeText( + this@MapActivity, + "${item.title}\n${item.snippet}", + Toast.LENGTH_SHORT + ).show() + } + return true + } + + override fun onItemLongPress(index: Int, item: OverlayItem): Boolean { + return false + } + }, + this + ) + + overlay.setFocusItemsOnTap(false) + map.overlays.add(overlay) + map.invalidate() + } + } + + private fun loadMarkerImage(point: Point, item: OverlayItem) { + try { + Log.d(TAG, "Attempting to fetch image for marker: ${point.picture}") + + Glide.with(this) + .asBitmap() + .load(point.picture) + .into(object : CustomTarget(100, 100) { + override fun onResourceReady(resource: Bitmap, transition: Transition?) { + val drawable = resource.toDrawable(resources) + item.setMarker(drawable) + map.invalidate() + } + + override fun onLoadCleared(placeholder: Drawable?) { + } + + override fun onLoadFailed(errorDrawable: Drawable?) { + Log.e(TAG, "Echec de chargement de l'image du point ${point.id}") + } + }) + } catch (e: Exception) { + Log.e(TAG, "Echec de chargement du marqueur: ${e.message}") + } + } + + private fun showBenchPictureDialog(point: Point, item: OverlayItem, rating: Float, xTrunc: String, yTrunc: String) { + if (isDialogShowing) { + Log.d(TAG, "Dialogue déja ouvert!") + return + } + + isDialogShowing = true + + val dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_bench_details, null) + val dialog = android.app.AlertDialog.Builder(this) + .setView(dialogView) + .setOnDismissListener { + isDialogShowing = false + } + .create() + + val titleTextView = dialogView.findViewById(R.id.dialogTitle) + val messageTextView = dialogView.findViewById(R.id.dialogMessage) + val benchImageView = dialogView.findViewById(R.id.benchImageView) + + titleTextView.text = item.title + + messageTextView.text = "Chargement des informations..." + + Glide.with(this) + .load(point.picture) + .placeholder(R.drawable.ic_bench_icon) + .error(R.drawable.ic_bench_icon) + .into(benchImageView) + + dialogView.findViewById