diff --git a/.opencode/specs/ui-inconsistencies-analysis.md b/.opencode/specs/ui-inconsistencies-analysis.md new file mode 100644 index 0000000..42c5cb7 --- /dev/null +++ b/.opencode/specs/ui-inconsistencies-analysis.md @@ -0,0 +1,461 @@ +# UI Inconsistencies Analysis - TracPulse + +**Date:** Fri May 01 2026 +**Status:** Analysis Complete +**Purpose:** Identify and document all UI inconsistencies across the TracPulse Flutter app + +--- + +## Executive Summary + +This analysis identifies **15 UI inconsistencies** across the TracPulse application, categorized into: +- **Critical** (4): Impact user experience significantly +- **Moderate** (6): Noticeable visual/interaction inconsistencies +- **Minor** (5): Small polish items + +--- + +## Critical Inconsistencies + +### 1. **Inconsistent AppBar Implementations** +**Severity:** Critical +**Location:** Multiple screens +**Issue:** +- `main_screen.dart`: Uses custom header with `Container` (lines 171-226) +- `settings_screen.dart`: Uses standard `AppBar` widget (lines 132-160) +- `status_screen.dart`: Uses standard `AppBar` widget (lines 102-139) +- `permission_screen.dart`: Uses custom header with `Container` (lines 247-271) + +**Impact:** Breaks visual consistency and navigation patterns. + +**Details:** +```dart +// Main & Permission screens - custom header +Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), + decoration: const BoxDecoration( + color: Color(0xFF161616), + border: Border(bottom: BorderSide(color: Color(0xFF2a2a2a), width: 1)), + ), + // ... +) + +// Settings & Status screens - AppBar +AppBar( + backgroundColor: const Color(0xFF161616), + elevation: 0, + leading: IconButton(...), + // ... +) +``` + +**Recommendation:** Standardize on either AppBar or custom header across all screens. + +--- + +### 2. **Inconsistent Border Radius Values** +**Severity:** Critical +**Location:** All screens +**Issue:** Mixed use of `4` and `3` pixel border radius values throughout the app. + +**Examples:** +- `permission_screen.dart` line 118: `borderRadius: BorderRadius.circular(4)` (dialog) +- `permission_screen.dart` line 288: `BorderRadius.all(Radius.circular(4))` (icon container) +- `permission_screen.dart` line 406: `borderRadius: BorderRadius.circular(3)` (granted badge) +- `status_screen.dart` line 224: `borderRadius: BorderRadius.circular(3)` (filter chip) +- `status_screen.dart` line 282: `borderRadius: BorderRadius.circular(3)` (icon container) + +**Impact:** Subtle visual inconsistency that degrades polish. + +**Recommendation:** Standardize on `4px` border radius globally (align with design system). + +--- + +### 3. **Inconsistent Icon Sizes** +**Severity:** Critical +**Location:** All screens +**Issue:** Icon sizes vary without clear hierarchy or purpose. + +**Examples:** +```dart +// Main Screen +Icons.shield, size: 20 // permission_screen header +Icons.gps_fixed, size: 14 // status card +Icons.radio_button_checked, size: 20 // tracking toggle +Icons.arrow_upward, size: 16 // send button +Icons.tune/terminal, size: 16 // nav buttons + +// Settings Screen +Icons.arrow_back, size: 20 // back button + +// Status Screen +Icons.terminal, size: 18 // title icon +Icons.arrow_back, size: 20 // back button +Icons.refresh, size: 20 // refresh button +Icons.inbox_outlined, size: 48 // empty state +Icons.[log icon], size: 10 // log entry icons +``` + +**Impact:** Creates visual hierarchy confusion. + +**Recommendation:** Define a clear icon size system: +- Extra Large: 48px (empty states, hero elements) +- Large: 24px (primary actions) +- Medium: 20px (navigation, headers) +- Small: 16px (inline actions) +- Extra Small: 12px (badges, chips) + +--- + +### 4. **Inconsistent Padding and Spacing** +**Severity:** Critical +**Location:** All screens +**Issue:** Inconsistent padding values without clear system. + +**Examples:** +```dart +// Various padding values used: +padding: EdgeInsets.all(20) // main_screen status card +padding: EdgeInsets.symmetric(horizontal: 20) // main_screen scroll +padding: EdgeInsets.all(16) // permission intro card +padding: EdgeInsets.all(14) // permission items +padding: EdgeInsets.symmetric(horizontal: 14) // settings fields +padding: EdgeInsets.symmetric(horizontal: 16) // status filter bar +padding: EdgeInsets.symmetric(horizontal: 10) // status filter chips +padding: EdgeInsets.only(left: 14, right: 14) // status log entries +``` + +**Impact:** Reduces visual rhythm and professionalism. + +**Recommendation:** Establish spacing system: +- xxs: 4px +- xs: 8px +- sm: 12px +- md: 16px +- lg: 20px +- xl: 24px +- xxl: 32px + +--- + +## Moderate Inconsistencies + +### 5. **Inconsistent Text Styles** +**Severity:** Moderate +**Location:** All screens +**Issue:** Text styling lacks systematic hierarchy. + +**Examples:** +```dart +// Font sizes used: 9, 10, 11, 12, 13, 14, 16, 18, 48 +// Letter spacing: 0, 1, 2, 3 +// Font weights: w400, w600, w700 + +// Headers +fontSize: 18, letterSpacing: 3 // main_screen +fontSize: 16, letterSpacing: 3 // permission_screen +fontSize: 14, letterSpacing: 2 // settings_screen, status_screen + +// Body text +fontSize: 13 // various places +fontSize: 12 // various places +fontSize: 11 // various places +``` + +**Impact:** Reduces readability and visual hierarchy. + +**Recommendation:** Create text style system with consistent naming. + +--- + +### 6. **Inconsistent Button Styles** +**Severity:** Moderate +**Location:** Multiple screens +**Issue:** Different button implementations for similar actions. + +**Examples:** +```dart +// permission_screen: Custom Material + InkWell (line 512) +Material( + color: _allGranted ? ... : ..., + child: InkWell(onTap: ..., child: Container(...)) +) + +// main_screen: GestureDetector (line 308) +GestureDetector( + onTap: _toggleTracking, + child: Container(...) +) + +// main_screen: OutlinedButton.icon (line 377) +OutlinedButton.icon( + onPressed: ..., + style: OutlinedButton.styleFrom(...), +) + +// main_screen: Custom Material + InkWell (line 442) +Material( + color: const Color(0xFF161616), + child: InkWell(...) +) +``` + +**Impact:** Inconsistent touch feedback and visual states. + +**Recommendation:** Standardize on Material + InkWell pattern with consistent ripple effects. + +--- + +### 7. **Inconsistent Empty State Handling** +**Severity:** Moderate +**Location:** `status_screen.dart` +**Issue:** Only Status screen has empty state, but other screens might need it too. + +**Current Implementation:** (lines 166-198) +```dart +child: _filteredLogs.isEmpty + ? Center(child: Column(...)) // Empty state + : ListView.builder(...) +``` + +**Impact:** Inconsistent UX when screens have no data. + +**Recommendation:** Consider empty states for other screens where applicable. + +--- + +### 8. **Inconsistent Color Opacity Usage** +**Severity:** Moderate +**Location:** All screens +**Issue:** Mixed use of `.withValues(alpha: x)` and color literals. + +**Examples:** +```dart +Color(0xFF00bcd4).withValues(alpha: 0.8) // main_screen line 251 +Color(0xFF00e676).withValues(alpha: 0.3) // permission_screen line 347 +Color(0xFF00e676).withValues(alpha: 0.15) // permission_screen line 359 +Color(0xFF00bcd4).withValues(alpha: 0.1) // permission_screen line 430 +``` + +**Impact:** Difficult to maintain consistent transparency levels. + +**Recommendation:** Define standard alpha values (0.05, 0.1, 0.15, 0.3, 0.5, 0.6, 0.8). + +--- + +### 9. **Inconsistent Loading/Error State Handling** +**Severity:** Moderate +**Location:** All screens +**Issue:** No consistent loading indicators or error states. + +**Observation:** +- No loading spinners shown during async operations +- Error handling relies only on SnackBar +- No retry mechanisms for failed operations + +**Recommendation:** Add loading states and error recovery UI. + +--- + +### 10. **Inconsistent Switch Widget Styling** +**Severity:** Moderate +**Location:** `settings_screen.dart` line 386 +**Issue:** Switch uses deprecated color properties pattern (though updated to new API). + +**Current:** +```dart +Switch( + value: value, + onChanged: onChanged, + activeThumbColor: const Color(0xFF00e676), + activeTrackColor: const Color(0xFF00e676).withValues(alpha: 0.3), +) +``` + +**Recommendation:** Consider using Material 3 Switch theming for consistency. + +--- + +## Minor Inconsistencies + +### 11. **Inconsistent SizedBox Spacing** +**Severity:** Minor +**Location:** All screens +**Issue:** Arbitrary SizedBox heights without clear system. + +**Examples:** +```dart +const SizedBox(height: 2) // settings_screen line 374 +const SizedBox(height: 4) // permission_screen line 311 +const SizedBox(height: 5) // status_screen line 306 +const SizedBox(height: 8) // multiple places +const SizedBox(height: 10) // multiple places +const SizedBox(height: 12) // multiple places +// ... and more +``` + +**Recommendation:** Use spacing system constants instead of literals. + +--- + +### 12. **Inconsistent Container Decoration Pattern** +**Severity:** Minor +**Location:** All screens +**Issue:** Some decorations defined inline, others could use constants. + +**Example:** +```dart +// Repeated decoration pattern: +decoration: BoxDecoration( + color: const Color(0xFF161616), + borderRadius: BorderRadius.circular(4), + border: Border.all(color: const Color(0xFF2a2a2a), width: 1), +) +``` + +**Recommendation:** Create reusable decoration constants. + +--- + +### 13. **Inconsistent Const Usage** +**Severity:** Minor +**Location:** Multiple files +**Issue:** Some widgets marked const, others not (inconsistently). + +**Example:** +```dart +// settings_screen line 111 +const [ + Icon(Icons.check_circle, color: Color(0xFF00e676), size: 18), + // ... +] +``` + +**Recommendation:** Audit and add const where applicable for performance. + +--- + +### 14. **Inconsistent Dialog Implementation** +**Severity:** Minor +**Location:** `permission_screen.dart` line 112 +**Issue:** Only one dialog in app, but pattern not reusable. + +**Current:** +```dart +showDialog( + context: context, + barrierDismissible: false, + builder: (ctx) => AlertDialog(...) +) +``` + +**Recommendation:** Create reusable dialog component with consistent styling. + +--- + +### 15. **Inconsistent Animation Usage** +**Severity:** Minor +**Location:** `main_screen.dart` only +**Issue:** Only main screen has animations (pulse effect). + +**Current:** (lines 24-36) +```dart +late AnimationController _pulseController; +late Animation _pulseAnimation; +``` + +**Impact:** Other screens feel static in comparison. + +**Recommendation:** Consider subtle animations elsewhere for consistency (e.g., permission granted transitions). + +--- + +## Color System Issues + +### Inconsistent Color Definitions +All colors are defined inline with hex literals. Should be centralized: + +```dart +// Current usage +const Color(0xFF0d0d0d) // Background +const Color(0xFF161616) // Card background +const Color(0xFF2a2a2a) // Border +const Color(0xFF00bcd4) // Primary (teal) +const Color(0xFF00e676) // Success (green) +const Color(0xFFe0e0e0) // Text primary +const Color(0xFF9e9e9e) // Text secondary +const Color(0xFF616161) // Text disabled +``` + +**Recommendation:** Create `lib/theme/colors.dart` with named constants. + +--- + +## Priority Matrix + +| Priority | Count | Items | +|----------|-------|-------| +| Critical | 4 | AppBar inconsistency, Border radius, Icon sizes, Padding/spacing | +| Moderate | 6 | Text styles, Button styles, Empty states, Color opacity, Loading states, Switch styling | +| Minor | 5 | SizedBox spacing, Container decorations, Const usage, Dialog pattern, Animation usage | + +--- + +## Recommendations Summary + +1. **Create Design System** (`lib/theme/`) + - `colors.dart` - Centralized color constants + - `spacing.dart` - Spacing scale + - `typography.dart` - Text style hierarchy + - `decorations.dart` - Reusable decorations + - `dimensions.dart` - Icon sizes, border radius + +2. **Standardize Components** (`lib/widgets/`) + - `app_button.dart` - Consistent button component + - `app_header.dart` - Reusable header/appbar + - `app_card.dart` - Standard card container + - `app_dialog.dart` - Consistent dialog styling + +3. **Update All Screens** + - Replace inline styles with design system constants + - Standardize component usage + - Add consistent loading/error states + - Ensure const usage for performance + +4. **Testing** + - Visual regression testing + - Ensure animations perform smoothly + - Verify touch targets meet accessibility standards + +--- + +## Next Steps + +1. **Phase 1: Design System Setup** + - Create theme files + - Define constants + - Document usage + +2. **Phase 2: Component Standardization** + - Build reusable widgets + - Update existing screens + - Test interactions + +3. **Phase 3: Polish** + - Add missing animations + - Improve empty/error states + - Performance optimization + +--- + +## Notes + +- All screens use `fontFamily: 'monospace'` consistently ✓ +- Dark theme colors are consistent (good base) ✓ +- Material 3 is enabled (`useMaterial3: true`) ✓ +- Deprecated APIs have been updated (`.withValues(alpha:)`) ✓ +- ZoomPageTransitionsBuilder prevents white flash ✓ + +--- + +**End of Analysis** diff --git a/android/app/src/main/kotlin/dev/fiatcode/tracpulse/BridgeModule.kt b/android/app/src/main/kotlin/dev/fiatcode/tracpulse/BridgeModule.kt index 8ed9929..7be79e5 100644 --- a/android/app/src/main/kotlin/dev/fiatcode/tracpulse/BridgeModule.kt +++ b/android/app/src/main/kotlin/dev/fiatcode/tracpulse/BridgeModule.kt @@ -93,6 +93,9 @@ class BridgeModule : FlutterPlugin, MethodChannel.MethodCallHandler { "isBatteryOptimizationDisabled" -> { result.success(isBatteryOptimizationDisabled()) } + "clearLogs" -> { + clearLogs(result) + } else -> result.notImplemented() } } @@ -191,6 +194,7 @@ class BridgeModule : FlutterPlugin, MethodChannel.MethodCallHandler { } mainHandler.post { result.success(logs) } } catch (e: Exception) { + Log.e(TAG, "fetchLogs failed", e) mainHandler.post { result.success(emptyList>()) } } }.start() @@ -228,4 +232,23 @@ class BridgeModule : FlutterPlugin, MethodChannel.MethodCallHandler { false } } + + private fun clearLogs(result: MethodChannel.Result) { + Thread { + try { + val deleted = runBlocking(Dispatchers.IO) { + val db = context?.let { AppDatabase.getInstance(it) } + if (db != null) { + db.eventLogDao().deleteAll() + } else { + 0 + } + } + mainHandler.post { result.success(deleted) } + } catch (e: Exception) { + Log.e(TAG, "clearLogs failed", e) + mainHandler.post { result.success(-1) } + } + }.start() + } } \ No newline at end of file diff --git a/android/app/src/main/kotlin/dev/fiatcode/tracpulse/service/LocationTrackingService.kt b/android/app/src/main/kotlin/dev/fiatcode/tracpulse/service/LocationTrackingService.kt index 00834c0..2c67e92 100644 --- a/android/app/src/main/kotlin/dev/fiatcode/tracpulse/service/LocationTrackingService.kt +++ b/android/app/src/main/kotlin/dev/fiatcode/tracpulse/service/LocationTrackingService.kt @@ -42,7 +42,7 @@ import android.util.Log class LocationTrackingService : Service() { private val binder = LocalBinder() - private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) + private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) private val TAG = "LocationTrackingService" private lateinit var fusedLocationProvider: FusedLocationProvider diff --git a/android/app/src/main/kotlin/dev/fiatcode/tracpulse/storage/EventLogDao.kt b/android/app/src/main/kotlin/dev/fiatcode/tracpulse/storage/EventLogDao.kt index f6bcb84..a770aa6 100644 --- a/android/app/src/main/kotlin/dev/fiatcode/tracpulse/storage/EventLogDao.kt +++ b/android/app/src/main/kotlin/dev/fiatcode/tracpulse/storage/EventLogDao.kt @@ -18,4 +18,7 @@ interface EventLogDao { @Query("DELETE FROM event_log WHERE timestamp < :beforeTimestamp") suspend fun deleteOlderThan(beforeTimestamp: Long) + + @Query("DELETE FROM event_log") + suspend fun deleteAll() } \ No newline at end of file diff --git a/lib/bridge/location_bridge.dart b/lib/bridge/location_bridge.dart index 096257f..a11a655 100644 --- a/lib/bridge/location_bridge.dart +++ b/lib/bridge/location_bridge.dart @@ -116,4 +116,14 @@ class LocationBridge { return false; } } + + static Future clearLogs() async { + try { + final result = await _methodChannel.invokeMethod('clearLogs'); + return result ?? -1; + } on PlatformException catch (e) { + debugPrint('Failed to clear logs: ${e.message}'); + return -1; + } + } } diff --git a/lib/info_screen.dart b/lib/info_screen.dart index 3eeff29..a980fd9 100644 --- a/lib/info_screen.dart +++ b/lib/info_screen.dart @@ -269,4 +269,4 @@ class InfoScreen extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/status_screen.dart b/lib/status_screen.dart index 65e3b2e..3e70759 100644 --- a/lib/status_screen.dart +++ b/lib/status_screen.dart @@ -47,6 +47,15 @@ class _StatusScreenState extends State { } } + Future _clearLogs() async { + final result = await LocationBridge.clearLogs(); + if (mounted) { + if (result >= 0) { + await _loadLogs(); + } + } + } + String _formatTimestamp(int timestamp) { final dt = DateTime.fromMillisecondsSinceEpoch(timestamp); final ms = (dt.millisecond / 1000).toStringAsFixed(3).substring(2); @@ -128,6 +137,14 @@ class _StatusScreenState extends State { onPressed: () => Navigator.pop(context), ), actions: [ + IconButton( + icon: const Icon( + Icons.delete_outline, + color: Color(0xFF616161), + size: 20, + ), + onPressed: _clearLogs, + ), IconButton( icon: const Icon(Icons.refresh, color: Color(0xFF616161), size: 20), onPressed: _loadLogs,