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

Loading…
Cancel
Save