Skip to content

New list insights feature among other stylistic changes#160

Merged
aadishsamir123 merged 8 commits intomainfrom
v5.0.0-phone-list-insights
Dec 11, 2025
Merged

New list insights feature among other stylistic changes#160
aadishsamir123 merged 8 commits intomainfrom
v5.0.0-phone-list-insights

Conversation

@aadishsamir123
Copy link
Copy Markdown
Member

@aadishsamir123 aadishsamir123 commented Dec 10, 2025

This pull request introduces a new dual-level analytics and insights architecture, adds user and list insights screens, and standardizes loading indicators across the app. It also updates documentation and UI navigation to reflect these changes. The most important changes are grouped below:

Analytics & Insights Architecture

  • Added a dual-level insights system: user-level insights accessible from the home drawer (UserInsightsScreen), and list-level insights accessible via a new tab in each list (ListInsightsScreen). Both use dedicated analytics services. [1] [2] [3] [4] [5]
  • Updated .github/copilot-instructions.md with conventions for analytics field names, navigation tab order, and architectural patterns for future development.

Navigation & UI Enhancements

  • Added an "Insights" tab to list navigation with a donut spin animation, ensuring consistent tab order: Items → Insights → Options. Updated navigation components and tab animations to support this. [1] [2] [3] [4] [5] [6] [7] [8]

Loading Indicator Standardization

  • Replaced all instances of CircularProgressIndicator with CustomLoadingSpinner or CircularProgressIndicatorM3E for a consistent look across phone, web, and WearOS. Updated relevant widgets and documentation. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]

Documentation & Linting

  • Updated copilot instructions to require documentation for new features and conventions. Added linter rule to ignore library_private_types_in_public_api warnings. [1] [2]

Miscellaneous

  • Minor improvements to widget imports and progress indicator appearance for enhanced user experience. [1] [2]

Summary by CodeRabbit

  • New Features

    • Added user-level and list-level analytics dashboards with time-frame filters, insights, trends, top categories, productivity, and collaborator activity; "Your Insights" navigation and per-list Insights screen.
  • Improvements

    • Unified loading UX with platform-aware loading indicators and an animated Insights tab icon; updated progress indicator styling and retry/empty states.
  • Tests

    • Added unit tests for analytics models, date ranges, and related helper logic.
  • Documentation

    • Expanded analytics & insights architecture, testing responsibilities, and Copilot guidance.
  • Chores

    • App version bumped to 5.0.0

✏️ Tip: You can customize this high-level summary in your review settings.

…ive data models and insights

- Added AnalyticsService for tracking shopping insights, including total lists created, items added, and completion rates.
- Introduced ListAnalyticsService for detailed list-specific insights, including item activity and collaborator contributions.
- Created data models for insights, completion data, and activity tracking.
- Implemented date range calculations for various timeframes.
- Developed unit tests for both services to ensure functionality and data integrity.
@aadishsamir123 aadishsamir123 added this to the v5.0.0-phone milestone Dec 10, 2025
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Dec 10, 2025

📝 Walkthrough

Walkthrough

Adds user- and list-level analytics services and models, two new insights screens with navigation and animations, replaces several CircularProgressIndicator usages with a CustomLoadingSpinner, updates analyzer suppressions and Copilot/docs, adds unit tests, and bumps app versioning.

Changes

Cohort / File(s) Summary
Documentation & Linting
/.github/copilot-instructions.md, analysis_options.yaml
Expanded Copilot/documentation (analytics architecture, testing guidance, "UPDATE COPILOT INSTRUCTIONS") and added analyzer suppression library_private_types_in_public_api: ignore.
User-level analytics & tests
lib/services/analytics_service.dart, test/unit/services/analytics_service_test.dart
New static AnalyticsService with TimeFrame/IconValue enums, domain models (ShoppingInsight, ListCompletionData, CategoryInsight, ChartData, DateRange), Firestore aggregation methods, Sentry error handling, and unit tests validating models and helpers.
List-level analytics & tests
lib/services/list_analytics_service.dart, test/unit/services/list_analytics_service_test.dart
New ListAnalyticsService with ListTimeFrame enum and models (ListInsightData, ItemActivityData, CategoryBreakdown, CollaboratorActivity), timeframe helpers, Firestore-backed metrics, Sentry error handling, and unit tests for data models and simple calculations.
Analytics UI screens
lib/screens/user_insights.dart, lib/screens/list_insights.dart
Added UserInsightsScreen and ListInsightsScreen (stateful) with timeframe selectors, key-insights grids, charts/timelines, category/collaborator sections, loading/error/empty states and retry affordances.
Navigation & main views
lib/screens/home.dart, lib/screens/list_view.dart
Added drawer item for "Your Insights"; integrated insights screens into navigation/IndexedStack; introduced _insightsAnimationController spin animation for Insights icon; adjusted tab indices and a progress indicator widget swap.
Loading spinner replacements
lib/widgets/advert.dart, lib/widgets/animated_google_button.dart, lib/widgets/expandable_list_group_widget.dart, lib/widgets/place_selector.dart, lib/widgets/smart_suggestions_widget.dart, lib/widgets/loading_spinner.dart
Replaced several CircularProgressIndicator usages with CustomLoadingSpinner, added imports and adjusted variant usage.
Packaging & versioning
android/app/build.gradle, pubspec.yaml
Bumped Android phone flavor versionCode/versionName (phone 4 → 5; versionName5.0.0-phone) and pubspec version (4.3.1-other5.0.0-other).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant App as App (UI / Navigation)
    participant UIS as UserInsightsScreen
    participant LIS as ListInsightsScreen
    participant AS as AnalyticsService
    participant LAS as ListAnalyticsService
    participant FS as Firestore

    User->>App: open "Your Insights" or select list → Insights
    App->>UIS: navigate to UserInsightsScreen
    UIS->>AS: request metrics (generateKeyInsights, trends, itemsPerDay)
    AS->>FS: query user lists / items / categories
    FS-->>AS: return documents
    AS-->>UIS: aggregated insights & chart data
    UIS-->>User: render cards & charts

    User->>App: open specific list insights
    App->>LIS: navigate to ListInsightsScreen(listId)
    LIS->>LAS: request list metrics (activityTimeline, categoryBreakdown, collaborators)
    LAS->>FS: query lists/{id}/items and categories
    FS-->>LAS: return documents
    LAS-->>LIS: aggregated list insights & timelines
    LIS-->>User: render activity chart, breakdowns, collaborator list
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Areas requiring attention:
    • Firestore query correctness, indexing and date-range mapping in analytics_service.dart and list_analytics_service.dart.
    • Aggregation math (averages, percentages) and zero-division guards.
    • In-memory filtering vs server-side queries (performance/cost implications).
    • Sentry usage and consistent error handling with safe defaults.
    • Async state handling and multiple FutureBuilder usages in user_insights.dart / list_insights.dart.
    • Animation lifecycle and integration in list_view.dart.
    • Consistency/theming of CustomLoadingSpinner replacements.

Possibly related PRs

Pre-merge checks

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title refers to list insights as a key feature but downplays the significant dual-level analytics architecture, navigation enhancements, and systematic loading indicator changes as 'other stylistic changes.'
Description check ✅ Passed The description provides a comprehensive overview of the major changes including dual-level analytics architecture, navigation enhancements, loading indicator standardization, and documentation updates with supporting reference links.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sentry
Copy link
Copy Markdown
Contributor

sentry bot commented Dec 10, 2025

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

ℹ️ You can also turn on project coverage checks and project coverage reporting on Pull Request comment

Thanks for integrating Codecov - We've got you covered ☂️

@coderabbitai coderabbitai bot added app:phone Related to ShopSync Phone/Web app enhancement New feature or request semver-minor Minor Updates labels Dec 10, 2025
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🧹 Nitpick comments (10)
test/unit/services/analytics_service_test.dart (1)

10-33: Tests don't exercise actual service methods.

These tests create local values and assert their types rather than calling the actual AnalyticsService methods. While the comments mention "Full integration would require Firebase mocking," these tests provide minimal coverage value.

Consider either:

  1. Adding Firebase mocks (using fake_cloud_firestore from dev dependencies) to test actual behavior
  2. Removing these placeholder tests to avoid misleading coverage metrics
lib/services/list_analytics_service.dart (2)

166-199: Performance: full collection scan can be avoided with composite index.

The comment states "we can't use multiple where clauses," but Firestore supports composite queries on different fields with an index. This approach loads all completed items into memory regardless of timeframe.

Consider adding a composite index on (completed, completedAt) and querying directly:

final snapshot = await _firestore
    .collection('lists')
    .doc(listId)
    .collection('items')
    .where('completed', isEqualTo: true)
    .where('completedAt', isGreaterThanOrEqualTo: dateRange.start)
    .where('completedAt', isLessThanOrEqualTo: dateRange.end)
    .count()
    .get();

386-396: Performance: sequential async calls can be parallelized.

Six independent Firestore calls are awaited sequentially. Use Future.wait to parallelize:

final results = await Future.wait([
  getTotalItems(listId),
  getCompletedItems(listId),
  getCompletionPercentage(listId),
  getItemsAddedInTimeframe(listId, timeFrame),
  getItemsCompletedInTimeframe(listId, timeFrame),
  getAverageItemsPerDay(listId, timeFrame),
]);

final totalItems = results[0] as int;
final completedItems = results[1] as int;
// ... etc
lib/widgets/expandable_list_group_widget.dart (1)

146-151: Consider explicit size parameter for better clarity.

The CustomLoadingSpinner is constrained to 12×12 via SizedBox, but the spinner's default size is 50.0. Consider passing an explicit size parameter to CustomLoadingSpinner for clarity, and adding const to prevent unnecessary rebuilds.

Apply this diff:

