Browse Source

feat: improve drawer behavior with scrolling and persistent minimal state

- Added ScrollView to expanded content for viewing all Pokemon data
  * Wrapped content in scrollable container with proper padding
  * Updated expand/collapse animations to work with ScrollView
  * All Pokemon Home data now accessible through scrolling

- Prevented drawer from fully disappearing on swipe down
  * Modified touch handling to only collapse when expanded
  * Drawer now stays in minimal collapsed state instead of hiding
  * Only the dismiss button can fully hide the drawer
  * Improved user experience with persistent results access

- Enhanced gesture behavior:
  * Swipe up: expands drawer to show full scrollable content
  * Swipe down when expanded: collapses to minimal state
  * Tap: toggles between expanded and collapsed states
  * Only dismiss button fully hides drawer

🤖 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
65fadd2060
  1. 86
      app/src/main/java/com/quillstudios/pokegoalshelper/ui/ResultsBottomDrawer.kt

86
app/src/main/java/com/quillstudios/pokegoalshelper/ui/ResultsBottomDrawer.kt

@ -278,59 +278,61 @@ class ResultsBottomDrawer(private val context: Context)
}
}
private fun createExpandedContent(result: DetectionResult): LinearLayout
private fun createExpandedContent(result: DetectionResult): ScrollView
{
return LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
return ScrollView(context).apply {
visibility = View.GONE // Initially hidden
tag = "expanded_content" // For easy finding
// Add some spacing
setPadding(0, dpToPx(8), 0, 0)
// Create the scrollable content container
val contentContainer = LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
setPadding(0, dpToPx(8), 0, dpToPx(16)) // Add bottom padding for scroll
}
if (result.success && result.pokemonInfo != null)
{
val pokemonInfo = result.pokemonInfo
// Basic Pokemon Info Section
addView(createSectionHeader("Pokemon Info"))
addView(createTwoColumnRow(
contentContainer.addView(createSectionHeader("Pokemon Info"))
contentContainer.addView(createTwoColumnRow(
leftLabel = "Species", leftValue = pokemonInfo.species ?: "Unknown",
rightLabel = "Dex #", rightValue = pokemonInfo.nationalDexNumber?.let { "#$it" } ?: "N/A"
))
addView(createTwoColumnRow(
contentContainer.addView(createTwoColumnRow(
leftLabel = "Nickname", leftValue = pokemonInfo.nickname ?: "None",
rightLabel = "Gender", rightValue = pokemonInfo.gender ?: "Unknown"
))
addView(createTwoColumnRow(
contentContainer.addView(createTwoColumnRow(
leftLabel = "Level", leftValue = pokemonInfo.level?.toString() ?: "N/A",
rightLabel = "Nature", rightValue = pokemonInfo.nature ?: "Unknown"
))
// Types Section
addView(createSectionHeader("Types"))
contentContainer.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(
contentContainer.addView(createTwoColumnRow(
leftLabel = "Type", leftValue = typeDisplay,
rightLabel = "Tera", rightValue = pokemonInfo.teraType ?: "N/A"
))
// Stats Section (if available)
pokemonInfo.stats?.let { stats ->
addView(createSectionHeader("Base Stats"))
addView(createThreeColumnRow(
contentContainer.addView(createSectionHeader("Base Stats"))
contentContainer.addView(createThreeColumnRow(
leftLabel = "HP", leftValue = stats.hp?.toString() ?: "?",
middleLabel = "ATK", middleValue = stats.attack?.toString() ?: "?",
rightLabel = "DEF", rightValue = stats.defense?.toString() ?: "?"
))
addView(createThreeColumnRow(
contentContainer.addView(createThreeColumnRow(
leftLabel = "SP.ATK", leftValue = stats.spAttack?.toString() ?: "?",
middleLabel = "SP.DEF", middleValue = stats.spDefense?.toString() ?: "?",
rightLabel = "SPEED", rightValue = stats.speed?.toString() ?: "?"
@ -338,25 +340,25 @@ class ResultsBottomDrawer(private val context: Context)
}
// Special Properties Section
addView(createSectionHeader("Properties"))
addView(createCheckboxRow(
contentContainer.addView(createSectionHeader("Properties"))
contentContainer.addView(createCheckboxRow(
leftLabel = "Shiny", leftChecked = pokemonInfo.isShiny,
rightLabel = "Alpha", rightChecked = pokemonInfo.isAlpha
))
addView(createMixedRow(
contentContainer.addView(createMixedRow(
leftLabel = "Favorited", leftChecked = pokemonInfo.isFavorited,
rightLabel = "Pokeball", rightValue = pokemonInfo.pokeballType ?: "Unknown"
))
// Game Origin Section
addView(createSectionHeader("Origin"))
addView(createTwoColumnRow(
contentContainer.addView(createSectionHeader("Origin"))
contentContainer.addView(createTwoColumnRow(
leftLabel = "Game", leftValue = pokemonInfo.gameSource ?: "Unknown",
rightLabel = "Language", rightValue = pokemonInfo.language ?: "Unknown"
))
pokemonInfo.originalTrainerName?.let { trainerName ->
addView(createTwoColumnRow(
contentContainer.addView(createTwoColumnRow(
leftLabel = "OT Name", leftValue = trainerName,
rightLabel = "OT ID", rightValue = pokemonInfo.originalTrainerId ?: "Unknown"
))
@ -364,45 +366,48 @@ class ResultsBottomDrawer(private val context: Context)
// Ability & Moves
pokemonInfo.ability?.let { ability ->
addView(createSectionHeader("Ability & Moves"))
addView(createDetailRow("Ability", ability))
contentContainer.addView(createSectionHeader("Ability & Moves"))
contentContainer.addView(createDetailRow("Ability", ability))
}
if (pokemonInfo.moves.isNotEmpty()) {
addView(createDetailRow("Moves", pokemonInfo.moves.take(4).joinToString(", ")))
contentContainer.addView(createDetailRow("Moves", pokemonInfo.moves.take(4).joinToString(", ")))
}
// Additional Data
if (pokemonInfo.stamps.isNotEmpty() || pokemonInfo.labels.isNotEmpty() || pokemonInfo.marks.isNotEmpty()) {
addView(createSectionHeader("Additional"))
contentContainer.addView(createSectionHeader("Additional"))
if (pokemonInfo.stamps.isNotEmpty()) {
addView(createDetailRow("Stamps", pokemonInfo.stamps.joinToString(", ")))
contentContainer.addView(createDetailRow("Stamps", pokemonInfo.stamps.joinToString(", ")))
}
if (pokemonInfo.labels.isNotEmpty()) {
addView(createDetailRow("Labels", pokemonInfo.labels.joinToString(", ")))
contentContainer.addView(createDetailRow("Labels", pokemonInfo.labels.joinToString(", ")))
}
if (pokemonInfo.marks.isNotEmpty()) {
addView(createDetailRow("Marks", pokemonInfo.marks.joinToString(", ")))
contentContainer.addView(createDetailRow("Marks", pokemonInfo.marks.joinToString(", ")))
}
}
}
else
{
// Show error details
addView(createSectionHeader("Detection Failed"))
addView(createDetailRow("Status", if (result.success) "No Pokemon detected" else "Detection failed"))
contentContainer.addView(createSectionHeader("Detection Failed"))
contentContainer.addView(createDetailRow("Status", if (result.success) "No Pokemon detected" else "Detection failed"))
result.errorMessage?.let { error ->
addView(createDetailRow("Error", error))
contentContainer.addView(createDetailRow("Error", error))
}
}
// Technical Info Section
addView(createSectionHeader("Technical Info"))
addView(createTwoColumnRow(
contentContainer.addView(createSectionHeader("Technical Info"))
contentContainer.addView(createTwoColumnRow(
leftLabel = "Processing", leftValue = "${result.processingTimeMs}ms",
rightLabel = "Detected", rightValue = "${result.detections.size} items"
))
addView(createDetailRow("Timestamp", result.timestamp.format(DateTimeFormatter.ofPattern("HH:mm:ss"))))
contentContainer.addView(createDetailRow("Timestamp", result.timestamp.format(DateTimeFormatter.ofPattern("HH:mm:ss"))))
// Add the content container to the ScrollView
addView(contentContainer)
}
}
@ -755,10 +760,15 @@ class ResultsBottomDrawer(private val context: Context)
{
val deltaY = event.rawY - initialTouchY
if (deltaY > SWIPE_THRESHOLD)
if (deltaY > SWIPE_THRESHOLD && isExpanded)
{
// Dismiss if swiped down enough
hide()
// Only collapse to minimal state if expanded, never fully dismiss
collapseDrawer()
ObjectAnimator.ofFloat(view, "translationY", view.translationY, initialTranslationY).apply {
duration = 200L
interpolator = AccelerateDecelerateInterpolator()
start()
}
}
else if (deltaY < EXPAND_THRESHOLD && !isExpanded)
{
@ -809,7 +819,7 @@ class ResultsBottomDrawer(private val context: Context)
isExpanded = true
// Show expanded content
drawerContainer?.findViewWithTag<LinearLayout>("expanded_content")?.let { expandedContent ->
drawerContainer?.findViewWithTag<ScrollView>("expanded_content")?.let { expandedContent ->
expandedContent.visibility = View.VISIBLE
expandedContent.alpha = 0f
@ -837,7 +847,7 @@ class ResultsBottomDrawer(private val context: Context)
isExpanded = false
// Hide expanded content
drawerContainer?.findViewWithTag<LinearLayout>("expanded_content")?.let { expandedContent ->
drawerContainer?.findViewWithTag<ScrollView>("expanded_content")?.let { expandedContent ->
ObjectAnimator.ofFloat(expandedContent, "alpha", 1f, 0f).apply {
duration = SLIDE_ANIMATION_DURATION
addListener(object : AnimatorListenerAdapter() {

Loading…
Cancel
Save