Browse Source

feat: implement proper nested scrolling in history cards using architectural redesign

- 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 <noreply@anthropic.com>
feature/pgh-1-results-display-history
Quildra 5 months ago
parent
commit
75ae5f8e4b
  1. 811
      app/src/main/java/com/quillstudios/pokegoalshelper/ui/HistoryAdapter.kt

811
app/src/main/java/com/quillstudios/pokegoalshelper/ui/HistoryAdapter.kt

@ -178,6 +178,19 @@ class HistoryAdapter(
} }
private fun createExpandedContent(context: Context): View 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 { return androidx.core.widget.NestedScrollView(context).apply {
// Set a reasonable fixed height for the scrollable area // Set a reasonable fixed height for the scrollable area
@ -189,6 +202,7 @@ class HistoryAdapter(
// Configure scrolling behavior optimized for nested scrolling // Configure scrolling behavior optimized for nested scrolling
isFillViewport = false isFillViewport = false
isNestedScrollingEnabled = true isNestedScrollingEnabled = true
tag = "expanded_content"
val contentContainer = LinearLayout(context).apply { val contentContainer = LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL orientation = LinearLayout.VERTICAL
@ -200,6 +214,16 @@ class HistoryAdapter(
tag = "expanded_container" 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) addView(contentContainer)
} }
} }
@ -223,469 +247,478 @@ class HistoryAdapter(
).toInt() ).toInt()
} }
inner class HistoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) private fun populatePokemonInfoViewsForContainer(pokemonInfo: com.quillstudios.pokegoalshelper.PokemonInfo, container: LinearLayout, context: Context)
{ {
private val cardContainer = itemView as LinearLayout // Basic Pokemon Info Section
private val collapsedContent = itemView.findViewWithTag<LinearLayout>("collapsed_content") container.addView(createSectionHeaderForContainer("Pokemon Info", context))
private val expandedContent = itemView.findViewWithTag<androidx.core.widget.NestedScrollView>("expanded_content") container.addView(createTwoColumnRowForContainer(
private val expandedContainer = expandedContent.findViewWithTag<LinearLayout>("expanded_container") leftLabel = "Species", leftValue = pokemonInfo.species ?: "Unknown",
rightLabel = "Dex #", rightValue = pokemonInfo.nationalDexNumber?.let { "#$it" } ?: "N/A",
// Collapsed content views context = context
private val statusIcon = collapsedContent.findViewWithTag<ImageView>("status_icon") ))
private val titleText = collapsedContent.findViewWithTag<TextView>("title_text")
private val subtitleText = collapsedContent.findViewWithTag<TextView>("subtitle_text") container.addView(createTwoColumnRowForContainer(
private val chevronIcon = collapsedContent.findViewWithTag<TextView>("chevron_icon") 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
))
}
fun bind(item: HistoryItem, position: Int) // Special Properties Section
{ container.addView(createSectionHeaderForContainer("Properties", context))
val result = item.result container.addView(createCheckboxRowForContainer(
val context = itemView.context 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
))
}
// Update collapsed content // Ability & Moves
updateCollapsedContent(result, context) pokemonInfo.ability?.let { ability ->
container.addView(createSectionHeaderForContainer("Ability & Moves", context))
container.addView(createInfoRowForContainer("Ability", ability, context))
}
// Update expanded content if needed if (pokemonInfo.moves.isNotEmpty()) {
if (item.isExpanded) { if (pokemonInfo.ability == null) {
updateExpandedContent(result, context) container.addView(createSectionHeaderForContainer("Moves", context))
showExpandedContent()
} else {
hideExpandedContent()
} }
container.addView(createInfoRowForContainer("Moves", pokemonInfo.moves.take(4).joinToString(", "), context))
}
// Set click listeners // Additional Data
collapsedContent.setOnClickListener { if (pokemonInfo.stamps.isNotEmpty() || pokemonInfo.labels.isNotEmpty() || pokemonInfo.marks.isNotEmpty()) {
onItemClick(result, position) container.addView(createSectionHeaderForContainer("Additional", context))
if (pokemonInfo.stamps.isNotEmpty()) {
container.addView(createInfoRowForContainer("Stamps", pokemonInfo.stamps.joinToString(", "), context))
} }
if (pokemonInfo.labels.isNotEmpty()) {
// Add delete button in expanded state container.addView(createInfoRowForContainer("Labels", pokemonInfo.labels.joinToString(", "), context))
if (item.isExpanded) { }
addDeleteButton(result, position, context) if (pokemonInfo.marks.isNotEmpty()) {
container.addView(createInfoRowForContainer("Marks", pokemonInfo.marks.joinToString(", "), context))
} }
} }
}
private fun updateCollapsedContent(result: DetectionResult, context: Context) private fun populateErrorInfoViewsForContainer(result: DetectionResult, container: LinearLayout, context: Context)
{ {
// Status icon container.addView(createSectionHeaderForContainer("Error Details", context))
statusIcon.setImageResource( container.addView(createInfoRowForContainer("Status", if (result.success) "No Pokemon found" else "Detection failed", context))
if (result.success) android.R.drawable.ic_menu_myplaces result.errorMessage?.let { container.addView(createInfoRowForContainer("Error", it, context)) }
else android.R.drawable.ic_dialog_alert }
)
statusIcon.setColorFilter(
ContextCompat.getColor(
context,
if (result.success) android.R.color.holo_green_light
else android.R.color.holo_red_light
)
)
// Title (Pokemon name or status)
titleText.text = when {
result.success && result.pokemonInfo?.species != null -> {
result.pokemonInfo.species +
(result.pokemonInfo.nationalDexNumber?.let { " (#$it)" } ?: "")
}
result.success -> "Pokemon Detected"
else -> "Detection Failed"
}
// Subtitle (timestamp and processing time) private fun populateTechnicalInfoViewsForContainer(result: DetectionResult, container: LinearLayout, context: Context)
val formatter = DateTimeFormatter.ofPattern("MMM dd, HH:mm") {
subtitleText.text = "${result.timestamp.format(formatter)}${result.processingTimeMs}ms" 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))
}
// Chevron rotation based on expansion state // Helper methods for container population - using the methods from the inner class
val rotation = if (expandedContent.visibility == View.VISIBLE) 180f else 0f private fun createSectionHeaderForContainer(title: String, context: Context): TextView
chevronIcon.rotation = rotation {
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 updateExpandedContent(result: DetectionResult, context: Context) private fun createInfoRowForContainer(label: String, value: String, context: Context): LinearLayout
{ {
expandedContainer.removeAllViews() val row = LinearLayout(context).apply {
orientation = LinearLayout.HORIZONTAL
if (result.success && result.pokemonInfo != null) { setPadding(0, dpToPx(context, 2), 0, dpToPx(context, 2))
addPokemonInfoViews(result.pokemonInfo, context)
} else {
addErrorInfoViews(result, context)
}
// Technical info
addTechnicalInfoViews(result, context)
} }
private fun addPokemonInfoViews(pokemonInfo: com.quillstudios.pokegoalshelper.PokemonInfo, context: Context) val labelView = TextView(context).apply {
{ text = "$label:"
// Basic Pokemon Info Section setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f)
addSectionHeader("Pokemon Info", context) setTextColor(ContextCompat.getColor(context, android.R.color.darker_gray))
addTwoColumnRow( layoutParams = LinearLayout.LayoutParams(
leftLabel = "Species", leftValue = pokemonInfo.species ?: "Unknown", dpToPx(context, 100),
rightLabel = "Dex #", rightValue = pokemonInfo.nationalDexNumber?.let { "#$it" } ?: "N/A", ViewGroup.LayoutParams.WRAP_CONTENT
context = context
) )
}
addTwoColumnRow( val valueView = TextView(context).apply {
leftLabel = "Nickname", leftValue = pokemonInfo.nickname ?: "None", text = value
rightLabel = "Gender", rightValue = pokemonInfo.gender ?: "Unknown", setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f)
context = context setTextColor(ContextCompat.getColor(context, android.R.color.white))
layoutParams = LinearLayout.LayoutParams(
0,
ViewGroup.LayoutParams.WRAP_CONTENT,
1f
) )
}
addTwoColumnRow( row.addView(labelView)
leftLabel = "Level", leftValue = pokemonInfo.level?.toString() ?: "N/A", row.addView(valueView)
rightLabel = "Nature", rightValue = pokemonInfo.nature ?: "Unknown", return row
context = context }
)
// Types Section private fun createTwoColumnRowForContainer(leftLabel: String, leftValue: String, rightLabel: String, rightValue: String, context: Context): LinearLayout
addSectionHeader("Types", context) {
val typeDisplay = when { val row = LinearLayout(context).apply {
pokemonInfo.primaryType != null && pokemonInfo.secondaryType != null -> orientation = LinearLayout.HORIZONTAL
"${pokemonInfo.primaryType} / ${pokemonInfo.secondaryType}" setPadding(0, dpToPx(context, 2), 0, dpToPx(context, 2))
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) // Left column
pokemonInfo.stats?.let { stats -> val leftColumn = createColumnItemForContainer(leftLabel, leftValue, context)
addSectionHeader("Base Stats", context) leftColumn.layoutParams = LinearLayout.LayoutParams(
addThreeColumnRow( 0,
leftLabel = "HP", leftValue = stats.hp?.toString() ?: "?", ViewGroup.LayoutParams.WRAP_CONTENT,
middleLabel = "ATK", middleValue = stats.attack?.toString() ?: "?", 1f
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 // Right column
addSectionHeader("Properties", context) val rightColumn = createColumnItemForContainer(rightLabel, rightValue, context)
addCheckboxRow( rightColumn.layoutParams = LinearLayout.LayoutParams(
leftLabel = "Shiny", leftChecked = pokemonInfo.isShiny, 0,
rightLabel = "Alpha", rightChecked = pokemonInfo.isAlpha, ViewGroup.LayoutParams.WRAP_CONTENT,
context = context 1f
) ).apply {
addMixedRow( setMargins(dpToPx(context, 8), 0, 0, 0)
leftLabel = "Favorited", leftChecked = pokemonInfo.isFavorited, }
rightLabel = "Pokeball", rightValue = pokemonInfo.pokeballType ?: "Unknown",
context = context
)
// Game Origin Section row.addView(leftColumn)
addSectionHeader("Origin", context) row.addView(rightColumn)
addTwoColumnRow( return row
leftLabel = "Game", leftValue = pokemonInfo.gameSource ?: "Unknown", }
rightLabel = "Language", rightValue = pokemonInfo.language ?: "Unknown",
context = context
)
pokemonInfo.originalTrainerName?.let { trainerName -> private fun createThreeColumnRowForContainer(leftLabel: String, leftValue: String, middleLabel: String, middleValue: String, rightLabel: String, rightValue: String, context: Context): LinearLayout
addTwoColumnRow( {
leftLabel = "OT Name", leftValue = trainerName, val row = LinearLayout(context).apply {
rightLabel = "OT ID", rightValue = pokemonInfo.originalTrainerId ?: "Unknown", orientation = LinearLayout.HORIZONTAL
context = context setPadding(0, dpToPx(context, 2), 0, dpToPx(context, 2))
) }
}
// Ability & Moves // Left column
pokemonInfo.ability?.let { ability -> val leftColumn = createColumnItemForContainer(leftLabel, leftValue, context)
addSectionHeader("Ability & Moves", context) leftColumn.layoutParams = LinearLayout.LayoutParams(
addInfoRow("Ability", ability, context) 0,
} ViewGroup.LayoutParams.WRAP_CONTENT,
1f
)
if (pokemonInfo.moves.isNotEmpty()) { // Middle column
if (pokemonInfo.ability == null) { val middleColumn = createColumnItemForContainer(middleLabel, middleValue, context)
addSectionHeader("Moves", context) middleColumn.layoutParams = LinearLayout.LayoutParams(
} 0,
addInfoRow("Moves", pokemonInfo.moves.take(4).joinToString(", "), context) ViewGroup.LayoutParams.WRAP_CONTENT,
} 1f
).apply {
setMargins(dpToPx(context, 4), 0, dpToPx(context, 4), 0)
}
// Additional Data // Right column
if (pokemonInfo.stamps.isNotEmpty() || pokemonInfo.labels.isNotEmpty() || pokemonInfo.marks.isNotEmpty()) { val rightColumn = createColumnItemForContainer(rightLabel, rightValue, context)
addSectionHeader("Additional", context) rightColumn.layoutParams = LinearLayout.LayoutParams(
if (pokemonInfo.stamps.isNotEmpty()) { 0,
addInfoRow("Stamps", pokemonInfo.stamps.joinToString(", "), context) ViewGroup.LayoutParams.WRAP_CONTENT,
} 1f
if (pokemonInfo.labels.isNotEmpty()) { )
addInfoRow("Labels", pokemonInfo.labels.joinToString(", "), context)
} row.addView(leftColumn)
if (pokemonInfo.marks.isNotEmpty()) { row.addView(middleColumn)
addInfoRow("Marks", pokemonInfo.marks.joinToString(", "), context) 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))
} }
private fun addErrorInfoViews(result: DetectionResult, context: Context) // Left checkbox
{ val leftCheckbox = createCheckboxItemForContainer(leftLabel, leftChecked, context)
addSectionHeader("Error Details", context) leftCheckbox.layoutParams = LinearLayout.LayoutParams(
addInfoRow("Status", if (result.success) "No Pokemon found" else "Detection failed", context) 0,
result.errorMessage?.let { addInfoRow("Error", it, context) } 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)
} }
private fun addTechnicalInfoViews(result: DetectionResult, context: Context) row.addView(leftCheckbox)
{ row.addView(rightCheckbox)
addSectionHeader("Technical Info", context) return row
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 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))
} }
private fun addSectionHeader(title: String, context: Context) // Left checkbox
{ val leftCheckbox = createCheckboxItemForContainer(leftLabel, leftChecked, context)
val header = TextView(context).apply { leftCheckbox.layoutParams = LinearLayout.LayoutParams(
text = title 0,
setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f) ViewGroup.LayoutParams.WRAP_CONTENT,
setTextColor(ContextCompat.getColor(context, android.R.color.holo_blue_light)) 1f
typeface = android.graphics.Typeface.DEFAULT_BOLD )
setPadding(0, dpToPx(context, 8), 0, dpToPx(context, 4))
} // Right text item
expandedContainer.addView(header) val rightItem = createColumnItemForContainer(rightLabel, rightValue, context)
rightItem.layoutParams = LinearLayout.LayoutParams(
0,
ViewGroup.LayoutParams.WRAP_CONTENT,
1f
).apply {
setMargins(dpToPx(context, 8), 0, 0, 0)
} }
private fun addInfoRow(label: String, value: String, context: Context) row.addView(leftCheckbox)
{ row.addView(rightItem)
val row = LinearLayout(context).apply { return row
orientation = LinearLayout.HORIZONTAL }
setPadding(0, dpToPx(context, 2), 0, dpToPx(context, 2))
} 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 { val labelView = TextView(context).apply {
text = "$label:" text = "$label:"
setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f) setTextSize(TypedValue.COMPLEX_UNIT_SP, 9f)
setTextColor(ContextCompat.getColor(context, android.R.color.darker_gray)) setTextColor(ContextCompat.getColor(context, android.R.color.darker_gray))
layoutParams = LinearLayout.LayoutParams(
dpToPx(context, 100),
ViewGroup.LayoutParams.WRAP_CONTENT
)
} }
val valueView = TextView(context).apply { val valueView = TextView(context).apply {
text = value text = value
setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f) setTextSize(TypedValue.COMPLEX_UNIT_SP, 11f)
setTextColor(ContextCompat.getColor(context, android.R.color.white)) setTextColor(ContextCompat.getColor(context, android.R.color.white))
layoutParams = LinearLayout.LayoutParams( typeface = android.graphics.Typeface.DEFAULT_BOLD
0,
ViewGroup.LayoutParams.WRAP_CONTENT,
1f
)
} }
row.addView(labelView) addView(labelView)
row.addView(valueView) addView(valueView)
expandedContainer.addView(row)
} }
}
private fun addTwoColumnRow( private fun createCheckboxItemForContainer(label: String, checked: Boolean, context: Context): LinearLayout
leftLabel: String, leftValue: String, {
rightLabel: String, rightValue: String, return LinearLayout(context).apply {
context: Context orientation = LinearLayout.HORIZONTAL
) gravity = android.view.Gravity.CENTER_VERTICAL
{
val row = LinearLayout(context).apply {
orientation = LinearLayout.HORIZONTAL
setPadding(0, dpToPx(context, 2), 0, dpToPx(context, 2))
}
// Left column // Checkbox symbol
val leftColumn = createColumnItem(leftLabel, leftValue, context) val checkboxView = TextView(context).apply {
leftColumn.layoutParams = LinearLayout.LayoutParams( text = if (checked) "" else ""
0, setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f)
ViewGroup.LayoutParams.WRAP_CONTENT, setTextColor(
1f 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)
}
}
// Right column // Label
val rightColumn = createColumnItem(rightLabel, rightValue, context) val labelView = TextView(context).apply {
rightColumn.layoutParams = LinearLayout.LayoutParams( text = label
0, setTextSize(TypedValue.COMPLEX_UNIT_SP, 11f)
ViewGroup.LayoutParams.WRAP_CONTENT, setTextColor(ContextCompat.getColor(context, android.R.color.white))
1f
).apply {
setMargins(dpToPx(context, 8), 0, 0, 0)
} }
row.addView(leftColumn) addView(checkboxView)
row.addView(rightColumn) addView(labelView)
expandedContainer.addView(row)
} }
}
private fun addThreeColumnRow( inner class HistoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
leftLabel: String, leftValue: String, {
middleLabel: String, middleValue: String, private val cardContainer = itemView as LinearLayout
rightLabel: String, rightValue: String, private val collapsedContent = itemView.findViewWithTag<LinearLayout>("collapsed_content")
context: Context private var expandedContent: View? = null // Will be created when needed
)
// Collapsed content views
private val statusIcon = collapsedContent.findViewWithTag<ImageView>("status_icon")
private val titleText = collapsedContent.findViewWithTag<TextView>("title_text")
private val subtitleText = collapsedContent.findViewWithTag<TextView>("subtitle_text")
private val chevronIcon = collapsedContent.findViewWithTag<TextView>("chevron_icon")
fun bind(item: HistoryItem, position: Int)
{ {
val row = LinearLayout(context).apply { val result = item.result
orientation = LinearLayout.HORIZONTAL val context = itemView.context
setPadding(0, dpToPx(context, 2), 0, dpToPx(context, 2))
}
// Left column // Update collapsed content
val leftColumn = createColumnItem(leftLabel, leftValue, context) updateCollapsedContent(result, context)
leftColumn.layoutParams = LinearLayout.LayoutParams(
0,
ViewGroup.LayoutParams.WRAP_CONTENT,
1f
)
// Middle column // Handle expanded content
val middleColumn = createColumnItem(middleLabel, middleValue, context) if (item.isExpanded) {
middleColumn.layoutParams = LinearLayout.LayoutParams( ensureExpandedContent(result, context)
0, showExpandedContent()
ViewGroup.LayoutParams.WRAP_CONTENT, } else {
1f hideExpandedContent()
).apply {
setMargins(dpToPx(context, 4), 0, dpToPx(context, 4), 0)
} }
// Right column // Set click listeners
val rightColumn = createColumnItem(rightLabel, rightValue, context) collapsedContent.setOnClickListener {
rightColumn.layoutParams = LinearLayout.LayoutParams( onItemClick(result, position)
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 private fun ensureExpandedContent(result: DetectionResult, context: Context)
{ {
return LinearLayout(context).apply { // Remove existing expanded content if any
orientation = LinearLayout.VERTICAL expandedContent?.let { existing ->
gravity = android.view.Gravity.START if (existing.parent == cardContainer) {
cardContainer.removeView(existing)
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 { // Create new expanded content with actual data
text = value expandedContent = createPopulatedExpandedContent(context, result).apply {
setTextSize(TypedValue.COMPLEX_UNIT_SP, 11f) // Add delete button to the content
setTextColor(ContextCompat.getColor(context, android.R.color.white)) val scrollView = this as androidx.core.widget.NestedScrollView
typeface = android.graphics.Typeface.DEFAULT_BOLD val container = scrollView.findViewWithTag<LinearLayout>("expanded_container")
container?.let {
addDeleteButton(result, adapterPosition, context, it)
} }
addView(labelView)
addView(valueView)
} }
// Add to card container
expandedContent?.let { cardContainer.addView(it) }
} }
private fun addCheckboxRow( private fun updateCollapsedContent(result: DetectionResult, context: Context)
leftLabel: String, leftChecked: Boolean,
rightLabel: String, rightChecked: Boolean,
context: Context
)
{ {
val row = LinearLayout(context).apply { // Status icon
orientation = LinearLayout.HORIZONTAL statusIcon.setImageResource(
setPadding(0, dpToPx(context, 2), 0, dpToPx(context, 2)) if (result.success) android.R.drawable.ic_menu_myplaces
} else android.R.drawable.ic_dialog_alert
)
// Left checkbox statusIcon.setColorFilter(
val leftCheckbox = createCheckboxItem(leftLabel, leftChecked, context) ContextCompat.getColor(
leftCheckbox.layoutParams = LinearLayout.LayoutParams( context,
0, if (result.success) android.R.color.holo_green_light
ViewGroup.LayoutParams.WRAP_CONTENT, else android.R.color.holo_red_light
1f )
) )
// Right checkbox // Title (Pokemon name or status)
val rightCheckbox = createCheckboxItem(rightLabel, rightChecked, context) titleText.text = when {
rightCheckbox.layoutParams = LinearLayout.LayoutParams( result.success && result.pokemonInfo?.species != null -> {
0, result.pokemonInfo.species +
ViewGroup.LayoutParams.WRAP_CONTENT, (result.pokemonInfo.nationalDexNumber?.let { " (#$it)" } ?: "")
1f }
).apply { result.success -> "Pokemon Detected"
setMargins(dpToPx(context, 8), 0, 0, 0) else -> "Detection Failed"
} }
row.addView(leftCheckbox) // Subtitle (timestamp and processing time)
row.addView(rightCheckbox) val formatter = DateTimeFormatter.ofPattern("MMM dd, HH:mm")
expandedContainer.addView(row) 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
chevronIcon.rotation = rotation
} }
private fun addMixedRow(
leftLabel: String, leftChecked: Boolean,
rightLabel: String, rightValue: String,
context: Context
)
{
val row = LinearLayout(context).apply {
orientation = LinearLayout.HORIZONTAL
setPadding(0, dpToPx(context, 2), 0, dpToPx(context, 2))
}
// Left checkbox private fun showExpandedContent()
val leftCheckbox = createCheckboxItem(leftLabel, leftChecked, context) {
leftCheckbox.layoutParams = LinearLayout.LayoutParams( expandedContent?.visibility = View.VISIBLE
0,
ViewGroup.LayoutParams.WRAP_CONTENT,
1f
)
// Right text item // Animate chevron rotation
val rightItem = createColumnItem(rightLabel, rightValue, context) ObjectAnimator.ofFloat(chevronIcon, "rotation", 0f, 180f).apply {
rightItem.layoutParams = LinearLayout.LayoutParams( duration = 200L
0, start()
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 private fun hideExpandedContent()
{ {
return LinearLayout(context).apply { expandedContent?.visibility = View.GONE
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) // Animate chevron rotation
addView(labelView) 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 { val deleteButton = Button(context).apply {
text = "Delete" text = "Delete"
@ -708,29 +741,7 @@ class HistoryAdapter(
} }
} }
expandedContainer.addView(deleteButton) container.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()
}
} }
} }
} }
Loading…
Cancel
Save