Browse Source

feat: enhance bottom drawer with multi-column layout and checkbox functionality

- Redesigned expanded content with organized sections:
  * Pokemon Info (Name, Dex #, Gender, Form)
  * Combat Stats (CP, HP, Level, IV%)
  * Individual Stats (ATK/DEF/STA if available)
  * Special Properties (Shiny/Alpha checkboxes)
  * Technical Info (Processing time, Detection count, Timestamp)
- Added multi-column layout helper methods for better space utilization
- Implemented checkbox indicators using Unicode symbols (☐/☑)
- Enhanced visual hierarchy with blue section headers
- Improved data organization and readability

🤖 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
013593cdca
  1. 3
      .vscode/settings.json
  2. 281
      app/src/main/java/com/quillstudios/pokegoalshelper/ui/ResultsBottomDrawer.kt
  3. 112
      temp/HumanInTheLoop.py
  4. 8402
      temp/Pokemon_Home_Training.ipynb

3
.vscode/settings.json

@ -0,0 +1,3 @@
{
"cmake.ignoreCMakeListsMissing": true
}

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

@ -293,63 +293,66 @@ class ResultsBottomDrawer(private val context: Context)
{
val pokemonInfo = result.pokemonInfo
// Pokemon name section
pokemonInfo.name?.let { name ->
addView(createDetailRow("Name", name))
}
// Pokemon Basic Info Section
addView(createSectionHeader("Pokemon Info"))
addView(createTwoColumnRow(
leftLabel = "Name", leftValue = pokemonInfo.name ?: "Unknown",
rightLabel = "Dex #", rightValue = pokemonInfo.nationalDexNumber?.let { "#$it" } ?: "N/A"
))
// National Dex Number
pokemonInfo.nationalDexNumber?.let { dexNum ->
addView(createDetailRow("Dex #", "#$dexNum"))
}
addView(createTwoColumnRow(
leftLabel = "Gender", leftValue = pokemonInfo.gender ?: "Unknown",
rightLabel = "Form", rightValue = pokemonInfo.form ?: "Normal"
))
// Stats section
pokemonInfo.cp?.let { cp ->
addView(createDetailRow("CP", cp.toString()))
}
pokemonInfo.level?.let { level ->
addView(createDetailRow("Level", String.format("%.1f", level)))
}
// 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"
))
pokemonInfo.hp?.let { hp ->
addView(createDetailRow("HP", hp.toString()))
}
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"
))
// IV Stats
// Individual Stats Section (if available)
pokemonInfo.stats?.let { stats ->
stats.perfectIV?.let { iv ->
addView(createDetailRow("IV %", "${String.format("%.1f", iv)}%"))
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() ?: "?"
))
}
// Individual stats
stats.attack?.let { addView(createDetailRow("Attack", it.toString())) }
stats.defense?.let { addView(createDetailRow("Defense", it.toString())) }
stats.stamina?.let { addView(createDetailRow("Stamina", it.toString())) }
}
// Other info
pokemonInfo.gender?.let { gender ->
addView(createDetailRow("Gender", gender))
}
pokemonInfo.form?.let { form ->
addView(createDetailRow("Form", form))
}
// 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
))
}
else
{
// Show error details
addView(createSectionHeader("Detection Failed"))
addView(createDetailRow("Status", if (result.success) "No Pokemon detected" else "Detection failed"))
result.errorMessage?.let { error ->
addView(createDetailRow("Error", error))
}
}
// Always show technical info
addView(createDetailRow("Processing Time", "${result.processingTimeMs}ms"))
// Technical Info Section
addView(createSectionHeader("Technical Info"))
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"))))
addView(createDetailRow("Detections Found", result.detections.size.toString()))
}
}
@ -393,6 +396,208 @@ class ResultsBottomDrawer(private val context: Context)
}
}
private fun createSectionHeader(title: String): TextView
{
return TextView(context).apply {
text = title
setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f)
setTextColor(ContextCompat.getColor(context, android.R.color.holo_blue_light))
typeface = android.graphics.Typeface.DEFAULT_BOLD
layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
).apply {
setMargins(0, dpToPx(8), 0, dpToPx(4))
}
}
}
private fun createTwoColumnRow(
leftLabel: String, leftValue: String,
rightLabel: String, rightValue: String
): LinearLayout
{
return LinearLayout(context).apply {
orientation = LinearLayout.HORIZONTAL
gravity = Gravity.CENTER_VERTICAL
// Left column
val leftColumn = createColumnItem(leftLabel, leftValue)
leftColumn.layoutParams = LinearLayout.LayoutParams(
0,
ViewGroup.LayoutParams.WRAP_CONTENT,
1f
)
// Right column
val rightColumn = createColumnItem(rightLabel, rightValue)
rightColumn.layoutParams = LinearLayout.LayoutParams(
0,
ViewGroup.LayoutParams.WRAP_CONTENT,
1f
).apply {
setMargins(dpToPx(8), 0, 0, 0)
}
addView(leftColumn)
addView(rightColumn)
layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
).apply {
setMargins(0, dpToPx(2), 0, dpToPx(2))
}
}
}
private fun createThreeColumnRow(
leftLabel: String, leftValue: String,
middleLabel: String, middleValue: String,
rightLabel: String, rightValue: String
): LinearLayout
{
return LinearLayout(context).apply {
orientation = LinearLayout.HORIZONTAL
gravity = Gravity.CENTER_VERTICAL
// Left column
val leftColumn = createColumnItem(leftLabel, leftValue)
leftColumn.layoutParams = LinearLayout.LayoutParams(
0,
ViewGroup.LayoutParams.WRAP_CONTENT,
1f
)
// Middle column
val middleColumn = createColumnItem(middleLabel, middleValue)
middleColumn.layoutParams = LinearLayout.LayoutParams(
0,
ViewGroup.LayoutParams.WRAP_CONTENT,
1f
).apply {
setMargins(dpToPx(4), 0, dpToPx(4), 0)
}
// Right column
val rightColumn = createColumnItem(rightLabel, rightValue)
rightColumn.layoutParams = LinearLayout.LayoutParams(
0,
ViewGroup.LayoutParams.WRAP_CONTENT,
1f
)
addView(leftColumn)
addView(middleColumn)
addView(rightColumn)
layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
).apply {
setMargins(0, dpToPx(2), 0, dpToPx(2))
}
}
}
private fun createColumnItem(label: String, value: String): LinearLayout
{
return LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
gravity = 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 createCheckboxRow(
leftLabel: String, leftChecked: Boolean,
rightLabel: String, rightChecked: Boolean
): 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 checkbox
val rightCheckbox = createCheckboxItem(rightLabel, rightChecked)
rightCheckbox.layoutParams = LinearLayout.LayoutParams(
0,
ViewGroup.LayoutParams.WRAP_CONTENT,
1f
).apply {
setMargins(dpToPx(8), 0, 0, 0)
}
addView(leftCheckbox)
addView(rightCheckbox)
layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
).apply {
setMargins(0, dpToPx(2), 0, dpToPx(2))
}
}
}
private fun createCheckboxItem(label: String, checked: Boolean): LinearLayout
{
return LinearLayout(context).apply {
orientation = LinearLayout.HORIZONTAL
gravity = 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(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)
}
}
private fun createDrawerBackground(): GradientDrawable
{
return GradientDrawable().apply {

112
temp/HumanInTheLoop.py

@ -0,0 +1,112 @@
from ultralytics import YOLO
import os
import shutil
from PIL import Image
# --- Configuration ---
# Load your trained model
model = YOLO('./best.pt') # Adjust 'train8' if needed
# Directory with new, unannotated images (your input)
unlabeled_image_dir = './untrained_images'
# Base directory for YOLOv8's output. YOLOv8 will create 'predictX' folders inside this.
# Example: /TempReview/predict, /TempReview/predict1, etc.
yolov8_project_dir = './.yolo_yemp' # This is where your 'predictX' folders are generated
# The final, flat directory where images and labels will be moved for LabelImg
flat_output_dir = './for_labelimg_review'
# --- Ensure input directory exists ---
os.makedirs(unlabeled_image_dir, exist_ok=True) # Make sure this exists if you haven't uploaded images yet
# Get all image files from the unlabeled directory
image_files = [f for f in os.listdir(unlabeled_image_dir) if f.lower().endswith(('.jpg', '.jpeg', '.png'))]
if not image_files:
print(f"No image files found in '{unlabeled_image_dir}'. Please upload images there.")
else:
print(f"--- Step 1: Running YOLOv8 Inference on new images ---")
print(f"Running inference on {len(image_files)} new images...")
# Run inference on all images in the directory
# YOLOv8 will automatically create a new 'predictX' folder (e.g., predict, predict1, predict2)
# inside the yolov8_project_dir for each unique run.
results_list = model(unlabeled_image_dir,
#save=True, # Save images with plotted boxes
save_txt=True, # Save the YOLO .txt label files
conf=0.6, # Confidence threshold for predictions (adjust as needed)
project=yolov8_project_dir # This is your 'TempReview'
)
# Find the most recently created 'predictX' folder
# This assumes the latest run will be the one with the highest number
# or the one most recently modified.
predict_dirs = [d for d in os.listdir(yolov8_project_dir) if d.startswith('predict') and os.path.isdir(os.path.join(yolov8_project_dir, d))]
if not predict_dirs:
print(f"Error: No 'predictX' folders found in '{yolov8_project_dir}'. Inference might have failed.")
else:
# Sort by creation time (most recent first) or by name (highest number)
# Sorting by name (predict, predict1, predict10, predict2...) doesn't always work numerically.
# Sorting by modification time is safer.
latest_predict_dir_name = max(predict_dirs, key=lambda d: os.path.getmtime(os.path.join(yolov8_project_dir, d)))
yolov8_run_path = os.path.join(yolov8_project_dir, latest_predict_dir_name)
yolov8_images_path = yolov8_run_path
yolov8_labels_path = os.path.join(yolov8_run_path, 'labels')
print(f"YOLOv8 results saved to: '{yolov8_run_path}'")
print(f"\n--- Step 2: Flattening output structure for LabelImg ---")
os.makedirs(flat_output_dir, exist_ok=True)
# Move images
if os.path.exists(yolov8_images_path):
for img_file in os.listdir(yolov8_images_path):
# Only move files that are original image files (e.g., .jpg, .png)
if img_file.lower().endswith(('.jpg', '.jpeg', '.png')):
shutil.move(os.path.join(yolov8_images_path, img_file),
os.path.join(flat_output_dir, img_file))
print(f"Moved images to '{flat_output_dir}'")
else:
print(f"Warning: No images found at '{yolov8_images_path}'.")
# Process and move labels
if os.path.exists(yolov8_labels_path):
for label_file in os.listdir(yolov8_labels_path):
if label_file.lower().endswith('.txt'):
label_path_src = os.path.join(yolov8_labels_path, label_file)
# Read lines, parse class_id, and sort
with open(label_path_src, 'r') as f:
lines = f.readlines()
# Sort lines based on the first element (class_id) as an integer
# Handle potential errors if a line is malformed, though unlikely from YOLO.
try:
sorted_lines = sorted(lines, key=lambda line: int(line.strip().split(' ')[0]))
except ValueError as e:
print(f"Warning: Could not sort lines in {label_file} due to format error: {e}. Skipping sort for this file.")
sorted_lines = lines # Fallback to original order
# Write sorted lines to the destination file
label_path_dest = os.path.join(flat_output_dir, label_file)
with open(label_path_dest, 'w') as f:
f.writelines(sorted_lines)
# Remove the original label file after processing (optional, but keeps source clean)
os.remove(label_path_src)
print(f"Moved and sorted labels to '{flat_output_dir}'")
else:
print(f"Warning: No labels found at '{yolov8_labels_path}'.")
# Clean up the intermediate YOLOv8 output directory (optional, but recommended for Colab space)
# Be careful with shutil.rmtree - it deletes recursively!
if os.path.exists(yolov8_run_path):
print(f"Cleaning up temporary YOLOv8 output: '{yolov8_run_path}'")
shutil.rmtree(yolov8_run_path)
print(f"\n--- Process Complete! ---")
print(f"Your images and predicted .txt labels are now in a flat structure in: '{flat_output_dir}'")
print("You can now open LabelImg and point it to this directory to begin review and correction.")

8402
temp/Pokemon_Home_Training.ipynb

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save