-                      ? SizedBox(
-                          width: 12,
-                          height: 12,
-                          child: CustomLoadingSpinner(),
-                        )
+                      ? const SizedBox(
+                          width: 12,
+                          height: 12,
+                          child: CustomLoadingSpinner(size: 12),
+                        )
lib/screens/list_insights.dart (1)

35-63: Consider parallel data loading for better performance.

The four analytics queries execute sequentially, which could slow down the initial load. Since they appear independent, consider using Future.wait to load them in parallel.

Apply this diff:

   Future<void> _loadData() async {
     setState(() => _isLoading = true);
 
     try {
-      final insights = await ListAnalyticsService.generateListInsights(
-          widget.listId, _selectedTimeFrame);
-      final categories =
-          await ListAnalyticsService.getCategoryBreakdown(widget.listId);
-      final activity = await ListAnalyticsService.getActivityTimeline(
-          widget.listId, _selectedTimeFrame);
-      final collaborators = await ListAnalyticsService.getCollaboratorActivity(
-          widget.listId, _selectedTimeFrame);
+      final results = await Future.wait([
+        ListAnalyticsService.generateListInsights(widget.listId, _selectedTimeFrame),
+        ListAnalyticsService.getCategoryBreakdown(widget.listId),
+        ListAnalyticsService.getActivityTimeline(widget.listId, _selectedTimeFrame),
+        ListAnalyticsService.getCollaboratorActivity(widget.listId, _selectedTimeFrame),
+      ]);
+
+      if (!mounted) return;
 
       setState(() {
-        _keyInsights = insights;
-        _categoryBreakdown = categories;
-        _activityTimeline = activity;
-        _collaboratorActivity = collaborators;
+        _keyInsights = results[0] as List<ListInsightData>;
+        _categoryBreakdown = results[1] as List<CategoryBreakdown>;
+        _activityTimeline = results[2] as List<ItemActivityData>;
+        _collaboratorActivity = results[3] as List<CollaboratorActivity>;
         _isLoading = false;
       });
     } catch (e) {
lib/screens/list_view.dart (2)

241-273: Inconsistent animation on unselected Insights icon in NavigationRail.

The unselected icon for the Insights destination is a static Icon without AnimatedBuilder, while selectedIcon uses AnimatedBuilder. This differs from the Items and Options destinations where both icons are animated. Consider wrapping the unselected icon with the same animation for consistency.

 NavigationRailDestination(
-  icon: Icon(
-    Icons.donut_small,
-    color: _selectedIndex == 1
-        ? Colors.white
-        : (isDark ? Colors.grey[400] : Colors.green[600]),
-  ),
+  icon: AnimatedBuilder(
+    animation: _insightsSpinAnimation,
+    builder: (context, child) {
+      return Transform.rotate(
+        angle: _selectedIndex == 1
+            ? _insightsSpinAnimation.value * 2 * 3.14159
+            : 0.0,
+        child: Icon(
+          Icons.donut_small,
+          color: _selectedIndex == 1
+              ? Colors.white
+              : (isDark ? Colors.grey[400] : Colors.green[600]),
+        ),
+      );
+    },
+  ),
   selectedIcon: AnimatedBuilder(

402-416: Same inconsistency: unselected Insights icon missing animation in NavigationBar.

For consistency with the NavigationRail and other destinations, wrap the unselected icon with AnimatedBuilder.

 NavigationDestination(
-  icon: const Icon(Icons.donut_small),
+  icon: AnimatedBuilder(
+    animation: _insightsSpinAnimation,
+    builder: (context, child) {
+      return Transform.rotate(
+        angle: _selectedIndex == 1
+            ? _insightsSpinAnimation.value * 2 * 3.14159
+            : 0.0,
+        child: const Icon(Icons.donut_small),
+      );
+    },
+  ),
   selectedIcon: AnimatedBuilder(
lib/services/analytics_service.dart (3)

131-142: N+1 query pattern may cause performance issues.

Looping through all user lists and issuing a separate Firestore query for each list's items can be slow for users with many lists. Consider:

  1. Using Firestore collection group queries if the schema supports it
  2. Implementing pagination or limiting the number of lists processed
  3. Adding client-side caching for repeated analytics views

489-544: Sequential await calls in generateKeyInsights impact load time.

Six sequential async operations compound latency. Consider parallel execution:

 static Future<List<ShoppingInsight>> generateKeyInsights(
     TimeFrame timeFrame) async {
   try {
-    final totalLists = await getTotalListsCreated(timeFrame);
-    final totalItems = await getTotalItemsAdded(timeFrame);
-    final totalCompleted = await getTotalItemsCompleted(timeFrame);
-    final avgCompletion = await getAverageCompletionPercentage(timeFrame);
-    final collaborators = await getCollaboratorCount(timeFrame);
-    final productivity = await getProductivityTrend(timeFrame);
+    final results = await Future.wait([
+      getTotalListsCreated(timeFrame),
+      getTotalItemsAdded(timeFrame),
+      getTotalItemsCompleted(timeFrame),
+      getAverageCompletionPercentage(timeFrame),
+      getCollaboratorCount(timeFrame),
+      getProductivityTrend(timeFrame),
+    ]);
+
+    final totalLists = results[0] as int;
+    final totalItems = results[1] as int;
+    final totalCompleted = results[2] as int;
+    final avgCompletion = results[3] as double;
+    final collaborators = results[4] as int;
+    final productivity = results[5] as double;

     return [
       // ... rest unchanged
     ];

Note: getProductivityTrend internally calls getTotalItemsAdded, so there's redundant fetching. Consider refactoring to share data.


547-559: Consider making DateRange and ChartData immutable with const constructors.

These are simple data classes that would benefit from immutability.

 class DateRange {
   final DateTime start;
   final DateTime end;

-  DateRange(this.start, this.end);
+  const DateRange(this.start, this.end);
 }

 class ChartData {
   final DateTime date;
   final double value;

-  ChartData({required this.date, required this.value});
+  const ChartData({required this.date, required this.value});
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9a7b699 and 0be253e.

📒 Files selected for processing (16)
  • .github/copilot-instructions.md (3 hunks)
  • analysis_options.yaml (1 hunks)
  • lib/screens/home.dart (2 hunks)
  • lib/screens/list_insights.dart (1 hunks)
  • lib/screens/list_view.dart (11 hunks)
  • lib/screens/user_insights.dart (1 hunks)
  • lib/services/analytics_service.dart (1 hunks)
  • lib/services/list_analytics_service.dart (1 hunks)
  • lib/widgets/advert.dart (2 hunks)
  • lib/widgets/animated_google_button.dart (2 hunks)
  • lib/widgets/expandable_list_group_widget.dart (1 hunks)
  • lib/widgets/loading_spinner.dart (1 hunks)
  • lib/widgets/place_selector.dart (2 hunks)
  • lib/widgets/smart_suggestions_widget.dart (2 hunks)
  • test/unit/services/analytics_service_test.dart (1 hunks)
  • test/unit/services/list_analytics_service_test.dart (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.dart

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.dart: Use snake_case for file names, PascalCase for classes, and camelCase for variables in Dart code
Use StreamBuilder<QuerySnapshot> and StreamBuilder<DocumentSnapshot> for real-time Firebase sync instead of Provider/ChangeNotifier
Use FieldValue.serverTimestamp() for createdAt/updatedAt fields in Firestore documents
Prefer SingleTickerProviderStateMixin + AnimationController for animations
Use FutureBuilder for one-time async operations like checking update status, not for ongoing streams
Use ListGroupsService for group CRUD operations and direct Firestore queries for list/item CRUD operations
Use kDebugMode checks for debug prints and conditional debugging logic

Files:

  • lib/widgets/animated_google_button.dart
  • lib/widgets/place_selector.dart
  • lib/widgets/smart_suggestions_widget.dart
  • lib/widgets/loading_spinner.dart
  • lib/widgets/advert.dart
  • lib/screens/list_insights.dart
  • lib/screens/home.dart
  • test/unit/services/list_analytics_service_test.dart
  • test/unit/services/analytics_service_test.dart
  • lib/widgets/expandable_list_group_widget.dart
  • lib/screens/user_insights.dart
  • lib/services/analytics_service.dart
  • lib/screens/list_view.dart
  • lib/services/list_analytics_service.dart
analysis_options.yaml

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Set use_build_context_synchronously: ignore in analysis_options.yaml for Flutter linting

Files:

  • analysis_options.yaml
lib/services/**/*.dart

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

lib/services/**/*.dart: Implement all services as static utility classes without singletons or instances (e.g., ListGroupsService, CategoriesService, GoogleAuthService)
Wrap all service method errors with Sentry.captureException() including contextual hints via Hint.withMap()
Use Credential Manager on Android (v2.0.0 API) for Google Sign-In to support passkey authentication
Use google_sign_in with client ID from GoogleAuthService._webClientId for web authentication flows
Use HomeWidgetService.updateWidget() to sync shopping list data to Android home widget launcher
Call UpdateService.checkForUpdate() to trigger Android Play update flow for in-app updates

Files:

  • lib/services/analytics_service.dart
  • lib/services/list_analytics_service.dart
🧠 Learnings (13)
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to **/*.dart : Prefer `SingleTickerProviderStateMixin` + `AnimationController` for animations

Applied to files:

  • lib/widgets/animated_google_button.dart
  • .github/copilot-instructions.md
  • lib/screens/list_view.dart
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to lib/wear/screens/**/*.dart : Implement WearOS UI in `lib/wear/screens/` using circular layouts with rotary scroll support via `rotary_scrollbar` package and ambient mode via `wear_plus`

Applied to files:

  • lib/widgets/animated_google_button.dart
  • lib/widgets/smart_suggestions_widget.dart
  • lib/screens/home.dart
  • .github/copilot-instructions.md
  • lib/screens/list_view.dart
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to lib/services/**/*.dart : Use `HomeWidgetService.updateWidget()` to sync shopping list data to Android home widget launcher

Applied to files:

  • lib/widgets/place_selector.dart
  • lib/widgets/smart_suggestions_widget.dart
  • lib/widgets/advert.dart
  • lib/screens/list_insights.dart
  • lib/screens/home.dart
  • .github/copilot-instructions.md
  • lib/widgets/expandable_list_group_widget.dart
  • lib/services/analytics_service.dart
  • lib/screens/list_view.dart
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to **/*.dart : Use `StreamBuilder<QuerySnapshot>` and `StreamBuilder<DocumentSnapshot>` for real-time Firebase sync instead of Provider/ChangeNotifier

Applied to files:

  • lib/widgets/place_selector.dart
  • lib/widgets/smart_suggestions_widget.dart
  • lib/screens/home.dart
  • .github/copilot-instructions.md
  • lib/screens/list_view.dart
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to **/*.dart : Use `FutureBuilder` for one-time async operations like checking update status, not for ongoing streams

Applied to files:

  • lib/widgets/place_selector.dart
  • .github/copilot-instructions.md
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to analysis_options.yaml : Set `use_build_context_synchronously: ignore` in `analysis_options.yaml` for Flutter linting

Applied to files:

  • analysis_options.yaml
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to **/*.dart : Use `ListGroupsService` for group CRUD operations and direct Firestore queries for list/item CRUD operations

Applied to files:

  • lib/screens/home.dart
  • .github/copilot-instructions.md
  • test/unit/services/list_analytics_service_test.dart
  • lib/services/analytics_service.dart
  • lib/screens/list_view.dart
  • lib/services/list_analytics_service.dart
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Organize Flutter project structure with `lib/screens/` for full-page UI, `lib/widgets/` for reusable components, and `lib/services/` for business logic & Firebase interactions

Applied to files:

  • lib/screens/home.dart
  • lib/screens/user_insights.dart
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to **/*.dart : Use `FieldValue.serverTimestamp()` for `createdAt`/`updatedAt` fields in Firestore documents

Applied to files:

  • .github/copilot-instructions.md
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to lib/services/**/*.dart : Wrap all service method errors with `Sentry.captureException()` including contextual hints via `Hint.withMap()`

Applied to files:

  • .github/copilot-instructions.md
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to lib/services/**/*.dart : Implement all services as static utility classes without singletons or instances (e.g., `ListGroupsService`, `CategoriesService`, `GoogleAuthService`)

Applied to files:

  • .github/copilot-instructions.md
  • test/unit/services/list_analytics_service_test.dart
  • lib/services/analytics_service.dart
  • lib/services/list_analytics_service.dart
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to lib/services/**/*.dart : Use Credential Manager on Android (v2.0.0 API) for Google Sign-In to support passkey authentication

Applied to files:

  • .github/copilot-instructions.md
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to lib/main.dart : Use Sentry with 100% trace and profile sample rate as configured in `main.dart`

Applied to files:

  • .github/copilot-instructions.md
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Tests & Coverage
  • GitHub Check: Build WearOS Flavor
  • GitHub Check: Build Phone Flavor
  • GitHub Check: Build Web (WASM)
  • GitHub Check: Seer Code Review
  • GitHub Check: Build Web (WASM)
  • GitHub Check: Build WearOS (Debug)
  • GitHub Check: Build Phone (Debug)
🔇 Additional comments (25)
analysis_options.yaml (1)

4-4: LGTM — suppression is reasonable for Flutter widget patterns.

This lint suppression is commonly needed when widget State classes use private types. As per coding guidelines, use_build_context_synchronously: ignore is already expected.

test/unit/services/analytics_service_test.dart (1)

74-227: Good coverage of data model constructors and edge cases.

The data model tests properly validate field storage, calculation logic (completionPercentage), and edge cases (zero items, single items). These provide meaningful coverage.

test/unit/services/list_analytics_service_test.dart (1)

120-193: Good integration tests for data model operations.

The sorting and list operations tests properly validate that data models work correctly in collections. These provide practical coverage for typical usage patterns.

.github/copilot-instructions.md (2)

98-108: Clear platform-specific loading indicator documentation.

Good addition documenting the loading indicator patterns with code examples for both Phone/Web (CustomLoadingSpinner) and WearOS (CircularProgressIndicator).


378-408: Comprehensive analytics architecture documentation.

The dual-level insights system documentation with file references, navigation paths, and tab order conventions will help maintain consistency. The field name conventions (lines 395-400) are especially valuable for avoiding query bugs.

lib/services/list_analytics_service.dart (1)

62-63: Service pattern follows guidelines.

Static utility class with Sentry.captureException including contextual hints aligns with the established service layer pattern.

lib/widgets/animated_google_button.dart (1)

2-2: LGTM! Consistent loading indicator upgrade.

The replacement of CircularProgressIndicator with CustomLoadingSpinner aligns with the project-wide standardization of loading UX while preserving the existing color logic based on dark mode.

Also applies to: 99-101

lib/screens/home.dart (1)

15-15: LGTM! User insights navigation properly integrated.

The new "Your Insights" drawer item follows the established navigation pattern, appropriately closes the drawer before navigating, and uses consistent styling with other menu items.

Also applies to: 783-797

lib/widgets/place_selector.dart (1)

3-3: LGTM! Consistent loading indicator upgrade.

The replacement aligns with the project-wide standardization of loading indicators using CustomLoadingSpinner.

Also applies to: 140-140

lib/widgets/advert.dart (1)

4-4: LGTM! Consistent loading indicator upgrade.

The replacement of CircularProgressIndicator with CustomLoadingSpinner in the ad placeholder aligns with the project-wide loading UX standardization.

Also applies to: 141-141

lib/widgets/smart_suggestions_widget.dart (1)

2-2: LGTM! Consistent loading indicator upgrade.

The replacement aligns with the project-wide standardization of loading indicators.

Also applies to: 61-61

lib/screens/list_insights.dart (1)

1-550: LGTM! Well-structured analytics screen with good UX patterns.

The ListInsightsScreen demonstrates solid architecture with:

  • Proper separation of concerns via builder methods
  • Appropriate empty state handling for all data sections
  • Theme-aware styling using ColorScheme
  • Pull-to-refresh support via RefreshIndicator
  • Consistent loading states
lib/widgets/loading_spinner.dart (1)

20-20: The LoadingIndicatorM3EVariant.contained variant is supported in m3e_collection 0.3.7 and is the correct way to use the contained variant of the LoadingIndicatorM3E widget. No changes needed.

Likely an incorrect or invalid review comment.

lib/screens/list_view.dart (6)

30-31: Good use of TickerProviderStateMixin for multiple animation controllers.

Using TickerProviderStateMixin instead of SingleTickerProviderStateMixin is correct here since multiple AnimationController instances require separate tickers. Based on learnings, this aligns with the preferred animation pattern.


78-90: LGTM: Insights animation controller setup.

The animation controller follows the same pattern as the existing options animation, with proper duration and curve settings. The 3.0 full rotations ensures the donut icon returns to its original orientation.


93-99: Animation controllers properly disposed.

All three animation controllers are correctly disposed in the dispose method, preventing memory leaks.


679-683: Good replacement of CircularProgressIndicator with CircularProgressIndicatorM3E.

This aligns with the PR objective of standardizing loading indicators across the app.


913-938: Proper Sentry error handling with contextual hints.

The _toggleItemCompletion method correctly wraps errors with Sentry.captureException() including contextual hints via Hint.withMap(), following the coding guidelines for services.


966-967: Good use of FieldValue.serverTimestamp() for deletedAt.

This follows the coding guideline to use FieldValue.serverTimestamp() for timestamp fields in Firestore documents.

lib/screens/user_insights.dart (3)

163-201: Good implementation of time frame selector.

The button implementation with proper visual feedback, dark/light theming, and accessibility (using Material + InkWell for ripple effects) is well done.


488-551: Good defensive coding in _buildSimpleBarChart.

The method handles empty data, calculates max value safely with fallback, and limits display to last 7 data points. The tooltip provides additional context on hover.


847-866: Well-structured icon mapping helper.

The exhaustive switch statement ensures all IconValue enum cases are handled, preventing runtime errors if new values are added.

lib/services/analytics_service.dart (3)

63-65: Service follows static utility class pattern correctly.

The AnalyticsService is implemented as a static utility class with private static members for _auth and _firestore, following the coding guidelines.


106-111: Good Sentry error handling with contextual hints.

All public methods properly wrap exceptions with Sentry.captureException() and include action context via Hint.withMap(), following the coding guidelines.


264-271: Firestore composite index required for this query.

The query combines a range filter on createdAt with an inequality filter on deleted across different fields. Firestore requires a composite index for this query pattern. If this index doesn't exist, Firestore will return an error with a direct link to create it in the Firebase console.

@coderabbitai coderabbitai bot added the semver-major Major updates label Dec 10, 2025
@aadishsamir123 aadishsamir123 removed the semver-minor Minor Updates label Dec 10, 2025
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (6)
lib/services/analytics_service.dart (2)

270-273: Same deleted field filter concern applies here.

The isNotEqualTo: true filter has the same limitation noted earlier.


76-81: Date arithmetic edge case persists for month/quarter boundaries.

When now.day is 31 and calculating a month ago (e.g., from March 31), DateTime(now.year, now.month - 1, now.day) normalizes Feb 31 → March 2/3, producing ~3 days instead of ~30 days. Same issue exists for quarter calculation.

Consider using duration subtraction or clamping to last valid day:

       case TimeFrame.month:
-        final monthAgo = DateTime(now.year, now.month - 1, now.day);
+        final monthAgo = startOfDay.subtract(const Duration(days: 30));
         return DateRange(monthAgo, now);
       case TimeFrame.quarter:
-        final quarterAgo = DateTime(now.year, now.month - 3, now.day);
+        final quarterAgo = startOfDay.subtract(const Duration(days: 90));
         return DateRange(quarterAgo, now);
lib/services/list_analytics_service.dart (4)

76-78: Same date arithmetic edge case as AnalyticsService.

The month calculation has the same overflow issue when now.day exceeds the target month's days.


451-456: DateRange is duplicated from analytics_service.dart.

Extract to a shared models file to avoid duplication.


184-186: Off-by-one: boundary dates excluded from completed items count.

isAfter and isBefore exclude items completed exactly at dateRange.start or dateRange.end. This was flagged in a previous review.

Use inclusive comparisons:

         if (completedAt != null &&
-            completedAt.isAfter(dateRange.start) &&
-            completedAt.isBefore(dateRange.end)) {
+            !completedAt.isBefore(dateRange.start) &&
+            !completedAt.isAfter(dateRange.end)) {
           count++;
         }

323-329: Inconsistent field name: createdAt should be addedAt.

This method queries 'createdAt' while other methods (getItemsAddedInTimeframe, getActivityTimeline) use 'addedAt' for items. This inconsistency will cause collaborator activity to not match the intended timeframe.

       final itemsSnapshot = await _firestore
           .collection('lists')
           .doc(listId)
           .collection('items')
-          .where('createdAt', isGreaterThanOrEqualTo: dateRange.start)
-          .where('createdAt', isLessThanOrEqualTo: dateRange.end)
+          .where('addedAt', isGreaterThanOrEqualTo: dateRange.start)
+          .where('addedAt', isLessThanOrEqualTo: dateRange.end)
           .get();
🧹 Nitpick comments (6)
.github/copilot-instructions.md (1)

410-430: Test writing guidance could be enhanced with analytics-specific examples.

The "Testing Responsibilities" and "Test Writing Quick Rules" sections (lines 410–430) are solid, but they do not include explicit guidance on testing analytics queries with the documented field-name constraints (e.g., verifying that 'addedAt' and 'completed' fields are correctly queried, or that 'deletedAt' filters work properly). Consider adding a brief note or example test pattern for analytics-specific scenarios to reinforce the field-naming conventions and prevent regressions.

Example enhancement (optional):

// Add to section "Test Writing Quick Rules" or create a new subsection:

**Analytics Query Testing**: When testing analytics services, verify field names explicitly:
- Use 'addedAt' (not 'createdAt') for item timestamp queries
- Use 'completed' (not 'checked') for completion state queries
- Filter deleted items with .where('deleted', isNotEqualTo: true)
- Use only 'addedAt' for time-range queries; 'completedAt' does not exist

Example:

dart
test('getItemAdditionRate queries addedAt field correctly', () {
// Mock Firestore to capture the query
// Verify that .where('addedAt', ...) is called, not .where('createdAt', ...)
expect(querySnapshot.docs.any((doc) => doc['addedAt'] != null), isTrue);
});

lib/services/analytics_service.dart (2)

491-545: Consider parallelizing independent async calls in generateKeyInsights.

The method makes 6 sequential async calls that are independent. Using Future.wait could improve performance.

   static Future<List<ShoppingInsight>> generateKeyInsights(
       TimeFrame timeFrame) async {
     try {
-      final totalLists = await getTotalListsCreated(timeFrame);
-      final totalItems = await getTotalItemsAdded(timeFrame);
-      final totalCompleted = await getTotalItemsCompleted(timeFrame);
-      final avgCompletion = await getAverageCompletionPercentage(timeFrame);
-      final collaborators = await getCollaboratorCount(timeFrame);
-      final productivity = await getProductivityTrend(timeFrame);
+      final results = await Future.wait([
+        getTotalListsCreated(timeFrame),
+        getTotalItemsAdded(timeFrame),
+        getTotalItemsCompleted(timeFrame),
+        getAverageCompletionPercentage(timeFrame),
+        getCollaboratorCount(timeFrame),
+        getProductivityTrend(timeFrame),
+      ]);
+      final totalLists = results[0] as int;
+      final totalItems = results[1] as int;
+      final totalCompleted = results[2] as int;
+      final avgCompletion = results[3] as double;
+      final collaborators = results[4] as int;
+      final productivity = results[5] as double;

548-560: DateRange class is duplicated in list_analytics_service.dart.

Consider extracting shared data models (like DateRange and potentially ChartData) to a common models file to avoid duplication.

lib/screens/list_insights.dart (1)

230-238: Max value calculation could be simplified.

The fold logic works but is verbose.

     final maxValue = _activityTimeline.fold<int>(
       0,
-      (max, data) {
-        final dataMax = data.addedCount > data.completedCount
-            ? data.addedCount
-            : data.completedCount;
-        return dataMax > max ? dataMax : max;
-      },
+      (max, data) => [max, data.addedCount, data.completedCount].reduce((a, b) => a > b ? a : b),
     );

Or use dart:math:

import 'dart:math' as math;
// ...
final maxValue = _activityTimeline.fold<int>(
  0, (max, data) => math.max(max, math.max(data.addedCount, data.completedCount)),
);
lib/services/list_analytics_service.dart (2)

286-292: Date boundary workaround extends range by 1 day on each side.

The subtract(Duration(days: 1)) and add(Duration(days: 1)) effectively make the comparison inclusive, but also include items from one day before/after the intended range. Consider using the cleaner inclusive comparison pattern:

-          if (completedDateOnly
-                  .isAfter(dateRange.start.subtract(const Duration(days: 1))) &&
-              completedDateOnly
-                  .isBefore(dateRange.end.add(const Duration(days: 1)))) {
+          if (!completedDateOnly.isBefore(dateRange.start) &&
+              !completedDateOnly.isAfter(dateRange.end)) {

386-448: generateListInsights could parallelize independent async calls.

Same optimization opportunity as in AnalyticsService.generateKeyInsights.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0be253e and 03ab16f.

📒 Files selected for processing (7)
  • .github/copilot-instructions.md (3 hunks)
  • android/app/build.gradle (1 hunks)
  • lib/screens/list_insights.dart (1 hunks)
  • lib/services/analytics_service.dart (1 hunks)
  • lib/services/list_analytics_service.dart (1 hunks)
  • pubspec.yaml (1 hunks)
  • test/unit/services/list_analytics_service_test.dart (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • pubspec.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/unit/services/list_analytics_service_test.dart
🧰 Additional context used
📓 Path-based instructions (5)
**/{Makefile,*.sh,build.gradle,pubspec.yaml}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use flutter run --release --flavor phone -d <device> for building the phone app

Files:

  • android/app/build.gradle
**/{Makefile,*.sh,build.gradle}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use flutter run --release --flavor wear -d <device> --target lib/wear/wear_main.dart for building the WearOS app

Files:

  • android/app/build.gradle
android/app/build.gradle

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

android/app/build.gradle: Use version code format XXYYYYYYY where XX=platform (30=phone, 40=wear) and YYYYYYY=versionCode for releases
Firebase flavors are defined in android/app/build.gradle with dimension 'platform' for phone and wear separation

Files:

  • android/app/build.gradle
**/*.dart

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.dart: Use snake_case for file names, PascalCase for classes, and camelCase for variables in Dart code
Use StreamBuilder<QuerySnapshot> and StreamBuilder<DocumentSnapshot> for real-time Firebase sync instead of Provider/ChangeNotifier
Use FieldValue.serverTimestamp() for createdAt/updatedAt fields in Firestore documents
Prefer SingleTickerProviderStateMixin + AnimationController for animations
Use FutureBuilder for one-time async operations like checking update status, not for ongoing streams
Use ListGroupsService for group CRUD operations and direct Firestore queries for list/item CRUD operations
Use kDebugMode checks for debug prints and conditional debugging logic

Files:

  • lib/screens/list_insights.dart
  • lib/services/list_analytics_service.dart
  • lib/services/analytics_service.dart
lib/services/**/*.dart

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

lib/services/**/*.dart: Implement all services as static utility classes without singletons or instances (e.g., ListGroupsService, CategoriesService, GoogleAuthService)
Wrap all service method errors with Sentry.captureException() including contextual hints via Hint.withMap()
Use Credential Manager on Android (v2.0.0 API) for Google Sign-In to support passkey authentication
Use google_sign_in with client ID from GoogleAuthService._webClientId for web authentication flows
Use HomeWidgetService.updateWidget() to sync shopping list data to Android home widget launcher
Call UpdateService.checkForUpdate() to trigger Android Play update flow for in-app updates

Files:

  • lib/services/list_analytics_service.dart
  • lib/services/analytics_service.dart
🧠 Learnings (16)
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to android/app/build.gradle : Use version code format `XXYYYYYYY` where XX=platform (30=phone, 40=wear) and YYYYYYY=versionCode for releases

Applied to files:

  • android/app/build.gradle
📚 Learning: 2025-12-05T14:08:58.025Z
Learnt from: aadishsamir123
Repo: ASDev-Official/shopsync PR: 147
File: android/app/build.gradle:51-58
Timestamp: 2025-12-05T14:08:58.025Z
Learning: For android/app/build.gradle in the shopsync repository: Use version code format `XXYYYYYYY` where XX=platform (30=phone, 40=wear), YYYYYYY=versionCode. Phone uses base 300000000, wear uses base 400000000.

Applied to files:

  • android/app/build.gradle
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to android/app/build.gradle : Firebase flavors are defined in `android/app/build.gradle` with dimension 'platform' for phone and wear separation

Applied to files:

  • android/app/build.gradle
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to **/{Makefile,*.sh,build.gradle,pubspec.yaml} : Use `flutter run --release --flavor phone -d <device>` for building the phone app

Applied to files:

  • android/app/build.gradle
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to **/{Makefile,*.sh,build.gradle} : Use `flutter run --release --flavor wear -d <device> --target lib/wear/wear_main.dart` for building the WearOS app

Applied to files:

  • android/app/build.gradle
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to **/*.dart : Use `FieldValue.serverTimestamp()` for `createdAt`/`updatedAt` fields in Firestore documents

Applied to files:

  • .github/copilot-instructions.md
  • lib/services/list_analytics_service.dart
  • lib/services/analytics_service.dart
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to **/*.dart : Use `StreamBuilder<QuerySnapshot>` and `StreamBuilder<DocumentSnapshot>` for real-time Firebase sync instead of Provider/ChangeNotifier

Applied to files:

  • .github/copilot-instructions.md
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to **/*.dart : Use `FutureBuilder` for one-time async operations like checking update status, not for ongoing streams

Applied to files:

  • .github/copilot-instructions.md
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to lib/wear/screens/**/*.dart : Implement WearOS UI in `lib/wear/screens/` using circular layouts with rotary scroll support via `rotary_scrollbar` package and ambient mode via `wear_plus`

Applied to files:

  • .github/copilot-instructions.md
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to **/*.dart : Use `ListGroupsService` for group CRUD operations and direct Firestore queries for list/item CRUD operations

Applied to files:

  • .github/copilot-instructions.md
  • lib/services/list_analytics_service.dart
  • lib/services/analytics_service.dart
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to **/*.dart : Prefer `SingleTickerProviderStateMixin` + `AnimationController` for animations

Applied to files:

  • .github/copilot-instructions.md
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to lib/services/**/*.dart : Wrap all service method errors with `Sentry.captureException()` including contextual hints via `Hint.withMap()`

Applied to files:

  • .github/copilot-instructions.md
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to lib/services/**/*.dart : Implement all services as static utility classes without singletons or instances (e.g., `ListGroupsService`, `CategoriesService`, `GoogleAuthService`)

Applied to files:

  • .github/copilot-instructions.md
  • lib/services/list_analytics_service.dart
  • lib/services/analytics_service.dart
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to lib/services/**/*.dart : Use `HomeWidgetService.updateWidget()` to sync shopping list data to Android home widget launcher

Applied to files:

  • .github/copilot-instructions.md
  • lib/screens/list_insights.dart
  • lib/services/analytics_service.dart
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to lib/services/**/*.dart : Use Credential Manager on Android (v2.0.0 API) for Google Sign-In to support passkey authentication

Applied to files:

  • .github/copilot-instructions.md
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to lib/main.dart : Use Sentry with 100% trace and profile sample rate as configured in `main.dart`

Applied to files:

  • .github/copilot-instructions.md
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build Phone Flavor
  • GitHub Check: Build WearOS Flavor
  • GitHub Check: Build Web (WASM)
  • GitHub Check: Build WearOS (Debug)
  • GitHub Check: Build Phone (Debug)
🔇 Additional comments (18)
android/app/build.gradle (2)

52-57: Version code calculation follows the correct platform-versioning format.

The phone flavor versionCode is correctly computed as 300000000 + 5 = 300000005, where 30 represents the phone platform and 0000005 is the padded 7-digit version code, aligning with the documented format.


53-53: Note: Version label may need alignment.

The PR is labeled semver-minor, but the version increment from 4.3.1-phone to 5.0.0-phone represents a major version bump. This is likely intentional given the source branch name includes v5.0.0, but verify the label is correct for the PR context.

Also applies to: 56-56

.github/copilot-instructions.md (2)

98-109: Loading indicators pattern is clear and well-targeted by platform.

The new section provides straightforward, platform-specific guidance with code examples for Phone/Web and WearOS, aligning with the PR's objective to standardize loading UI across platforms.


376-409: Analytics architecture documentation is comprehensive and directly addresses prior field-naming inconsistencies.

The "UPDATE COPILOT INSTRUCTIONS" directive (line 376) and "Analytics & Insights Architecture" section (lines 378–409) provide clear, actionable guidance on dual-level insights, file locations, services, and critically, the correct field-name conventions. Line 396 explicitly documents 'addedAt' for items (NOT 'createdAt') and line 397 clarifies that items lack a 'completedAt' field—resolving the inconsistency flagged in the prior review. Navigation tab order with specific index positions and animation styles (lines 403–409) is well-detailed.

All referenced files and classes exist at the documented paths:

  • lib/services/analytics_service.dart (AnalyticsService)
  • lib/services/list_analytics_service.dart (ListAnalyticsService)
  • lib/screens/user_insights.dart (UserInsightsScreen)
  • lib/screens/list_insights.dart (ListInsightsScreen)
lib/services/analytics_service.dart (4)

1-66: Data models and service structure look good.

The enums, data classes, and service declaration follow the project's static utility class pattern. ShoppingInsight, ListCompletionData, and CategoryInsight are well-structured with appropriate computed properties (e.g., completionPercentage in ListCompletionData).


171-183: Field name issue resolved; sequential queries may impact performance.

The 'completed' field is now correctly used (previously was 'checked'). However, the N+1 query pattern (fetching all lists, then querying items for each) could be slow for users with many lists. This is acceptable for analytics but worth noting for future optimization.


332-347: LGTM - Consistent pattern with correct field usage.

Uses 'completed' field correctly and follows the established query pattern.


369-437: Trend calculation methods are well-implemented.

getCollaboratorCount correctly collects unique collaborators excluding the current user. getProductivityTrend and getCompletionTrend handle edge cases (zero days/items) appropriately.

lib/screens/list_insights.dart (7)

6-28: Widget structure is well-organized.

Clean separation of required parameters (listId, listName) and internal state variables for loading status and data collections.


65-98: Build method structure is clean with proper loading state handling.

Good use of RefreshIndicator with AlwaysScrollableScrollPhysics to enable pull-to-refresh. The modular approach with separate builder methods keeps the code readable.


118-141: Time frame button implementation is straightforward.

Selection state management and data reload on change work correctly.


162-210: Key insights grid implementation looks good.

Proper use of shrinkWrap and NeverScrollableScrollPhysics for nested scrollable content. Empty state is handled appropriately.


310-354: Bar chart rendering logic is clean with proper minimum height handling.

The height < 1 ? 1 : height ensures bars are always visible even for zero values.


397-449: Category breakdown with progress indicators is well-implemented.

Proper division-by-zero guard and text overflow handling for long category names.


505-516: Empty userName crash fix is properly implemented.

The isNotEmpty check with '?' fallback correctly addresses the previously flagged RangeError issue.

lib/services/list_analytics_service.dart (3)

35-46: CategoryBreakdown.percentage calculation is now correct.

The formula correctly uses completedCount / itemCount (previously was itemCount / itemCount).


84-164: Basic item methods are well-implemented.

Consistent field usage (completed, addedAt), proper Sentry integration with contextual hints, and appropriate null-coalescing for count results.


201-254: Category breakdown implementation is clean.

Good use of record types for aggregation and proper category name lookup from the categories subcollection.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
lib/services/list_analytics_service.dart (3)

35-46: CategoryBreakdown percentage is correct; consider clamping

The completion-rate formula is now correct and guarded against division by zero. If you expect any data inconsistencies where completedCount > itemCount, consider clamping to [0, 100] for robustness:

}) : percentage = itemCount == 0
        ? 0
        : ((completedCount / itemCount) * 100).clamp(0, 100);

166-199: Consider pushing completedAt timeframe filtering into Firestore

Current implementation fetches all completed items for the list and then filters by completedAt in memory. This is fine for small lists but can become expensive as histories grow.

You can likely move the timeframe filter server‑side for better performance, e.g.:

final snapshot = await _firestore
    .collection('lists')
    .doc(listId)
    .collection('items')
    .where('completed', isEqualTo: true)
    .where('completedAt', isGreaterThanOrEqualTo: dateRange.start)
    .where('completedAt', isLessThanOrEqualTo: dateRange.end)
    .get();

return snapshot.docs.length;

This will require an appropriate composite index on completed + completedAt.


317-365: Clarify timeframe semantics for collaborator completion counts

Here the timeframe is enforced via addedAt, but itemsCompleted increments for any item that is currently completed, even if completedAt falls outside the timeframe. That effectively reports:

“Activity on items created in this timeframe,”
not
“Activity (adds + completions) that occurred in this timeframe.”

If the UX intends the latter, you may want to also bound itemsCompleted by completedAt within dateRange, similar to getItemsCompletedInTimeframe.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 03ab16f and a84376f.

📒 Files selected for processing (1)
  • lib/services/list_analytics_service.dart (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.dart

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.dart: Use snake_case for file names, PascalCase for classes, and camelCase for variables in Dart code
Use StreamBuilder<QuerySnapshot> and StreamBuilder<DocumentSnapshot> for real-time Firebase sync instead of Provider/ChangeNotifier
Use FieldValue.serverTimestamp() for createdAt/updatedAt fields in Firestore documents
Prefer SingleTickerProviderStateMixin + AnimationController for animations
Use FutureBuilder for one-time async operations like checking update status, not for ongoing streams
Use ListGroupsService for group CRUD operations and direct Firestore queries for list/item CRUD operations
Use kDebugMode checks for debug prints and conditional debugging logic

Files:

  • lib/services/list_analytics_service.dart
lib/services/**/*.dart

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

lib/services/**/*.dart: Implement all services as static utility classes without singletons or instances (e.g., ListGroupsService, CategoriesService, GoogleAuthService)
Wrap all service method errors with Sentry.captureException() including contextual hints via Hint.withMap()
Use Credential Manager on Android (v2.0.0 API) for Google Sign-In to support passkey authentication
Use google_sign_in with client ID from GoogleAuthService._webClientId for web authentication flows
Use HomeWidgetService.updateWidget() to sync shopping list data to Android home widget launcher
Call UpdateService.checkForUpdate() to trigger Android Play update flow for in-app updates

Files:

  • lib/services/list_analytics_service.dart
🧠 Learnings (3)
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to **/*.dart : Use `ListGroupsService` for group CRUD operations and direct Firestore queries for list/item CRUD operations

Applied to files:

  • lib/services/list_analytics_service.dart
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to lib/services/**/*.dart : Implement all services as static utility classes without singletons or instances (e.g., `ListGroupsService`, `CategoriesService`, `GoogleAuthService`)

Applied to files:

  • lib/services/list_analytics_service.dart
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to **/*.dart : Use `FieldValue.serverTimestamp()` for `createdAt`/`updatedAt` fields in Firestore documents

Applied to files:

  • lib/services/list_analytics_service.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Build Phone Flavor
  • GitHub Check: Build Web (WASM)
  • GitHub Check: Build WearOS Flavor
  • GitHub Check: Tests & Coverage
  • GitHub Check: Build Web (WASM)
  • GitHub Check: Build WearOS (Debug)
  • GitHub Check: Build Phone (Debug)
🔇 Additional comments (1)
lib/services/list_analytics_service.dart (1)

62-139: Service structure & error handling align with guidelines

Static utility pattern, direct Firestore access for lists/items, and consistent Sentry.captureException + Hint.withMap usage look solid and match the documented service conventions.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
lib/screens/user_insights.dart (1)

584-703: Add error handling to top categories FutureBuilder.

The FutureBuilder lacks error state handling. Errors will render the empty state instead of informing the user of the failure.

Add error handling after the loading check:

       builder: (context, snapshot) {
         if (snapshot.connectionState == ConnectionState.waiting) {
           return Container(
             height: 200,
             decoration: BoxDecoration(
               color: isDark ? Colors.grey[800] : Colors.white,
               borderRadius: BorderRadius.circular(16),
               border: Border.all(
                 color: isDark ? Colors.grey[700]! : Colors.grey[200]!,
                 width: 1,
               ),
             ),
             child: const Center(child: CustomLoadingSpinner()),
           );
         }

+        if (snapshot.hasError) {
+          debugPrint('Error loading top categories: ${snapshot.error}');
+          return Container(
+            height: 200,
+            decoration: BoxDecoration(
+              color: isDark ? Colors.grey[800] : Colors.white,
+              borderRadius: BorderRadius.circular(16),
+              border: Border.all(
+                color: isDark ? Colors.grey[700]! : Colors.grey[200]!,
+                width: 1,
+              ),
+            ),
+            child: Center(
+              child: Column(
+                mainAxisAlignment: MainAxisAlignment.center,
+                children: [
+                  Text(
+                    'Failed to load categories',
+                    style: TextStyle(
+                      color: isDark ? Colors.grey[400] : Colors.grey[600],
+                    ),
+                  ),
+                  const SizedBox(height: 8),
+                  TextButton.icon(
+                    onPressed: () => setState(() {}),
+                    icon: const Icon(Icons.refresh),
+                    label: const Text('Retry'),
+                  ),
+                ],
+              ),
+            ),
+          );
+        }
+
         final categories = snapshot.data ?? [];
🧹 Nitpick comments (1)
lib/screens/user_insights.dart (1)

51-833: Consider targeted retry for individual failed sections.

The retry buttons (e.g., line 344) call setState(() {}) which rebuilds the entire screen and re-executes all FutureBuilders, not just the failed one. This causes unnecessary network calls for sections that loaded successfully.

Consider storing individual Future variables in state and updating only the failed one:

class _UserInsightsScreenState extends State<UserInsightsScreen> {
  TimeFrame _selectedTimeFrame = TimeFrame.month;
  late Future<List<ShoppingInsight>> _insightsFuture;
  late Future<double> _completionTrendFuture;
  // ... other futures

  @override
  void initState() {
    super.initState();
    _loadData();
  }

  void _loadData() {
    _insightsFuture = AnalyticsService.generateKeyInsights(_selectedTimeFrame);
    _completionTrendFuture = AnalyticsService.getCompletionTrend(_selectedTimeFrame);
    // ... initialize other futures
  }

  void _retryCompletionTrend() {
    setState(() {
      _completionTrendFuture = AnalyticsService.getCompletionTrend(_selectedTimeFrame);
    });
  }

  // Update _loadData() when time frame changes
  void _onTimeFrameChanged(TimeFrame newFrame) {
    setState(() {
      _selectedTimeFrame = newFrame;
      _loadData();
    });
  }
}

Then use the stored futures in FutureBuilders and call specific retry methods.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a84376f and cb2c382.

📒 Files selected for processing (4)
  • .github/copilot-instructions.md (3 hunks)
  • lib/screens/list_insights.dart (1 hunks)
  • lib/screens/user_insights.dart (1 hunks)
  • lib/services/analytics_service.dart (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/screens/list_insights.dart
  • lib/services/analytics_service.dart
🧰 Additional context used
📓 Path-based instructions (1)
**/*.dart

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.dart: Use snake_case for file names, PascalCase for classes, and camelCase for variables in Dart code
Use StreamBuilder<QuerySnapshot> and StreamBuilder<DocumentSnapshot> for real-time Firebase sync instead of Provider/ChangeNotifier
Use FieldValue.serverTimestamp() for createdAt/updatedAt fields in Firestore documents
Prefer SingleTickerProviderStateMixin + AnimationController for animations
Use FutureBuilder for one-time async operations like checking update status, not for ongoing streams
Use ListGroupsService for group CRUD operations and direct Firestore queries for list/item CRUD operations
Use kDebugMode checks for debug prints and conditional debugging logic

Files:

  • lib/screens/user_insights.dart
🧠 Learnings (12)
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to lib/wear/screens/**/*.dart : Implement WearOS UI in `lib/wear/screens/` using circular layouts with rotary scroll support via `rotary_scrollbar` package and ambient mode via `wear_plus`

Applied to files:

  • lib/screens/user_insights.dart
  • .github/copilot-instructions.md
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Organize Flutter project structure with `lib/screens/` for full-page UI, `lib/widgets/` for reusable components, and `lib/services/` for business logic & Firebase interactions

Applied to files:

  • lib/screens/user_insights.dart
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to **/*.dart : Use `FutureBuilder` for one-time async operations like checking update status, not for ongoing streams

Applied to files:

  • lib/screens/user_insights.dart
  • .github/copilot-instructions.md
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to **/*.dart : Use `StreamBuilder<QuerySnapshot>` and `StreamBuilder<DocumentSnapshot>` for real-time Firebase sync instead of Provider/ChangeNotifier

Applied to files:

  • lib/screens/user_insights.dart
  • .github/copilot-instructions.md
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to **/*.dart : Use `FieldValue.serverTimestamp()` for `createdAt`/`updatedAt` fields in Firestore documents

Applied to files:

  • .github/copilot-instructions.md
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to **/*.dart : Use `ListGroupsService` for group CRUD operations and direct Firestore queries for list/item CRUD operations

Applied to files:

  • .github/copilot-instructions.md
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to **/*.dart : Prefer `SingleTickerProviderStateMixin` + `AnimationController` for animations

Applied to files:

  • .github/copilot-instructions.md
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to lib/services/**/*.dart : Wrap all service method errors with `Sentry.captureException()` including contextual hints via `Hint.withMap()`

Applied to files:

  • .github/copilot-instructions.md
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to lib/services/**/*.dart : Implement all services as static utility classes without singletons or instances (e.g., `ListGroupsService`, `CategoriesService`, `GoogleAuthService`)

Applied to files:

  • .github/copilot-instructions.md
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to lib/services/**/*.dart : Use `HomeWidgetService.updateWidget()` to sync shopping list data to Android home widget launcher

Applied to files:

  • .github/copilot-instructions.md
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to lib/services/**/*.dart : Use Credential Manager on Android (v2.0.0 API) for Google Sign-In to support passkey authentication

Applied to files:

  • .github/copilot-instructions.md
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to lib/main.dart : Use Sentry with 100% trace and profile sample rate as configured in `main.dart`

Applied to files:

  • .github/copilot-instructions.md
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Build Web (WASM)
  • GitHub Check: Build Phone Flavor
  • GitHub Check: Tests & Coverage
  • GitHub Check: Build WearOS Flavor
  • GitHub Check: Build WearOS (Debug)
  • GitHub Check: Build Web (WASM)
  • GitHub Check: Build Phone (Debug)
🔇 Additional comments (5)
.github/copilot-instructions.md (4)

393-409: Excellent: Explicit field name documentation prevents prior issue recurrence.

The addition of the "Analytics & Insights Architecture" section with explicit field name mappings (lines 393–409) directly addresses the prior review's feedback on createdAt vs addedAt inconsistency. By documenting that items use 'addedAt' (not 'createdAt'), have no 'completedAt' field, and that deleted items live in recycled_items subcollection, you've created a clear reference that should prevent similar field-name mix-ups in future analytics implementations.

The tab order specification (Items → Insights → Options, lines 403–408) and animation cues (donut spin with Icons.donut_small → Icons.donut_large) are actionable and will help maintain consistency as the feature scales.

Verify that lib/services/list_analytics_service.dart and lib/services/analytics_service.dart (referenced but not provided) strictly follow these field name conventions throughout their query and aggregation logic.


98-108: Clear platform-specific loading indicator guidance.

The new "Loading Indicators" section provides concrete guidance on when to use CustomLoadingSpinner() (Phone/Web) vs CircularProgressIndicator() (WearOS), with code examples. This should reduce inconsistency across platforms as the codebase evolves.

Ensure that the migration from CircularProgressIndicator to CustomLoadingSpinner (mentioned in the PR summary) is complete across all phone/web screens, and that no stray CircularProgressIndicator instances remain where CustomLoadingSpinner should be used.


376-377: "UPDATE COPILOT INSTRUCTIONS" directive sets clear expectation.

Making it explicit that new features require copilot-instructions.md updates will help keep guidance synchronized with implementation and reduce gaps in future architectural decisions.


410-437: Testing Responsibilities expanded with analytics-specific guidance.

The expanded section now includes expectations around analytics documentation (line 414) and testing field names/logic (implied by context). Lines 422–429 ("Test Writing Quick Rules") are practical and align with the AAA pattern emphasized elsewhere.

lib/screens/user_insights.dart (1)

51-72: Error handling properly implemented.

The previous review concern about missing error state handling has been addressed. The FutureBuilder now correctly handles snapshot.hasError and displays an appropriate error state.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
test/unit/services/analytics_service_test.dart (1)

68-72: Placeholder test doesn't validate generateKeyInsights behavior.

Similar to the earlier service method tests, this validates the type but doesn't call generateKeyInsights. Consider using Firebase mocking to test that the method generates insights based on actual user data.

🧹 Nitpick comments (5)
lib/screens/user_insights.dart (1)

327-328: Gate debugPrint calls behind kDebugMode as per logging guidelines

Multiple error branches log using debugPrint unconditionally:

  • Line 327–328: completion trend
  • Line 437–438: productivity trend
  • Line 533–535: items-per-day trend
  • Line 784–785: list completions

Project guidelines call for wrapping debug logging with kDebugMode. You can centralize this by importing kDebugMode and guarding the logs:

-import 'package:flutter/material.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/foundation.dart';

Then update the logs, for example:

-              if (snapshot.hasError) {
-                debugPrint('Error loading completion trend: ${snapshot.error}');
+              if (snapshot.hasError) {
+                if (kDebugMode) {
+                  debugPrint(
+                    'Error loading completion trend: ${snapshot.error}',
+                  );
+                }
                 return SizedBox(

Apply the same if (kDebugMode) guard pattern to the other debugPrint usages in this file.

Also applies to: 437-438, 533-535, 784-785

test/unit/services/analytics_service_test.dart (4)

10-33: Consider using Firebase mocking to test actual service methods.

These tests validate basic Dart types but don't invoke the actual AnalyticsService methods, limiting their value for catching regressions. To improve coverage, consider using fake_cloud_firestore or mockito to mock Firebase and verify that the service methods return expected results with test data.

For example:

test('getTotalListsCreated returns correct count with test data', () async {
  // Arrange: set up fake Firestore with test lists
  final fakeFirestore = FakeFirebaseFirestore();
  // ... populate test data
  
  // Act
  final result = await AnalyticsService.getTotalListsCreated(userId, TimeFrame.week);
  
  // Assert
  expect(result, expectedCount);
});

35-42: Use consistent date precision in week test.

The test compares weekAgo (derived from startOfDay without time component) with now (includes current time), leading to inconsistent precision. Additionally, this test doesn't call _getDateRange (which is private).

Consider using a deterministic test date for both values, similar to the month test at lines 44-51:

-test('_getDateRange week returns correct date range', () {
-  final now = DateTime.now();
-  final startOfDay = DateTime(now.year, now.month, now.day);
-  final weekAgo = startOfDay.subtract(const Duration(days: 7));
-
-  // Verify the week range logic
-  expect(weekAgo.difference(now).inDays, lessThanOrEqualTo(-7));
-});
+test('_getDateRange week returns correct date range', () {
+  final testDate = DateTime(2024, 3, 15);
+  final weekAgo = DateTime(2024, 3, 8);
+
+  // Verify the week range logic
+  expect(weekAgo.difference(testDate).inDays, equals(-7));
+});

53-66: Date range tests don't invoke the actual _getDateRange method.

These tests validate date arithmetic but not the actual _getDateRange behavior. Since _getDateRange is private, consider testing it indirectly through public methods that use it, or refactor to make date range logic testable with dependency injection.

Also, lines 61-66 just verify that DateTime(2020).year equals 2020, which is trivial—consider removing or replacing with a test of a public method that uses the all-time range.


74-227: Excellent coverage of data models and edge cases.

The tests for ChartData, ListCompletionData, CategoryInsight, ShoppingInsight, DateRange, and enum types are well-structured and cover important edge cases (zero items, 100% completion, same start/end dates, etc.). These tests provide solid validation of the analytics data models.

Minor note: Lines 152-162 explicitly set color: null, which is redundant if color is an optional parameter with a null default. You can omit the parameter entirely:

 test('ShoppingInsight with color', () {
   final insight = ShoppingInsight(
     title: 'Test',
     value: '10',
     subtitle: 'items',
     iconType: IconValue.shoppingCart,
-    color: null,
   );

   expect(insight.color, isNull);
 });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cb2c382 and fe3b3a9.

📒 Files selected for processing (3)
  • lib/screens/user_insights.dart (1 hunks)
  • lib/services/analytics_service.dart (1 hunks)
  • test/unit/services/analytics_service_test.dart (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/services/analytics_service.dart
🧰 Additional context used
📓 Path-based instructions (1)
**/*.dart

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.dart: Use snake_case for file names, PascalCase for classes, and camelCase for variables in Dart code
Use StreamBuilder<QuerySnapshot> and StreamBuilder<DocumentSnapshot> for real-time Firebase sync instead of Provider/ChangeNotifier
Use FieldValue.serverTimestamp() for createdAt/updatedAt fields in Firestore documents
Prefer SingleTickerProviderStateMixin + AnimationController for animations
Use FutureBuilder for one-time async operations like checking update status, not for ongoing streams
Use ListGroupsService for group CRUD operations and direct Firestore queries for list/item CRUD operations
Use kDebugMode checks for debug prints and conditional debugging logic

Files:

  • lib/screens/user_insights.dart
  • test/unit/services/analytics_service_test.dart
🧠 Learnings (4)
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Organize Flutter project structure with `lib/screens/` for full-page UI, `lib/widgets/` for reusable components, and `lib/services/` for business logic & Firebase interactions

Applied to files:

  • lib/screens/user_insights.dart
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to lib/wear/screens/**/*.dart : Implement WearOS UI in `lib/wear/screens/` using circular layouts with rotary scroll support via `rotary_scrollbar` package and ambient mode via `wear_plus`

Applied to files:

  • lib/screens/user_insights.dart
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to **/*.dart : Use `FutureBuilder` for one-time async operations like checking update status, not for ongoing streams

Applied to files:

  • lib/screens/user_insights.dart
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to **/*.dart : Use `StreamBuilder<QuerySnapshot>` and `StreamBuilder<DocumentSnapshot>` for real-time Firebase sync instead of Provider/ChangeNotifier

Applied to files:

  • lib/screens/user_insights.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Build Web (WASM)
  • GitHub Check: Tests & Coverage
  • GitHub Check: Build Phone Flavor
  • GitHub Check: Build WearOS Flavor
  • GitHub Check: Build Phone (Debug)
  • GitHub Check: Build Web (WASM)
  • GitHub Check: Build WearOS (Debug)
🔇 Additional comments (2)
lib/screens/user_insights.dart (1)

7-146: Overall screen structure and async patterns look solid

State management, time-frame selection, use of FutureBuilder for one-shot analytics calls, and loading/empty/error handling in most sections are all consistent and well-structured. The layout composition and dark/light theming are coherent and match the documented architecture for insights.

test/unit/services/analytics_service_test.dart (1)

44-51: Good fix for the month boundary edge case.

Using deterministic dates (DateTime(2024, 3, 15) and DateTime(2024, 2, 15)) correctly addresses the previous review concern about day overflow when calculating month boundaries. This approach ensures the test is stable regardless of when it runs.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
lib/screens/user_insights.dart (1)

644-763: Add error handling to top categories FutureBuilder.

The _buildTopCategoriesCard FutureBuilder still lacks error state handling. When AnalyticsService.getMostUsedCategories throws an exception, the code treats it as empty data (line 663-666), showing "No data available" instead of an informative error message with retry capability. This is inconsistent with the other insight cards that have explicit error handling.

Add error handling after the loading check:

       builder: (context, snapshot) {
         if (snapshot.connectionState == ConnectionState.waiting) {
           return Container(
             height: 200,
             decoration: BoxDecoration(
               color: isDark ? Colors.grey[800] : Colors.white,
               borderRadius: BorderRadius.circular(16),
               border: Border.all(
                 color: isDark ? Colors.grey[700]! : Colors.grey[200]!,
                 width: 1,
               ),
             ),
             child: const Center(child: CustomLoadingSpinner()),
           );
         }
+
+        if (snapshot.hasError) {
+          debugPrint('Error loading top categories: ${snapshot.error}');
+          return Container(
+            height: 200,
+            decoration: BoxDecoration(
+              color: isDark ? Colors.grey[800] : Colors.white,
+              borderRadius: BorderRadius.circular(16),
+              border: Border.all(
+                color: isDark ? Colors.grey[700]! : Colors.grey[200]!,
+                width: 1,
+              ),
+            ),
+            child: Center(
+              child: Column(
+                mainAxisAlignment: MainAxisAlignment.center,
+                children: [
+                  Text(
+                    'Failed to load category data',
+                    style: TextStyle(
+                      color: isDark ? Colors.grey[400] : Colors.grey[600],
+                    ),
+                  ),
+                  const SizedBox(height: 8),
+                  TextButton.icon(
+                    onPressed: () => setState(() {}),
+                    icon: const Icon(Icons.refresh),
+                    label: const Text('Retry'),
+                  ),
+                ],
+              ),
+            ),
+          );
+        }
 
         final categories = snapshot.data ?? [];
🧹 Nitpick comments (3)
lib/screens/user_insights.dart (3)

328-328: Wrap debug prints in kDebugMode checks per coding guidelines.

The coding guidelines require kDebugMode checks for debug prints. While debugPrint is automatically stripped in release builds, explicitly guarding these calls aligns with the project's conventions.

Add the import at the top of the file:

import 'package:flutter/foundation.dart';

Then wrap the debug prints:

-              if (snapshot.hasError) {
-                debugPrint('Error loading completion trend: ${snapshot.error}');
+              if (snapshot.hasError) {
+                if (kDebugMode) {
+                  debugPrint('Error loading completion trend: ${snapshot.error}');
+                }

Apply similar changes to lines 437-438, 534, and 785.

Based on coding guidelines, which specify using kDebugMode checks for debug prints.

Also applies to: 437-438, 534-534, 785-785


561-569: Consider using _buildEmptyState() helper for consistency.

The inline empty state could use the existing _buildEmptyState() helper (lines 932-973) for consistency with other sections of the screen.

Replace with:

               final data = snapshot.data ?? [];
               if (data.isEmpty) {
-                return Center(
-                  child: Text(
-                    'No data available',
-                    style: TextStyle(
-                      color: isDark ? Colors.grey[400] : Colors.grey[600],
-                    ),
-                  ),
-                );
+                return _buildEmptyState();
               }

591-593: Remove redundant isEmpty check.

The data.isEmpty check at line 591 is redundant since the method already returns early at line 580 if data is empty.

Simplify to:

-    final maxValue = data.isEmpty
-        ? 1
-        : data.map((e) => e.value).reduce((a, b) => a > b ? a : b);
+    final maxValue = data.map((e) => e.value).reduce((a, b) => a > b ? a : b);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fe3b3a9 and b40c93f.

📒 Files selected for processing (1)
  • lib/screens/user_insights.dart (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.dart

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.dart: Use snake_case for file names, PascalCase for classes, and camelCase for variables in Dart code
Use StreamBuilder<QuerySnapshot> and StreamBuilder<DocumentSnapshot> for real-time Firebase sync instead of Provider/ChangeNotifier
Use FieldValue.serverTimestamp() for createdAt/updatedAt fields in Firestore documents
Prefer SingleTickerProviderStateMixin + AnimationController for animations
Use FutureBuilder for one-time async operations like checking update status, not for ongoing streams
Use ListGroupsService for group CRUD operations and direct Firestore queries for list/item CRUD operations
Use kDebugMode checks for debug prints and conditional debugging logic

Files:

  • lib/screens/user_insights.dart
🧠 Learnings (3)
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Organize Flutter project structure with `lib/screens/` for full-page UI, `lib/widgets/` for reusable components, and `lib/services/` for business logic & Firebase interactions

Applied to files:

  • lib/screens/user_insights.dart
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to **/*.dart : Use `FutureBuilder` for one-time async operations like checking update status, not for ongoing streams

Applied to files:

  • lib/screens/user_insights.dart
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to **/*.dart : Use `StreamBuilder<QuerySnapshot>` and `StreamBuilder<DocumentSnapshot>` for real-time Firebase sync instead of Provider/ChangeNotifier

Applied to files:

  • lib/screens/user_insights.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Build Web (WASM)
  • GitHub Check: Build Phone Flavor
  • GitHub Check: Build WearOS Flavor
  • GitHub Check: Tests & Coverage
  • GitHub Check: Build WearOS (Debug)
  • GitHub Check: Build Web (WASM)
  • GitHub Check: Build Phone (Debug)

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
lib/screens/user_insights.dart (3)

327-328: Use kDebugMode check for debug prints.

Per coding guidelines, debug prints should be wrapped with kDebugMode checks to ensure they don't execute in release builds.

+import 'package:flutter/foundation.dart';
+
 if (snapshot.hasError) {
-  debugPrint('Error loading completion trend: ${snapshot.error}');
+  if (kDebugMode) {
+    debugPrint('Error loading completion trend: ${snapshot.error}');
+  }

This pattern should be applied to all debugPrint calls in this file (lines 328, 437-438, 534, 664, 819). Based on coding guidelines requiring kDebugMode checks for debug prints.


579-594: Redundant empty check after early return.

Line 591 checks data.isEmpty again, but the early return at line 580 already handles this case. The ternary will always take the non-empty branch.

-  final maxValue = data.isEmpty
-      ? 1
-      : data.map((e) => e.value).reduce((a, b) => a > b ? a : b);
+  final maxValue = data.map((e) => e.value).reduce((a, b) => a > b ? a : b);

Alternatively, consider using fold or importing dart:math for max:

final maxValue = data.map((e) => e.value).fold(1.0, max);

1009-1050: Inconsistent error state: missing retry button.

The _buildErrorState() method lacks a retry button, unlike the inline error states in other FutureBuilders (e.g., lines 343-347, 452-457, 548-552). Users encountering errors in the key insights section have no way to retry without navigating away.

 Widget _buildErrorState() {
   final isDark = Theme.of(context).brightness == Brightness.dark;
   return Container(
     padding: const EdgeInsets.all(32),
     decoration: BoxDecoration(
       color: isDark ? Colors.grey[800] : Colors.white,
       borderRadius: BorderRadius.circular(16),
       border: Border.all(
         color: isDark ? Colors.grey[700]! : Colors.grey[200]!,
         width: 1,
       ),
     ),
     child: Center(
       child: Column(
         mainAxisSize: MainAxisSize.min,
         children: [
           Icon(
-            Icons.analytics_outlined,
+            Icons.error_outline,
             size: 48,
             color: isDark ? Colors.grey[600] : Colors.grey[400],
           ),
           const SizedBox(height: 16),
           Text(
             'An error occurred',
             style: TextStyle(
               fontSize: 16,
               color: isDark ? Colors.grey[400] : Colors.grey[600],
             ),
           ),
           const SizedBox(height: 8),
           Text(
             'Please try again later',
             style: TextStyle(
               fontSize: 12,
               color: isDark ? Colors.grey[500] : Colors.grey[500],
             ),
           ),
+          const SizedBox(height: 16),
+          TextButton.icon(
+            onPressed: () => setState(() {}),
+            icon: const Icon(Icons.refresh),
+            label: const Text('Retry'),
+          ),
         ],
       ),
     ),
   );
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b40c93f and 7aa08b0.

📒 Files selected for processing (1)
  • lib/screens/user_insights.dart (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.dart

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.dart: Use snake_case for file names, PascalCase for classes, and camelCase for variables in Dart code
Use StreamBuilder<QuerySnapshot> and StreamBuilder<DocumentSnapshot> for real-time Firebase sync instead of Provider/ChangeNotifier
Use FieldValue.serverTimestamp() for createdAt/updatedAt fields in Firestore documents
Prefer SingleTickerProviderStateMixin + AnimationController for animations
Use FutureBuilder for one-time async operations like checking update status, not for ongoing streams
Use ListGroupsService for group CRUD operations and direct Firestore queries for list/item CRUD operations
Use kDebugMode checks for debug prints and conditional debugging logic

Files:

  • lib/screens/user_insights.dart
🧠 Learnings (3)
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Organize Flutter project structure with `lib/screens/` for full-page UI, `lib/widgets/` for reusable components, and `lib/services/` for business logic & Firebase interactions

Applied to files:

  • lib/screens/user_insights.dart
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to **/*.dart : Use `FutureBuilder` for one-time async operations like checking update status, not for ongoing streams

Applied to files:

  • lib/screens/user_insights.dart
📚 Learning: 2025-12-05T14:59:55.596Z
Learnt from: CR
Repo: ASDev-Official/shopsync PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-05T14:59:55.596Z
Learning: Applies to **/*.dart : Use `StreamBuilder<QuerySnapshot>` and `StreamBuilder<DocumentSnapshot>` for real-time Firebase sync instead of Provider/ChangeNotifier

Applied to files:

  • lib/screens/user_insights.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build Web (WASM)
  • GitHub Check: Build WearOS Flavor
  • GitHub Check: Build Phone Flavor
  • GitHub Check: Build Phone (Debug)
  • GitHub Check: Build WearOS (Debug)
🔇 Additional comments (7)
lib/screens/user_insights.dart (7)

1-12: LGTM!

File naming (snake_case), class naming (PascalCase), and import structure follow Flutter conventions and coding guidelines.


14-146: Well-structured screen layout with appropriate async patterns.

Good use of FutureBuilder for one-time async data fetching, consistent with coding guidelines. The theme-aware styling and modular section composition are well organized.


148-205: Clean time frame selector implementation.

The horizontal scrollable selector with proper Material ripple effects and theme-aware styling is well implemented.


207-287: LGTM!

Grid layout for insight cards is correctly configured with shrinkWrap and NeverScrollableScrollPhysics for proper scrolling behavior within the parent SingleChildScrollView.


644-797: LGTM!

Error handling, loading states, and empty state handling are properly implemented. Theme-aware styling is consistent with other cards.


799-964: LGTM!

List completion card properly handles all states (loading, error, empty, data). Theme-aware colors are correctly applied to the completion percentage badge.


1052-1092: LGTM!

Icon utility methods provide clean mappings from IconValue enum to colors and icon data. All enum cases are covered.

Copy link
Copy Markdown
Member Author

@aadishsamir123 aadishsamir123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm(the build failures are failing because the actions runners dont have enough storage space)

@aadishsamir123 aadishsamir123 merged commit 4d989a2 into main Dec 11, 2025
20 of 29 checks passed
@aadishsamir123 aadishsamir123 deleted the v5.0.0-phone-list-insights branch December 11, 2025 02:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app:phone Related to ShopSync Phone/Web app enhancement New feature or request semver-major Major updates

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant