diff --git a/app/src/main/java/com/quillstudios/pokegoalshelper/models/DetectionResult.kt b/app/src/main/java/com/quillstudios/pokegoalshelper/models/DetectionResult.kt index 773e38c..4568932 100644 --- a/app/src/main/java/com/quillstudios/pokegoalshelper/models/DetectionResult.kt +++ b/app/src/main/java/com/quillstudios/pokegoalshelper/models/DetectionResult.kt @@ -12,7 +12,7 @@ data class DetectionResult( val id: String = UUID.randomUUID().toString(), val timestamp: LocalDateTime = LocalDateTime.now(), val detections: List, - val pokemonInfo: PokemonDetectionInfo?, + val pokemonInfo: com.quillstudios.pokegoalshelper.PokemonInfo?, val processingTimeMs: Long, val success: Boolean, val errorMessage: String? = null @@ -52,9 +52,11 @@ data class PokemonDetectionStats( */ data class DetectionFilter( val pokemonName: String? = null, - val minCP: Int? = null, - val maxCP: Int? = null, - val minIV: Float? = null, + val minLevel: Int? = null, + val maxLevel: Int? = null, + val isShiny: Boolean? = null, + val isAlpha: Boolean? = null, + val gameSource: String? = null, val dateRange: Pair? = null, val successOnly: Boolean = false, val limit: Int? = null @@ -67,10 +69,10 @@ enum class DetectionSortBy(val description: String) { TIMESTAMP_DESC("Newest first"), TIMESTAMP_ASC("Oldest first"), - CP_DESC("Highest CP first"), - CP_ASC("Lowest CP first"), - IV_DESC("Best IV first"), - IV_ASC("Worst IV first"), + LEVEL_DESC("Highest level first"), + LEVEL_ASC("Lowest level first"), + CONFIDENCE_DESC("Best extraction confidence first"), + CONFIDENCE_ASC("Worst extraction confidence first"), NAME_ASC("Name A-Z"), NAME_DESC("Name Z-A") } \ No newline at end of file diff --git a/app/src/main/java/com/quillstudios/pokegoalshelper/storage/InMemoryStorageService.kt b/app/src/main/java/com/quillstudios/pokegoalshelper/storage/InMemoryStorageService.kt index fabeeb1..95a18e6 100644 --- a/app/src/main/java/com/quillstudios/pokegoalshelper/storage/InMemoryStorageService.kt +++ b/app/src/main/java/com/quillstudios/pokegoalshelper/storage/InMemoryStorageService.kt @@ -191,19 +191,28 @@ class InMemoryStorageService : StorageInterface // Pokemon name filter if (filter.pokemonName != null && - result.pokemonInfo?.name?.contains(filter.pokemonName, ignoreCase = true) != true) + result.pokemonInfo?.species?.contains(filter.pokemonName, ignoreCase = true) != true) { return@filter false } - // CP range filter - val cp = result.pokemonInfo?.cp - if (filter.minCP != null && (cp == null || cp < filter.minCP)) return@filter false - if (filter.maxCP != null && (cp == null || cp > filter.maxCP)) return@filter false + // Level range filter + val level = result.pokemonInfo?.level + if (filter.minLevel != null && (level == null || level < filter.minLevel)) return@filter false + if (filter.maxLevel != null && (level == null || level > filter.maxLevel)) return@filter false - // IV filter - val iv = result.pokemonInfo?.stats?.perfectIV - if (filter.minIV != null && (iv == null || iv < filter.minIV)) return@filter false + // Shiny filter + if (filter.isShiny != null && result.pokemonInfo?.isShiny != filter.isShiny) return@filter false + + // Alpha filter + if (filter.isAlpha != null && result.pokemonInfo?.isAlpha != filter.isAlpha) return@filter false + + // Game source filter + if (filter.gameSource != null && + result.pokemonInfo?.gameSource?.contains(filter.gameSource, ignoreCase = true) != true) + { + return@filter false + } // Date range filter if (filter.dateRange != null) @@ -228,12 +237,12 @@ class InMemoryStorageService : StorageInterface { DetectionSortBy.TIMESTAMP_DESC -> results.sortedByDescending { it.timestamp } DetectionSortBy.TIMESTAMP_ASC -> results.sortedBy { it.timestamp } - DetectionSortBy.CP_DESC -> results.sortedByDescending { it.pokemonInfo?.cp ?: -1 } - DetectionSortBy.CP_ASC -> results.sortedBy { it.pokemonInfo?.cp ?: Int.MAX_VALUE } - DetectionSortBy.IV_DESC -> results.sortedByDescending { it.pokemonInfo?.stats?.perfectIV ?: -1f } - DetectionSortBy.IV_ASC -> results.sortedBy { it.pokemonInfo?.stats?.perfectIV ?: Float.MAX_VALUE } - DetectionSortBy.NAME_ASC -> results.sortedBy { it.pokemonInfo?.name ?: "zzz" } - DetectionSortBy.NAME_DESC -> results.sortedByDescending { it.pokemonInfo?.name ?: "" } + DetectionSortBy.LEVEL_DESC -> results.sortedByDescending { it.pokemonInfo?.level ?: -1 } + DetectionSortBy.LEVEL_ASC -> results.sortedBy { it.pokemonInfo?.level ?: Int.MAX_VALUE } + DetectionSortBy.CONFIDENCE_DESC -> results.sortedByDescending { it.pokemonInfo?.extractionConfidence ?: -1.0 } + DetectionSortBy.CONFIDENCE_ASC -> results.sortedBy { it.pokemonInfo?.extractionConfidence ?: Double.MAX_VALUE } + DetectionSortBy.NAME_ASC -> results.sortedBy { it.pokemonInfo?.species ?: "zzz" } + DetectionSortBy.NAME_DESC -> results.sortedByDescending { it.pokemonInfo?.species ?: "" } } } diff --git a/app/src/main/java/com/quillstudios/pokegoalshelper/ui/DetectionResultHandler.kt b/app/src/main/java/com/quillstudios/pokegoalshelper/ui/DetectionResultHandler.kt index c52b10e..2ee5d45 100644 --- a/app/src/main/java/com/quillstudios/pokegoalshelper/ui/DetectionResultHandler.kt +++ b/app/src/main/java/com/quillstudios/pokegoalshelper/ui/DetectionResultHandler.kt @@ -3,8 +3,6 @@ package com.quillstudios.pokegoalshelper.ui import android.content.Context import com.quillstudios.pokegoalshelper.di.ServiceLocator import com.quillstudios.pokegoalshelper.models.DetectionResult -import com.quillstudios.pokegoalshelper.models.PokemonDetectionInfo -import com.quillstudios.pokegoalshelper.models.PokemonDetectionStats import com.quillstudios.pokegoalshelper.ml.Detection import com.quillstudios.pokegoalshelper.utils.PGHLog import kotlinx.coroutines.CoroutineScope @@ -30,7 +28,7 @@ class DetectionResultHandler(private val context: Context) /** * Handle successful detection results. - * Converts old PokemonInfo format to new DetectionResult format. + * Uses the actual PokemonInfo directly without conversion. */ fun handleSuccessfulDetection( detections: List, @@ -41,13 +39,10 @@ class DetectionResultHandler(private val context: Context) coroutineScope.launch { try { - // Convert old PokemonInfo to new format - val detectionInfo = pokemonInfo?.let { convertPokemonInfo(it) } - val result = DetectionResult( timestamp = LocalDateTime.now(), detections = detections, - pokemonInfo = detectionInfo, + pokemonInfo = pokemonInfo, processingTimeMs = processingTimeMs, success = pokemonInfo != null, errorMessage = null @@ -135,53 +130,6 @@ class DetectionResultHandler(private val context: Context) handleFailedDetection(detections, "No Pokemon detected in current view", processingTimeMs) } - /** - * Convert old PokemonInfo format to new PokemonDetectionInfo format. - */ - private fun convertPokemonInfo(oldInfo: com.quillstudios.pokegoalshelper.PokemonInfo): PokemonDetectionInfo - { - // Extract basic stats from old format - val stats = oldInfo.stats?.let { oldStats -> - PokemonDetectionStats( - attack = oldStats.attack, - defense = oldStats.defense, - stamina = oldStats.hp, // HP maps to stamina in Pokemon GO - perfectIV = calculatePerfectIV(oldStats), - attackIV = null, // Not available in old format - defenseIV = null, - staminaIV = null - ) - } - - return PokemonDetectionInfo( - name = oldInfo.species ?: oldInfo.nickname, - cp = null, // Not available in old format - this is Pokemon Home, not GO - hp = oldInfo.stats?.hp, - level = null, // Not available in old format - nationalDexNumber = oldInfo.nationalDexNumber, - stats = stats, - form = null, // Could be extracted from species string if needed - gender = oldInfo.gender - ) - } - - /** - * Calculate perfect IV percentage from stats. - */ - private fun calculatePerfectIV(stats: com.quillstudios.pokegoalshelper.PokemonStats): Float? - { - // This is a simplified calculation - in reality, IV calculation is more complex - val attack = stats.attack ?: return null - val defense = stats.defense ?: return null - val hp = stats.hp ?: return null - - // Max stats vary by Pokemon, but this gives a rough percentage - // In Pokemon Home context, this might not be accurate IVs - val totalStats = attack + defense + hp - val maxPossibleTotal = 300f // Rough estimate - - return (totalStats.toFloat() / maxPossibleTotal * 100f).coerceAtMost(100f) - } /** * Hide the bottom drawer if currently showing. diff --git a/app/src/main/java/com/quillstudios/pokegoalshelper/ui/ResultsBottomDrawer.kt b/app/src/main/java/com/quillstudios/pokegoalshelper/ui/ResultsBottomDrawer.kt index 4465807..ca7cf8c 100644 --- a/app/src/main/java/com/quillstudios/pokegoalshelper/ui/ResultsBottomDrawer.kt +++ b/app/src/main/java/com/quillstudios/pokegoalshelper/ui/ResultsBottomDrawer.kt @@ -35,7 +35,7 @@ class ResultsBottomDrawer(private val context: Context) { private const val TAG = "ResultsBottomDrawer" private const val DRAWER_HEIGHT_COLLAPSED_DP = 80 - private const val DRAWER_HEIGHT_EXPANDED_DP = 240 + private const val DRAWER_HEIGHT_EXPANDED_DP = 400 // Increased to show all data private const val SLIDE_ANIMATION_DURATION = 300L private const val SWIPE_THRESHOLD = 100f private const val EXPAND_THRESHOLD = -50f // Negative because we're pulling up @@ -237,14 +237,13 @@ class ResultsBottomDrawer(private val context: Context) val pokemonInfo = result.pokemonInfo val dataPoints = mutableListOf() - // Collect all available data points - pokemonInfo.name?.let { dataPoints.add(it) } + // Collect all available data points from actual PokemonInfo structure + pokemonInfo.species?.let { dataPoints.add(it) } pokemonInfo.nationalDexNumber?.let { dataPoints.add("#$it") } - pokemonInfo.cp?.let { dataPoints.add("CP $it") } - pokemonInfo.level?.let { dataPoints.add("Lv${String.format("%.1f", it)}") } - pokemonInfo.hp?.let { dataPoints.add("${it}HP") } - pokemonInfo.stats?.perfectIV?.let { dataPoints.add("${String.format("%.1f", it)}%") } + pokemonInfo.level?.let { dataPoints.add("Lv$it") } pokemonInfo.gender?.let { dataPoints.add(it) } + if (pokemonInfo.isShiny) dataPoints.add("✨") + if (pokemonInfo.isAlpha) dataPoints.add("🅰") // Add processing time dataPoints.add("${result.processingTimeMs}ms") @@ -293,48 +292,99 @@ class ResultsBottomDrawer(private val context: Context) { val pokemonInfo = result.pokemonInfo - // Pokemon Basic Info Section + // Basic Pokemon Info Section addView(createSectionHeader("Pokemon Info")) addView(createTwoColumnRow( - leftLabel = "Name", leftValue = pokemonInfo.name ?: "Unknown", + leftLabel = "Species", leftValue = pokemonInfo.species ?: "Unknown", rightLabel = "Dex #", rightValue = pokemonInfo.nationalDexNumber?.let { "#$it" } ?: "N/A" )) addView(createTwoColumnRow( - leftLabel = "Gender", leftValue = pokemonInfo.gender ?: "Unknown", - rightLabel = "Form", rightValue = pokemonInfo.form ?: "Normal" + leftLabel = "Nickname", leftValue = pokemonInfo.nickname ?: "None", + rightLabel = "Gender", rightValue = pokemonInfo.gender ?: "Unknown" )) - // Combat Stats Section - addView(createSectionHeader("Combat Stats")) addView(createTwoColumnRow( - leftLabel = "CP", leftValue = pokemonInfo.cp?.toString() ?: "N/A", - rightLabel = "HP", rightValue = pokemonInfo.hp?.toString() ?: "N/A" + leftLabel = "Level", leftValue = pokemonInfo.level?.toString() ?: "N/A", + rightLabel = "Nature", rightValue = pokemonInfo.nature ?: "Unknown" )) + // Types Section + addView(createSectionHeader("Types")) + val typeDisplay = when { + pokemonInfo.primaryType != null && pokemonInfo.secondaryType != null -> + "${pokemonInfo.primaryType} / ${pokemonInfo.secondaryType}" + pokemonInfo.primaryType != null -> pokemonInfo.primaryType + else -> "Unknown" + } addView(createTwoColumnRow( - leftLabel = "Level", leftValue = pokemonInfo.level?.let { String.format("%.1f", it) } ?: "N/A", - rightLabel = "IV %", rightValue = pokemonInfo.stats?.perfectIV?.let { "${String.format("%.1f", it)}%" } ?: "N/A" + leftLabel = "Type", leftValue = typeDisplay, + rightLabel = "Tera", rightValue = pokemonInfo.teraType ?: "N/A" )) - // Individual Stats Section (if available) + // Stats Section (if available) pokemonInfo.stats?.let { stats -> - if (stats.attack != null || stats.defense != null || stats.stamina != null) { - addView(createSectionHeader("Individual Stats")) - addView(createThreeColumnRow( - leftLabel = "ATK", leftValue = stats.attack?.toString() ?: "?", - middleLabel = "DEF", middleValue = stats.defense?.toString() ?: "?", - rightLabel = "STA", rightValue = stats.stamina?.toString() ?: "?" - )) - } + addView(createSectionHeader("Base Stats")) + addView(createThreeColumnRow( + leftLabel = "HP", leftValue = stats.hp?.toString() ?: "?", + middleLabel = "ATK", middleValue = stats.attack?.toString() ?: "?", + rightLabel = "DEF", rightValue = stats.defense?.toString() ?: "?" + )) + addView(createThreeColumnRow( + leftLabel = "SP.ATK", leftValue = stats.spAttack?.toString() ?: "?", + middleLabel = "SP.DEF", middleValue = stats.spDefense?.toString() ?: "?", + rightLabel = "SPEED", rightValue = stats.speed?.toString() ?: "?" + )) } // Special Properties Section addView(createSectionHeader("Properties")) addView(createCheckboxRow( - leftLabel = "Shiny", leftChecked = false, // TODO: get from pokemonInfo when available - rightLabel = "Alpha", rightChecked = false // TODO: get from pokemonInfo when available + leftLabel = "Shiny", leftChecked = pokemonInfo.isShiny, + rightLabel = "Alpha", rightChecked = pokemonInfo.isAlpha )) + addView(createMixedRow( + leftLabel = "Favorited", leftChecked = pokemonInfo.isFavorited, + rightLabel = "Pokeball", rightValue = pokemonInfo.pokeballType ?: "Unknown" + )) + + // Game Origin Section + addView(createSectionHeader("Origin")) + addView(createTwoColumnRow( + leftLabel = "Game", leftValue = pokemonInfo.gameSource ?: "Unknown", + rightLabel = "Language", rightValue = pokemonInfo.language ?: "Unknown" + )) + + pokemonInfo.originalTrainerName?.let { trainerName -> + addView(createTwoColumnRow( + leftLabel = "OT Name", leftValue = trainerName, + rightLabel = "OT ID", rightValue = pokemonInfo.originalTrainerId ?: "Unknown" + )) + } + + // Ability & Moves + pokemonInfo.ability?.let { ability -> + addView(createSectionHeader("Ability & Moves")) + addView(createDetailRow("Ability", ability)) + } + + if (pokemonInfo.moves.isNotEmpty()) { + addView(createDetailRow("Moves", pokemonInfo.moves.take(4).joinToString(", "))) + } + + // Additional Data + if (pokemonInfo.stamps.isNotEmpty() || pokemonInfo.labels.isNotEmpty() || pokemonInfo.marks.isNotEmpty()) { + addView(createSectionHeader("Additional")) + if (pokemonInfo.stamps.isNotEmpty()) { + addView(createDetailRow("Stamps", pokemonInfo.stamps.joinToString(", "))) + } + if (pokemonInfo.labels.isNotEmpty()) { + addView(createDetailRow("Labels", pokemonInfo.labels.joinToString(", "))) + } + if (pokemonInfo.marks.isNotEmpty()) { + addView(createDetailRow("Marks", pokemonInfo.marks.joinToString(", "))) + } + } } else { @@ -598,6 +648,45 @@ class ResultsBottomDrawer(private val context: Context) } } + private fun createMixedRow( + leftLabel: String, leftChecked: Boolean, + rightLabel: String, rightValue: String + ): LinearLayout + { + return LinearLayout(context).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + + // Left checkbox + val leftCheckbox = createCheckboxItem(leftLabel, leftChecked) + leftCheckbox.layoutParams = LinearLayout.LayoutParams( + 0, + ViewGroup.LayoutParams.WRAP_CONTENT, + 1f + ) + + // Right text item + val rightItem = createColumnItem(rightLabel, rightValue) + rightItem.layoutParams = LinearLayout.LayoutParams( + 0, + ViewGroup.LayoutParams.WRAP_CONTENT, + 1f + ).apply { + setMargins(dpToPx(8), 0, 0, 0) + } + + addView(leftCheckbox) + addView(rightItem) + + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { + setMargins(0, dpToPx(2), 0, dpToPx(2)) + } + } + } + private fun createDrawerBackground(): GradientDrawable { return GradientDrawable().apply {