From 75ae5f8e4b6e88e1c5ca767082a31c94e404b252 Mon Sep 17 00:00:00 2001 From: Quildra Date: Mon, 4 Aug 2025 18:41:06 +0100 Subject: [PATCH] feat: implement proper nested scrolling in history cards using architectural redesign MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replaced dynamic content loading with immediate population approach - Created dedicated container-level populate methods that match ResultsBottomDrawer - Restructured ViewHolder to recreate expanded content entirely when needed - Fixed content lifecycle: NestedScrollView now contains content at creation time - Added comprehensive helper methods for all UI elements (sections, rows, checkboxes) - Enabled proper scrollable behavior within expanded Pokemon data sections Key architectural change: Content is now populated immediately when NestedScrollView is created, matching the working ResultsBottomDrawer pattern, instead of being dynamically added later which prevented proper scroll recognition. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../pokegoalshelper/ui/HistoryAdapter.kt | 829 +++++++++--------- 1 file changed, 420 insertions(+), 409 deletions(-) diff --git a/app/src/main/java/com/quillstudios/pokegoalshelper/ui/HistoryAdapter.kt b/app/src/main/java/com/quillstudios/pokegoalshelper/ui/HistoryAdapter.kt index b06668d..ba052d4 100644 --- a/app/src/main/java/com/quillstudios/pokegoalshelper/ui/HistoryAdapter.kt +++ b/app/src/main/java/com/quillstudios/pokegoalshelper/ui/HistoryAdapter.kt @@ -178,6 +178,19 @@ class HistoryAdapter( } private fun createExpandedContent(context: Context): View + { + // Create placeholder that will be replaced when content is needed + return android.widget.FrameLayout(context).apply { + layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ) + tag = "expanded_content" + visibility = View.GONE + } + } + + private fun createPopulatedExpandedContent(context: Context, result: DetectionResult): View { return androidx.core.widget.NestedScrollView(context).apply { // Set a reasonable fixed height for the scrollable area @@ -189,6 +202,7 @@ class HistoryAdapter( // Configure scrolling behavior optimized for nested scrolling isFillViewport = false isNestedScrollingEnabled = true + tag = "expanded_content" val contentContainer = LinearLayout(context).apply { orientation = LinearLayout.VERTICAL @@ -200,6 +214,16 @@ class HistoryAdapter( tag = "expanded_container" } + // Populate content immediately - this is the key fix! + if (result.success && result.pokemonInfo != null) { + populatePokemonInfoViewsForContainer(result.pokemonInfo, contentContainer, context) + } else { + populateErrorInfoViewsForContainer(result, contentContainer, context) + } + + // Technical info + populateTechnicalInfoViewsForContainer(result, contentContainer, context) + addView(contentContainer) } } @@ -223,12 +247,368 @@ class HistoryAdapter( ).toInt() } + private fun populatePokemonInfoViewsForContainer(pokemonInfo: com.quillstudios.pokegoalshelper.PokemonInfo, container: LinearLayout, context: Context) + { + // Basic Pokemon Info Section + container.addView(createSectionHeaderForContainer("Pokemon Info", context)) + container.addView(createTwoColumnRowForContainer( + leftLabel = "Species", leftValue = pokemonInfo.species ?: "Unknown", + rightLabel = "Dex #", rightValue = pokemonInfo.nationalDexNumber?.let { "#$it" } ?: "N/A", + context = context + )) + + container.addView(createTwoColumnRowForContainer( + leftLabel = "Nickname", leftValue = pokemonInfo.nickname ?: "None", + rightLabel = "Gender", rightValue = pokemonInfo.gender ?: "Unknown", + context = context + )) + + container.addView(createTwoColumnRowForContainer( + leftLabel = "Level", leftValue = pokemonInfo.level?.toString() ?: "N/A", + rightLabel = "Nature", rightValue = pokemonInfo.nature ?: "Unknown", + context = context + )) + + // Types Section + container.addView(createSectionHeaderForContainer("Types", context)) + val typeDisplay = when { + pokemonInfo.primaryType != null && pokemonInfo.secondaryType != null -> + "${pokemonInfo.primaryType} / ${pokemonInfo.secondaryType}" + pokemonInfo.primaryType != null -> pokemonInfo.primaryType + else -> "Unknown" + } + container.addView(createTwoColumnRowForContainer( + leftLabel = "Type", leftValue = typeDisplay, + rightLabel = "Tera", rightValue = pokemonInfo.teraType ?: "N/A", + context = context + )) + + // Stats Section (if available) + pokemonInfo.stats?.let { stats -> + container.addView(createSectionHeaderForContainer("Base Stats", context)) + container.addView(createThreeColumnRowForContainer( + leftLabel = "HP", leftValue = stats.hp?.toString() ?: "?", + middleLabel = "ATK", middleValue = stats.attack?.toString() ?: "?", + rightLabel = "DEF", rightValue = stats.defense?.toString() ?: "?", + context = context + )) + container.addView(createThreeColumnRowForContainer( + leftLabel = "SP.ATK", leftValue = stats.spAttack?.toString() ?: "?", + middleLabel = "SP.DEF", middleValue = stats.spDefense?.toString() ?: "?", + rightLabel = "SPEED", rightValue = stats.speed?.toString() ?: "?", + context = context + )) + } + + // Special Properties Section + container.addView(createSectionHeaderForContainer("Properties", context)) + container.addView(createCheckboxRowForContainer( + leftLabel = "Shiny", leftChecked = pokemonInfo.isShiny, + rightLabel = "Alpha", rightChecked = pokemonInfo.isAlpha, + context = context + )) + container.addView(createMixedRowForContainer( + leftLabel = "Favorited", leftChecked = pokemonInfo.isFavorited, + rightLabel = "Pokeball", rightValue = pokemonInfo.pokeballType ?: "Unknown", + context = context + )) + + // Game Origin Section + container.addView(createSectionHeaderForContainer("Origin", context)) + container.addView(createTwoColumnRowForContainer( + leftLabel = "Game", leftValue = pokemonInfo.gameSource ?: "Unknown", + rightLabel = "Language", rightValue = pokemonInfo.language ?: "Unknown", + context = context + )) + + pokemonInfo.originalTrainerName?.let { trainerName -> + container.addView(createTwoColumnRowForContainer( + leftLabel = "OT Name", leftValue = trainerName, + rightLabel = "OT ID", rightValue = pokemonInfo.originalTrainerId ?: "Unknown", + context = context + )) + } + + // Ability & Moves + pokemonInfo.ability?.let { ability -> + container.addView(createSectionHeaderForContainer("Ability & Moves", context)) + container.addView(createInfoRowForContainer("Ability", ability, context)) + } + + if (pokemonInfo.moves.isNotEmpty()) { + if (pokemonInfo.ability == null) { + container.addView(createSectionHeaderForContainer("Moves", context)) + } + container.addView(createInfoRowForContainer("Moves", pokemonInfo.moves.take(4).joinToString(", "), context)) + } + + // Additional Data + if (pokemonInfo.stamps.isNotEmpty() || pokemonInfo.labels.isNotEmpty() || pokemonInfo.marks.isNotEmpty()) { + container.addView(createSectionHeaderForContainer("Additional", context)) + if (pokemonInfo.stamps.isNotEmpty()) { + container.addView(createInfoRowForContainer("Stamps", pokemonInfo.stamps.joinToString(", "), context)) + } + if (pokemonInfo.labels.isNotEmpty()) { + container.addView(createInfoRowForContainer("Labels", pokemonInfo.labels.joinToString(", "), context)) + } + if (pokemonInfo.marks.isNotEmpty()) { + container.addView(createInfoRowForContainer("Marks", pokemonInfo.marks.joinToString(", "), context)) + } + } + } + + private fun populateErrorInfoViewsForContainer(result: DetectionResult, container: LinearLayout, context: Context) + { + container.addView(createSectionHeaderForContainer("Error Details", context)) + container.addView(createInfoRowForContainer("Status", if (result.success) "No Pokemon found" else "Detection failed", context)) + result.errorMessage?.let { container.addView(createInfoRowForContainer("Error", it, context)) } + } + + private fun populateTechnicalInfoViewsForContainer(result: DetectionResult, container: LinearLayout, context: Context) + { + container.addView(createSectionHeaderForContainer("Technical Info", context)) + container.addView(createInfoRowForContainer("Processing Time", "${result.processingTimeMs}ms", context)) + container.addView(createInfoRowForContainer("Detections Found", result.detections.size.toString(), context)) + container.addView(createInfoRowForContainer("Timestamp", result.timestamp.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), context)) + } + + // Helper methods for container population - using the methods from the inner class + private fun createSectionHeaderForContainer(title: String, context: Context): TextView + { + return TextView(context).apply { + text = title + setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f) + setTextColor(ContextCompat.getColor(context, android.R.color.holo_blue_light)) + typeface = android.graphics.Typeface.DEFAULT_BOLD + setPadding(0, dpToPx(context, 8), 0, dpToPx(context, 4)) + } + } + + private fun createInfoRowForContainer(label: String, value: String, context: Context): LinearLayout + { + val row = LinearLayout(context).apply { + orientation = LinearLayout.HORIZONTAL + setPadding(0, dpToPx(context, 2), 0, dpToPx(context, 2)) + } + + val labelView = TextView(context).apply { + text = "$label:" + setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f) + setTextColor(ContextCompat.getColor(context, android.R.color.darker_gray)) + layoutParams = LinearLayout.LayoutParams( + dpToPx(context, 100), + ViewGroup.LayoutParams.WRAP_CONTENT + ) + } + + val valueView = TextView(context).apply { + text = value + setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f) + setTextColor(ContextCompat.getColor(context, android.R.color.white)) + layoutParams = LinearLayout.LayoutParams( + 0, + ViewGroup.LayoutParams.WRAP_CONTENT, + 1f + ) + } + + row.addView(labelView) + row.addView(valueView) + return row + } + + private fun createTwoColumnRowForContainer(leftLabel: String, leftValue: String, rightLabel: String, rightValue: String, context: Context): LinearLayout + { + val row = LinearLayout(context).apply { + orientation = LinearLayout.HORIZONTAL + setPadding(0, dpToPx(context, 2), 0, dpToPx(context, 2)) + } + + // Left column + val leftColumn = createColumnItemForContainer(leftLabel, leftValue, context) + leftColumn.layoutParams = LinearLayout.LayoutParams( + 0, + ViewGroup.LayoutParams.WRAP_CONTENT, + 1f + ) + + // Right column + val rightColumn = createColumnItemForContainer(rightLabel, rightValue, context) + rightColumn.layoutParams = LinearLayout.LayoutParams( + 0, + ViewGroup.LayoutParams.WRAP_CONTENT, + 1f + ).apply { + setMargins(dpToPx(context, 8), 0, 0, 0) + } + + row.addView(leftColumn) + row.addView(rightColumn) + return row + } + + private fun createThreeColumnRowForContainer(leftLabel: String, leftValue: String, middleLabel: String, middleValue: String, rightLabel: String, rightValue: String, context: Context): LinearLayout + { + val row = LinearLayout(context).apply { + orientation = LinearLayout.HORIZONTAL + setPadding(0, dpToPx(context, 2), 0, dpToPx(context, 2)) + } + + // Left column + val leftColumn = createColumnItemForContainer(leftLabel, leftValue, context) + leftColumn.layoutParams = LinearLayout.LayoutParams( + 0, + ViewGroup.LayoutParams.WRAP_CONTENT, + 1f + ) + + // Middle column + val middleColumn = createColumnItemForContainer(middleLabel, middleValue, context) + middleColumn.layoutParams = LinearLayout.LayoutParams( + 0, + ViewGroup.LayoutParams.WRAP_CONTENT, + 1f + ).apply { + setMargins(dpToPx(context, 4), 0, dpToPx(context, 4), 0) + } + + // Right column + val rightColumn = createColumnItemForContainer(rightLabel, rightValue, context) + rightColumn.layoutParams = LinearLayout.LayoutParams( + 0, + ViewGroup.LayoutParams.WRAP_CONTENT, + 1f + ) + + row.addView(leftColumn) + row.addView(middleColumn) + row.addView(rightColumn) + return row + } + + private fun createCheckboxRowForContainer(leftLabel: String, leftChecked: Boolean, rightLabel: String, rightChecked: Boolean, context: Context): LinearLayout + { + val row = LinearLayout(context).apply { + orientation = LinearLayout.HORIZONTAL + setPadding(0, dpToPx(context, 2), 0, dpToPx(context, 2)) + } + + // Left checkbox + val leftCheckbox = createCheckboxItemForContainer(leftLabel, leftChecked, context) + leftCheckbox.layoutParams = LinearLayout.LayoutParams( + 0, + ViewGroup.LayoutParams.WRAP_CONTENT, + 1f + ) + + // Right checkbox + val rightCheckbox = createCheckboxItemForContainer(rightLabel, rightChecked, context) + rightCheckbox.layoutParams = LinearLayout.LayoutParams( + 0, + ViewGroup.LayoutParams.WRAP_CONTENT, + 1f + ).apply { + setMargins(dpToPx(context, 8), 0, 0, 0) + } + + row.addView(leftCheckbox) + row.addView(rightCheckbox) + return row + } + + private fun createMixedRowForContainer(leftLabel: String, leftChecked: Boolean, rightLabel: String, rightValue: String, context: Context): LinearLayout + { + val row = LinearLayout(context).apply { + orientation = LinearLayout.HORIZONTAL + setPadding(0, dpToPx(context, 2), 0, dpToPx(context, 2)) + } + + // Left checkbox + val leftCheckbox = createCheckboxItemForContainer(leftLabel, leftChecked, context) + leftCheckbox.layoutParams = LinearLayout.LayoutParams( + 0, + ViewGroup.LayoutParams.WRAP_CONTENT, + 1f + ) + + // Right text item + val rightItem = createColumnItemForContainer(rightLabel, rightValue, context) + rightItem.layoutParams = LinearLayout.LayoutParams( + 0, + ViewGroup.LayoutParams.WRAP_CONTENT, + 1f + ).apply { + setMargins(dpToPx(context, 8), 0, 0, 0) + } + + row.addView(leftCheckbox) + row.addView(rightItem) + return row + } + + private fun createColumnItemForContainer(label: String, value: String, context: Context): LinearLayout + { + return LinearLayout(context).apply { + orientation = LinearLayout.VERTICAL + gravity = android.view.Gravity.START + + val labelView = TextView(context).apply { + text = "$label:" + setTextSize(TypedValue.COMPLEX_UNIT_SP, 9f) + setTextColor(ContextCompat.getColor(context, android.R.color.darker_gray)) + } + + val valueView = TextView(context).apply { + text = value + setTextSize(TypedValue.COMPLEX_UNIT_SP, 11f) + setTextColor(ContextCompat.getColor(context, android.R.color.white)) + typeface = android.graphics.Typeface.DEFAULT_BOLD + } + + addView(labelView) + addView(valueView) + } + } + + private fun createCheckboxItemForContainer(label: String, checked: Boolean, context: Context): LinearLayout + { + return LinearLayout(context).apply { + orientation = LinearLayout.HORIZONTAL + gravity = android.view.Gravity.CENTER_VERTICAL + + // Checkbox symbol + val checkboxView = TextView(context).apply { + text = if (checked) "☑" else "☐" + setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f) + setTextColor( + if (checked) ContextCompat.getColor(context, android.R.color.holo_green_light) + else ContextCompat.getColor(context, android.R.color.darker_gray) + ) + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { + setMargins(0, 0, dpToPx(context, 6), 0) + } + } + + // Label + val labelView = TextView(context).apply { + text = label + setTextSize(TypedValue.COMPLEX_UNIT_SP, 11f) + setTextColor(ContextCompat.getColor(context, android.R.color.white)) + } + + addView(checkboxView) + addView(labelView) + } + } + inner class HistoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { private val cardContainer = itemView as LinearLayout private val collapsedContent = itemView.findViewWithTag("collapsed_content") - private val expandedContent = itemView.findViewWithTag("expanded_content") - private val expandedContainer = expandedContent.findViewWithTag("expanded_container") + private var expandedContent: View? = null // Will be created when needed // Collapsed content views private val statusIcon = collapsedContent.findViewWithTag("status_icon") @@ -244,9 +624,9 @@ class HistoryAdapter( // Update collapsed content updateCollapsedContent(result, context) - // Update expanded content if needed + // Handle expanded content if (item.isExpanded) { - updateExpandedContent(result, context) + ensureExpandedContent(result, context) showExpandedContent() } else { hideExpandedContent() @@ -256,11 +636,29 @@ class HistoryAdapter( collapsedContent.setOnClickListener { onItemClick(result, position) } + } + + private fun ensureExpandedContent(result: DetectionResult, context: Context) + { + // Remove existing expanded content if any + expandedContent?.let { existing -> + if (existing.parent == cardContainer) { + cardContainer.removeView(existing) + } + } - // Add delete button in expanded state - if (item.isExpanded) { - addDeleteButton(result, position, context) + // Create new expanded content with actual data + expandedContent = createPopulatedExpandedContent(context, result).apply { + // Add delete button to the content + val scrollView = this as androidx.core.widget.NestedScrollView + val container = scrollView.findViewWithTag("expanded_container") + container?.let { + addDeleteButton(result, adapterPosition, context, it) + } } + + // Add to card container + expandedContent?.let { cardContainer.addView(it) } } private fun updateCollapsedContent(result: DetectionResult, context: Context) @@ -293,399 +691,34 @@ class HistoryAdapter( subtitleText.text = "${result.timestamp.format(formatter)} • ${result.processingTimeMs}ms" // Chevron rotation based on expansion state - val rotation = if (expandedContent.visibility == View.VISIBLE) 180f else 0f + val rotation = if (expandedContent?.visibility == View.VISIBLE) 180f else 0f chevronIcon.rotation = rotation } - private fun updateExpandedContent(result: DetectionResult, context: Context) - { - expandedContainer.removeAllViews() - - if (result.success && result.pokemonInfo != null) { - addPokemonInfoViews(result.pokemonInfo, context) - } else { - addErrorInfoViews(result, context) - } - - // Technical info - addTechnicalInfoViews(result, context) - } - - private fun addPokemonInfoViews(pokemonInfo: com.quillstudios.pokegoalshelper.PokemonInfo, context: Context) - { - // Basic Pokemon Info Section - addSectionHeader("Pokemon Info", context) - addTwoColumnRow( - leftLabel = "Species", leftValue = pokemonInfo.species ?: "Unknown", - rightLabel = "Dex #", rightValue = pokemonInfo.nationalDexNumber?.let { "#$it" } ?: "N/A", - context = context - ) - - addTwoColumnRow( - leftLabel = "Nickname", leftValue = pokemonInfo.nickname ?: "None", - rightLabel = "Gender", rightValue = pokemonInfo.gender ?: "Unknown", - context = context - ) - - addTwoColumnRow( - leftLabel = "Level", leftValue = pokemonInfo.level?.toString() ?: "N/A", - rightLabel = "Nature", rightValue = pokemonInfo.nature ?: "Unknown", - context = context - ) - - // Types Section - addSectionHeader("Types", context) - val typeDisplay = when { - pokemonInfo.primaryType != null && pokemonInfo.secondaryType != null -> - "${pokemonInfo.primaryType} / ${pokemonInfo.secondaryType}" - pokemonInfo.primaryType != null -> pokemonInfo.primaryType - else -> "Unknown" - } - addTwoColumnRow( - leftLabel = "Type", leftValue = typeDisplay, - rightLabel = "Tera", rightValue = pokemonInfo.teraType ?: "N/A", - context = context - ) - - // Stats Section (if available) - pokemonInfo.stats?.let { stats -> - addSectionHeader("Base Stats", context) - addThreeColumnRow( - leftLabel = "HP", leftValue = stats.hp?.toString() ?: "?", - middleLabel = "ATK", middleValue = stats.attack?.toString() ?: "?", - rightLabel = "DEF", rightValue = stats.defense?.toString() ?: "?", - context = context - ) - addThreeColumnRow( - leftLabel = "SP.ATK", leftValue = stats.spAttack?.toString() ?: "?", - middleLabel = "SP.DEF", middleValue = stats.spDefense?.toString() ?: "?", - rightLabel = "SPEED", rightValue = stats.speed?.toString() ?: "?", - context = context - ) - } - - // Special Properties Section - addSectionHeader("Properties", context) - addCheckboxRow( - leftLabel = "Shiny", leftChecked = pokemonInfo.isShiny, - rightLabel = "Alpha", rightChecked = pokemonInfo.isAlpha, - context = context - ) - addMixedRow( - leftLabel = "Favorited", leftChecked = pokemonInfo.isFavorited, - rightLabel = "Pokeball", rightValue = pokemonInfo.pokeballType ?: "Unknown", - context = context - ) - - // Game Origin Section - addSectionHeader("Origin", context) - addTwoColumnRow( - leftLabel = "Game", leftValue = pokemonInfo.gameSource ?: "Unknown", - rightLabel = "Language", rightValue = pokemonInfo.language ?: "Unknown", - context = context - ) - - pokemonInfo.originalTrainerName?.let { trainerName -> - addTwoColumnRow( - leftLabel = "OT Name", leftValue = trainerName, - rightLabel = "OT ID", rightValue = pokemonInfo.originalTrainerId ?: "Unknown", - context = context - ) - } - - // Ability & Moves - pokemonInfo.ability?.let { ability -> - addSectionHeader("Ability & Moves", context) - addInfoRow("Ability", ability, context) - } - - if (pokemonInfo.moves.isNotEmpty()) { - if (pokemonInfo.ability == null) { - addSectionHeader("Moves", context) - } - addInfoRow("Moves", pokemonInfo.moves.take(4).joinToString(", "), context) - } - - // Additional Data - if (pokemonInfo.stamps.isNotEmpty() || pokemonInfo.labels.isNotEmpty() || pokemonInfo.marks.isNotEmpty()) { - addSectionHeader("Additional", context) - if (pokemonInfo.stamps.isNotEmpty()) { - addInfoRow("Stamps", pokemonInfo.stamps.joinToString(", "), context) - } - if (pokemonInfo.labels.isNotEmpty()) { - addInfoRow("Labels", pokemonInfo.labels.joinToString(", "), context) - } - if (pokemonInfo.marks.isNotEmpty()) { - addInfoRow("Marks", pokemonInfo.marks.joinToString(", "), context) - } - } - } - - private fun addErrorInfoViews(result: DetectionResult, context: Context) - { - addSectionHeader("Error Details", context) - addInfoRow("Status", if (result.success) "No Pokemon found" else "Detection failed", context) - result.errorMessage?.let { addInfoRow("Error", it, context) } - } - - private fun addTechnicalInfoViews(result: DetectionResult, context: Context) - { - addSectionHeader("Technical Info", context) - addInfoRow("Processing Time", "${result.processingTimeMs}ms", context) - addInfoRow("Detections Found", result.detections.size.toString(), context) - addInfoRow("Timestamp", result.timestamp.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), context) - } - - private fun addSectionHeader(title: String, context: Context) - { - val header = TextView(context).apply { - text = title - setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f) - setTextColor(ContextCompat.getColor(context, android.R.color.holo_blue_light)) - typeface = android.graphics.Typeface.DEFAULT_BOLD - setPadding(0, dpToPx(context, 8), 0, dpToPx(context, 4)) - } - expandedContainer.addView(header) - } - - private fun addInfoRow(label: String, value: String, context: Context) - { - val row = LinearLayout(context).apply { - orientation = LinearLayout.HORIZONTAL - setPadding(0, dpToPx(context, 2), 0, dpToPx(context, 2)) - } - - val labelView = TextView(context).apply { - text = "$label:" - setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f) - setTextColor(ContextCompat.getColor(context, android.R.color.darker_gray)) - layoutParams = LinearLayout.LayoutParams( - dpToPx(context, 100), - ViewGroup.LayoutParams.WRAP_CONTENT - ) - } - - val valueView = TextView(context).apply { - text = value - setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f) - setTextColor(ContextCompat.getColor(context, android.R.color.white)) - layoutParams = LinearLayout.LayoutParams( - 0, - ViewGroup.LayoutParams.WRAP_CONTENT, - 1f - ) - } - - row.addView(labelView) - row.addView(valueView) - expandedContainer.addView(row) - } - - private fun addTwoColumnRow( - leftLabel: String, leftValue: String, - rightLabel: String, rightValue: String, - context: Context - ) - { - val row = LinearLayout(context).apply { - orientation = LinearLayout.HORIZONTAL - setPadding(0, dpToPx(context, 2), 0, dpToPx(context, 2)) - } - - // Left column - val leftColumn = createColumnItem(leftLabel, leftValue, context) - leftColumn.layoutParams = LinearLayout.LayoutParams( - 0, - ViewGroup.LayoutParams.WRAP_CONTENT, - 1f - ) - - // Right column - val rightColumn = createColumnItem(rightLabel, rightValue, context) - rightColumn.layoutParams = LinearLayout.LayoutParams( - 0, - ViewGroup.LayoutParams.WRAP_CONTENT, - 1f - ).apply { - setMargins(dpToPx(context, 8), 0, 0, 0) - } - - row.addView(leftColumn) - row.addView(rightColumn) - expandedContainer.addView(row) - } - - private fun addThreeColumnRow( - leftLabel: String, leftValue: String, - middleLabel: String, middleValue: String, - rightLabel: String, rightValue: String, - context: Context - ) - { - val row = LinearLayout(context).apply { - orientation = LinearLayout.HORIZONTAL - setPadding(0, dpToPx(context, 2), 0, dpToPx(context, 2)) - } - - // Left column - val leftColumn = createColumnItem(leftLabel, leftValue, context) - leftColumn.layoutParams = LinearLayout.LayoutParams( - 0, - ViewGroup.LayoutParams.WRAP_CONTENT, - 1f - ) - - // Middle column - val middleColumn = createColumnItem(middleLabel, middleValue, context) - middleColumn.layoutParams = LinearLayout.LayoutParams( - 0, - ViewGroup.LayoutParams.WRAP_CONTENT, - 1f - ).apply { - setMargins(dpToPx(context, 4), 0, dpToPx(context, 4), 0) - } - - // Right column - val rightColumn = createColumnItem(rightLabel, rightValue, context) - rightColumn.layoutParams = LinearLayout.LayoutParams( - 0, - ViewGroup.LayoutParams.WRAP_CONTENT, - 1f - ) - - row.addView(leftColumn) - row.addView(middleColumn) - row.addView(rightColumn) - expandedContainer.addView(row) - } - - private fun createColumnItem(label: String, value: String, context: Context): LinearLayout - { - return LinearLayout(context).apply { - orientation = LinearLayout.VERTICAL - gravity = android.view.Gravity.START - - val labelView = TextView(context).apply { - text = "$label:" - setTextSize(TypedValue.COMPLEX_UNIT_SP, 9f) - setTextColor(ContextCompat.getColor(context, android.R.color.darker_gray)) - } - - val valueView = TextView(context).apply { - text = value - setTextSize(TypedValue.COMPLEX_UNIT_SP, 11f) - setTextColor(ContextCompat.getColor(context, android.R.color.white)) - typeface = android.graphics.Typeface.DEFAULT_BOLD - } - - addView(labelView) - addView(valueView) - } - } - private fun addCheckboxRow( - leftLabel: String, leftChecked: Boolean, - rightLabel: String, rightChecked: Boolean, - context: Context - ) + private fun showExpandedContent() { - val row = LinearLayout(context).apply { - orientation = LinearLayout.HORIZONTAL - setPadding(0, dpToPx(context, 2), 0, dpToPx(context, 2)) - } + expandedContent?.visibility = View.VISIBLE - // Left checkbox - val leftCheckbox = createCheckboxItem(leftLabel, leftChecked, context) - leftCheckbox.layoutParams = LinearLayout.LayoutParams( - 0, - ViewGroup.LayoutParams.WRAP_CONTENT, - 1f - ) - - // Right checkbox - val rightCheckbox = createCheckboxItem(rightLabel, rightChecked, context) - rightCheckbox.layoutParams = LinearLayout.LayoutParams( - 0, - ViewGroup.LayoutParams.WRAP_CONTENT, - 1f - ).apply { - setMargins(dpToPx(context, 8), 0, 0, 0) + // Animate chevron rotation + ObjectAnimator.ofFloat(chevronIcon, "rotation", 0f, 180f).apply { + duration = 200L + start() } - - row.addView(leftCheckbox) - row.addView(rightCheckbox) - expandedContainer.addView(row) } - private fun addMixedRow( - leftLabel: String, leftChecked: Boolean, - rightLabel: String, rightValue: String, - context: Context - ) + private fun hideExpandedContent() { - val row = LinearLayout(context).apply { - orientation = LinearLayout.HORIZONTAL - setPadding(0, dpToPx(context, 2), 0, dpToPx(context, 2)) - } + expandedContent?.visibility = View.GONE - // Left checkbox - val leftCheckbox = createCheckboxItem(leftLabel, leftChecked, context) - leftCheckbox.layoutParams = LinearLayout.LayoutParams( - 0, - ViewGroup.LayoutParams.WRAP_CONTENT, - 1f - ) - - // Right text item - val rightItem = createColumnItem(rightLabel, rightValue, context) - rightItem.layoutParams = LinearLayout.LayoutParams( - 0, - ViewGroup.LayoutParams.WRAP_CONTENT, - 1f - ).apply { - setMargins(dpToPx(context, 8), 0, 0, 0) - } - - row.addView(leftCheckbox) - row.addView(rightItem) - expandedContainer.addView(row) - } - - private fun createCheckboxItem(label: String, checked: Boolean, context: Context): LinearLayout - { - return LinearLayout(context).apply { - orientation = LinearLayout.HORIZONTAL - gravity = android.view.Gravity.CENTER_VERTICAL - - // Checkbox symbol - val checkboxView = TextView(context).apply { - text = if (checked) "☑" else "☐" - setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f) - setTextColor( - if (checked) ContextCompat.getColor(context, android.R.color.holo_green_light) - else ContextCompat.getColor(context, android.R.color.darker_gray) - ) - layoutParams = LinearLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ).apply { - setMargins(0, 0, dpToPx(context, 6), 0) - } - } - - // Label - val labelView = TextView(context).apply { - text = label - setTextSize(TypedValue.COMPLEX_UNIT_SP, 11f) - setTextColor(ContextCompat.getColor(context, android.R.color.white)) - } - - addView(checkboxView) - addView(labelView) + // Animate chevron rotation + ObjectAnimator.ofFloat(chevronIcon, "rotation", 180f, 0f).apply { + duration = 200L + start() } } - private fun addDeleteButton(result: DetectionResult, position: Int, context: Context) + private fun addDeleteButton(result: DetectionResult, position: Int, context: Context, container: LinearLayout) { val deleteButton = Button(context).apply { text = "Delete" @@ -708,29 +741,7 @@ class HistoryAdapter( } } - expandedContainer.addView(deleteButton) - } - - private fun showExpandedContent() - { - expandedContent.visibility = View.VISIBLE - - // Animate chevron rotation - ObjectAnimator.ofFloat(chevronIcon, "rotation", 0f, 180f).apply { - duration = 200L - start() - } - } - - private fun hideExpandedContent() - { - expandedContent.visibility = View.GONE - - // Animate chevron rotation - ObjectAnimator.ofFloat(chevronIcon, "rotation", 180f, 0f).apply { - duration = 200L - start() - } + container.addView(deleteButton) } } } \ No newline at end of file