Tablet fixes#89
Conversation
Provides LocalWindowSizeClass to all child composables for responsive layouts. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Uses 80% of screen height, capped between 400-800dp. Closes #87 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Sheets now use 80% of screen height, capped between 300-600dp. Closes #72 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Dialog scales from 280dp (phone) to 400dp (tablet). Applies to both AutoStopOverlay and AutoStartOverlay components. Closes #74 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Scales from 320dp (phone) to 480dp (expanded tablet). Partial fix for #67 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Partial fix for #67
Partial fix for #67 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Partial fix for #67
Partial fix for #67 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add ResponsiveDimensions import for tablet-aware sizing - Make Y-axis column width responsive (50dp compact, 65dp medium, 80dp expanded) - Adjust X-axis label padding to match responsive column width Closes #85, partial fix for #67 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Cards now have max-width on tablets to prevent over-stretching. Closes #68 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- FAB spacer scales with screen size (180dp/210dp/240dp) - Hero card image scales appropriately (200dp/240dp/280dp) - ActiveCycleHero height adapts to tablets (180dp/210dp/240dp) Closes #69 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Metric display scales with screen size using ResponsiveDimensions - Button heights adapt to tablets via WindowSizeClass Closes #73, partial #86 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Grid columns: 3 (phone) -> 4 (medium) -> 6 (expanded) Closes #80 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add imports for LocalWindowSizeClass, WindowWidthSizeClass, ResponsiveDimensions - Replace fixed .height(64.dp) with responsive buttonHeight (64/72/80dp) - Replace fixed .size(200.dp) on countdown overlay with ResponsiveDimensions.componentSize() Closes #86 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add LocalWindowSizeClass and WindowWidthSizeClass imports - Make tab indicator height responsive (8dp/10dp/12dp for compact/medium/expanded) - Make FAB icon size responsive (28dp/32dp/36dp for compact/medium/expanded) Closes #70 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add responsive width calculation to LiveMetricsCard weight labels for cable position displays. Labels now scale from 50dp (compact) to 80dp (expanded) based on window size class. Closes #84 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Closes #71 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Closes #81 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…lets Use WindowSizeClass to set horizontal padding - 24dp for compact, 36dp for medium, and 48dp for expanded displays. Issue #77. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use BoxWithConstraints to calculate preview height dynamically based on available space (40% of maxHeight, constrained between 150-400dp). Issue #79. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Convert fixed .width(60.dp), .width(100.dp), and .width(120.dp) to flexible weight-based layouts for better tablet and responsive display. Fixes #76 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ponents Add responsiveDimension() helper function that scales dimensions based on WindowWidthSizeClass: - Expanded: 1.4x base size - Medium: 1.2x base size - Compact: base size (original) Apply to WorkoutHistoryCardSkeleton, PersonalRecordCardSkeleton, and RoutineCardSkeleton for consistent tablet scaling of skeleton loaders. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
@copilot Review please |
|
@DasBluEyedDevil I've opened a new pull request, #90, to work on those changes. Once the pull request is ready, I'll request review from you. |
There was a problem hiding this comment.
Pull request overview
This PR implements comprehensive tablet support improvements across the application, introduces a responsive design system based on Material 3 window size classes, fixes database migration issues, and enhances CSV export functionality with proper thread handling and error logging.
Key Changes
- Introduces a new
WindowSizeClassutility system with responsive dimension helpers for adaptive UI layouts across phones, tablets, and larger screens - Implements tablet-responsive sizing throughout the app for buttons, charts, cards, dialogs, and components using breakpoints at 600dp and 840dp
- Adds resilient database migration handling to prevent crashes from partial migrations and fixes startup issues for users upgrading to the Training Cycles feature
Reviewed changes
Copilot reviewed 31 out of 32 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| WindowSizeClass.kt | New responsive design system with Material 3 breakpoints and dimension helpers |
| CsvExporter.ios.kt | Adds main thread dispatch for UIKit operations to prevent crashes |
| 6.sqm | Database migration for Training Cycles feature tables and indexes |
| DriverFactory.android.kt | Implements resilient migration handling with duplicate column/table error recovery |
| WorkoutTabAlt.kt, WorkoutTab.kt, WorkoutHud.kt | Responsive button heights and component sizing based on screen size |
| InsightsTab.kt | Adds ResponsiveCardWrapper to prevent card over-stretching on tablets |
| HomeScreen.kt | Responsive spacing and hero card dimensions for tablets |
| ExerciseEditDialog.kt | Improved layout weights for better tablet display |
| EnhancedMainScreen.kt | Integrates WindowSizeClass provider at root level |
| Various chart components | Responsive chart heights using ResponsiveDimensions helpers |
| ShimmerEffect.kt | Responsive skeleton loading card dimensions |
| Various other components | Responsive dialog widths, panel widths, grid columns, and spacing |
| CsvExporter.android.kt | Enhanced error logging for debugging |
| file_paths.xml | FileProvider configuration for CSV sharing |
| AndroidManifest.xml | Adds FileProvider declaration |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Extension for Dp multiplication | ||
| private operator fun Dp.times(factor: Float): Dp = (this.value * factor).dp | ||
|
|
There was a problem hiding this comment.
The Dp multiplication extension operator is duplicated. This same extension is already defined in WindowSizeClass.kt at line 134. Consider consolidating this into a shared utility file or removing one of the duplicates to follow the DRY principle.
| // Extension for Dp multiplication | |
| private operator fun Dp.times(factor: Float): Dp = (this.value * factor).dp |
| private fun getMigrationStatements(version: Int): List<String> { | ||
| return when (version) { | ||
| 5 -> listOf( | ||
| "ALTER TABLE WorkoutSession ADD COLUMN peakForceConcentricA REAL", | ||
| "ALTER TABLE WorkoutSession ADD COLUMN peakForceConcentricB REAL", | ||
| "ALTER TABLE WorkoutSession ADD COLUMN peakForceEccentricA REAL", | ||
| "ALTER TABLE WorkoutSession ADD COLUMN peakForceEccentricB REAL", | ||
| "ALTER TABLE WorkoutSession ADD COLUMN avgForceConcentricA REAL", | ||
| "ALTER TABLE WorkoutSession ADD COLUMN avgForceConcentricB REAL", | ||
| "ALTER TABLE WorkoutSession ADD COLUMN avgForceEccentricA REAL", | ||
| "ALTER TABLE WorkoutSession ADD COLUMN avgForceEccentricB REAL", | ||
| "ALTER TABLE WorkoutSession ADD COLUMN heaviestLiftKg REAL", | ||
| "ALTER TABLE WorkoutSession ADD COLUMN totalVolumeKg REAL", | ||
| "ALTER TABLE WorkoutSession ADD COLUMN estimatedCalories REAL", | ||
| "ALTER TABLE WorkoutSession ADD COLUMN warmupAvgWeightKg REAL", | ||
| "ALTER TABLE WorkoutSession ADD COLUMN workingAvgWeightKg REAL", | ||
| "ALTER TABLE WorkoutSession ADD COLUMN burnoutAvgWeightKg REAL", | ||
| "ALTER TABLE WorkoutSession ADD COLUMN peakWeightKg REAL", | ||
| "ALTER TABLE WorkoutSession ADD COLUMN rpe INTEGER" | ||
| ) | ||
| 6 -> listOf( | ||
| """CREATE TABLE IF NOT EXISTS TrainingCycle ( | ||
| id TEXT PRIMARY KEY NOT NULL, | ||
| name TEXT NOT NULL, | ||
| description TEXT, | ||
| created_at INTEGER NOT NULL, | ||
| is_active INTEGER NOT NULL DEFAULT 0 | ||
| )""", | ||
| """CREATE TABLE IF NOT EXISTS CycleDay ( | ||
| id TEXT PRIMARY KEY NOT NULL, | ||
| cycle_id TEXT NOT NULL, | ||
| day_number INTEGER NOT NULL, | ||
| name TEXT, | ||
| routine_id TEXT, | ||
| is_rest_day INTEGER NOT NULL DEFAULT 0, | ||
| echo_level TEXT, | ||
| eccentric_load_percent INTEGER, | ||
| weight_progression_percent REAL, | ||
| rep_modifier INTEGER, | ||
| rest_time_override_seconds INTEGER, | ||
| FOREIGN KEY (cycle_id) REFERENCES TrainingCycle(id) ON DELETE CASCADE, | ||
| FOREIGN KEY (routine_id) REFERENCES Routine(id) ON DELETE SET NULL | ||
| )""", | ||
| """CREATE TABLE IF NOT EXISTS CycleProgress ( | ||
| id TEXT PRIMARY KEY NOT NULL, | ||
| cycle_id TEXT NOT NULL UNIQUE, | ||
| current_day_number INTEGER NOT NULL DEFAULT 1, | ||
| last_completed_date INTEGER, | ||
| cycle_start_date INTEGER NOT NULL, | ||
| last_advanced_at INTEGER, | ||
| completed_days TEXT, | ||
| missed_days TEXT, | ||
| rotation_count INTEGER NOT NULL DEFAULT 0, | ||
| FOREIGN KEY (cycle_id) REFERENCES TrainingCycle(id) ON DELETE CASCADE | ||
| )""", | ||
| """CREATE TABLE IF NOT EXISTS CycleProgression ( | ||
| cycle_id TEXT PRIMARY KEY NOT NULL, | ||
| frequency_cycles INTEGER NOT NULL DEFAULT 2, | ||
| weight_increase_percent REAL, | ||
| echo_level_increase INTEGER NOT NULL DEFAULT 0, | ||
| eccentric_load_increase_percent INTEGER, | ||
| FOREIGN KEY (cycle_id) REFERENCES TrainingCycle(id) ON DELETE CASCADE | ||
| )""", | ||
| """CREATE TABLE IF NOT EXISTS PlannedSet ( | ||
| id TEXT PRIMARY KEY NOT NULL, | ||
| routine_exercise_id TEXT NOT NULL, | ||
| set_number INTEGER NOT NULL, | ||
| set_type TEXT NOT NULL DEFAULT 'STANDARD', | ||
| target_reps INTEGER, | ||
| target_weight_kg REAL, | ||
| target_rpe INTEGER, | ||
| rest_seconds INTEGER, | ||
| FOREIGN KEY (routine_exercise_id) REFERENCES RoutineExercise(id) ON DELETE CASCADE | ||
| )""", | ||
| """CREATE TABLE IF NOT EXISTS CompletedSet ( | ||
| id TEXT PRIMARY KEY NOT NULL, | ||
| session_id TEXT NOT NULL, | ||
| planned_set_id TEXT, | ||
| set_number INTEGER NOT NULL, | ||
| set_type TEXT NOT NULL DEFAULT 'STANDARD', | ||
| actual_reps INTEGER NOT NULL, | ||
| actual_weight_kg REAL NOT NULL, | ||
| logged_rpe INTEGER, | ||
| is_pr INTEGER NOT NULL DEFAULT 0, | ||
| completed_at INTEGER NOT NULL, | ||
| FOREIGN KEY (session_id) REFERENCES WorkoutSession(id) ON DELETE CASCADE, | ||
| FOREIGN KEY (planned_set_id) REFERENCES PlannedSet(id) ON DELETE SET NULL | ||
| )""", | ||
| """CREATE TABLE IF NOT EXISTS ProgressionEvent ( | ||
| id TEXT PRIMARY KEY NOT NULL, | ||
| exercise_id TEXT NOT NULL, | ||
| suggested_weight_kg REAL NOT NULL, | ||
| previous_weight_kg REAL NOT NULL, | ||
| reason TEXT NOT NULL, | ||
| user_response TEXT, | ||
| actual_weight_kg REAL, | ||
| timestamp INTEGER NOT NULL, | ||
| FOREIGN KEY (exercise_id) REFERENCES Exercise(id) ON DELETE CASCADE | ||
| )""", | ||
| "CREATE INDEX IF NOT EXISTS idx_cycle_day_cycle ON CycleDay(cycle_id)", | ||
| "CREATE INDEX IF NOT EXISTS idx_cycle_progress_cycle ON CycleProgress(cycle_id)", | ||
| "CREATE INDEX IF NOT EXISTS idx_planned_set_exercise ON PlannedSet(routine_exercise_id)", | ||
| "CREATE INDEX IF NOT EXISTS idx_completed_set_session ON CompletedSet(session_id)", | ||
| "CREATE INDEX IF NOT EXISTS idx_progression_event_exercise ON ProgressionEvent(exercise_id)" | ||
| ) | ||
| else -> emptyList() | ||
| } | ||
| } |
There was a problem hiding this comment.
The hardcoded SQL migration statements in getMigrationStatements() duplicate the migration definitions from the .sqm files. This creates a maintenance burden - if migrations are updated, both locations must be changed. Consider reading the .sqm files directly or relying solely on SQLDelight's built-in migration mechanism with better error handling.
| msg.contains("already exists") || | ||
| msg.contains("table .* already exists".toRegex())) { |
There was a problem hiding this comment.
The regex pattern "table .* already exists" is created on every SQL statement execution inside a loop. For better performance, compile this regex once outside the loop or use a simple string contains check instead.
| msg.contains("already exists") || | |
| msg.contains("table .* already exists".toRegex())) { | |
| msg.contains("already exists")) { |
No description provided.