Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Fundraising Campaigns (GSoC) #2635

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from

Conversation

Dante291
Copy link

@Dante291 Dante291 commented Nov 4, 2024

What kind of change does this PR introduce?

Adds supports for fund raising campaigns in Talawa mobile app

Issue Number:

Fixes #2593

Did you add tests for your changes?

Yes

Snapshots/Videos:

funds.mp4

If relevant, did you update the documentation?

Yes

Summary

Does this PR introduce a breaking change?

Yes

Have you read the contributing guide?

Yes

Summary by CodeRabbit

  • New Features

    • Introduced fund management capabilities with the addition of funds, campaigns, and pledges functionalities.
    • Added a user interface for managing pledges associated with campaigns, including search and sort options.
    • Implemented a dialog for adding and updating pledges, enhancing user interaction.
    • Integrated a new FundViewModel for managing fund-related data and interactions.
  • Bug Fixes

    • Improved error handling for image decoding and data fetching processes.
  • Documentation

    • Added comprehensive documentation for new classes and methods to enhance code readability.
  • Tests

    • Established a suite of unit tests for FundService and FundViewModel to ensure functionality and reliability.

Copy link

coderabbitai bot commented Nov 4, 2024

Walkthrough

The changes in this pull request introduce a comprehensive set of features related to fund management within the application. Key modifications include the addition of new classes for Fund, Campaign, and Pledge, as well as the integration of a FundService and FundViewModel for managing fund-related operations. The locator.dart file has been updated to register these services for dependency injection. Furthermore, several UI components have been implemented to facilitate user interactions with funds and pledges, including screens for managing campaigns and pledges.

Changes

File Path Change Summary
lib/locator.dart Added imports for FundService and FundViewModel, registered them in setupLocator.
lib/models/funds/fund.dart Introduced Fund class with properties and a factory method for JSON deserialization.
lib/models/funds/fund_campaign.dart Introduced Campaign class with properties and a factory method for JSON deserialization.
lib/models/funds/fund_pledges.dart Introduced Pledge class with properties and a factory method for JSON deserialization.
lib/services/fund_service.dart Implemented FundService class with methods for managing funds, campaigns, and pledges.
lib/services/image_service.dart Added methods for decoding base64 images and MIME type detection.
lib/utils/fund_queries.dart Introduced FundQueries class for constructing GraphQL queries and mutations related to funds.
lib/view_model/after_auth_view_models/funds_view_models/fund_view_model.dart Added FundViewModel class to manage fund-related data and operations.
lib/views/after_auth_screens/funds/fund_pledges_screen.dart Implemented PledgesScreen for managing campaign pledges.
lib/views/after_auth_screens/funds/fundraising_campaigns_screen.dart Implemented CampaignsScreen for displaying fundraising campaigns.
lib/widgets/add_pledge_dialogue_box.dart Introduced AddPledgeDialog for creating pledges.
lib/widgets/update_pledge_dialogue_box.dart Introduced UpdatePledgeDialog for updating pledges.
test/model_tests/funds/fund_campaign_test.dart Added tests for the Campaign model's fromJson method.
test/model_tests/funds/fund_pledges_test.dart Added tests for the Pledge model's fromJson method.
test/model_tests/funds/fund_test.dart Added tests for the Fund model's fromJson method.
test/service_tests/fund_service_test.dart Introduced tests for the FundService class methods.
test/utils/fund_queries_test.dart Added tests for the FundQueries class methods.
.github/workflows/pull-request.yml Modified workflow to exclude specific files from line counting.

Assessment against linked issues

Objective Addressed Explanation
Feature for making pledges/donations (#2593)

Possibly related PRs

  • Timezone in App #2581: This PR introduces timezone handling in the application, which is relevant to the main PR's changes in lib/locator.dart that involve the integration of FundService, as both deal with data management and service integration in the context of time-sensitive operations.

Suggested reviewers

  • palisadoes
  • noman2002

🐇 In the fields where funds grow,
A pledge to help, a heart to show.
With campaigns bright and dreams to chase,
Together we can make a place!
So hop along, let’s raise the cheer,
For every pledge, we hold so dear! 🐇


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

github-actions bot commented Nov 4, 2024

Our Pull Request Approval Process

We have these basic policies to make the approval process smoother for our volunteer team.

Testing Your Code

Please make sure your code passes all tests. Our test code coverage system will fail if either of these two conditions occur:

  1. The overall code coverage drops below the target threshold of the repository
  2. Any file in the pull request has code coverage levels below the repository threshold

The process helps maintain the overall reliability of the code base and is a prerequisite for getting your PR approved. Assigned reviewers regularly review the PR queue and tend to focus on PRs that are passing.

Reviewers

Do not assign reviewers. Our Queue Monitors will review your PR and assign them.
When your PR has been assigned reviewers contact them to get your code reviewed and approved via:

  1. comments in this PR or
  2. our slack channel

Reviewing Your Code

Your reviewer(s) will have the following roles:

  1. arbitrators of future discussions with other contributors about the validity of your changes
  2. point of contact for evaluating the validity of your work
  3. person who verifies matching issues by others that should be closed.
  4. person who gives general guidance in fixing your tests

Other

🎯 Please be considerate of our volunteers' time. Contacting the person who assigned the reviewers is not advised unless they ask for your input. Do not @ the person who did the assignment otherwise.

Copy link

@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: 56

🧹 Outside diff range and nitpick comments (51)
test/model_tests/funds/fund_test.dart (3)

8-23: Add missing field to test data.

The 'referenceNumber' field is tested in the null case but missing in the primary test case. Consider adding it to ensure consistent test coverage.

       final fundJson = {
         '_id': 'fund1',
         'organizationId': 'org1',
         'name': 'General Fund',
+        'referenceNumber': 'REF001',
         'taxDeductible': true,

7-38: Add test cases for date parsing edge cases.

The current test only verifies successful date parsing. Consider adding test cases for invalid date formats to ensure robust error handling.

Would you like me to help create additional test cases for date parsing scenarios?


40-67: Consider adding more edge cases to null testing.

While the current null test is good, consider adding:

  1. Partial null scenarios (some fields null, others populated)
  2. Empty string vs null handling
  3. Missing fields vs null fields

This would provide more comprehensive coverage of real-world scenarios.

test/model_tests/funds/fund_campaign_test.dart (2)

8-19: Consider using dynamic dates and additional test cases.

The test data could be improved in several ways:

  1. Instead of hardcoding future dates (2024), consider using DateTime.now() with relative offsets
  2. The pledge creation Pledge(id: 'pledge1') might not match the actual JSON structure from the API

Consider this approach:

 final campaignJson = {
   '_id': 'campaign1',
   'fundId': 'fund1',
   'name': 'Annual Fundraiser',
-  'startDate': '2024-01-01T00:00:00Z',
-  'endDate': '2024-12-31T23:59:59Z',
+  'startDate': DateTime.now().toIso8601String(),
+  'endDate': DateTime.now().add(Duration(days: 365)).toIso8601String(),
   'fundingGoal': 10000.0,
   'currency': 'USD',
-  'pledges': [Pledge(id: 'pledge1')],
+  'pledges': [{'_id': 'pledge1', 'amount': 100}],
   'createdAt': '2024-01-01T00:00:00Z',
   'updatedAt': '2024-01-02T00:00:00Z',
 };

36-62: Enhance null value testing coverage.

While testing all-null values is good, consider adding:

  1. Test cases with partial null values (mix of null and valid values)
  2. Validation for required fields (if any fields are mandatory in the Campaign model)

Example additional test:

test('Test Campaign fromJson with partial null values', () {
  final campaignJson = {
    '_id': 'campaign1',
    'fundId': 'fund1',
    'name': 'Test Campaign',
    'startDate': null,  // Optional field
    'endDate': null,    // Optional field
    'fundingGoal': 1000.0,
    'currency': 'USD',
    'pledges': null,    // Optional field
    'createdAt': DateTime.now().toIso8601String(),
    'updatedAt': DateTime.now().toIso8601String(),
  };

  final campaign = Campaign.fromJson(campaignJson);
  
  expect(campaign.id, 'campaign1');
  expect(campaign.startDate, isNull);
  expect(campaign.endDate, isNull);
  expect(campaign.fundingGoal, 1000.0);
  expect(campaign.pledges, isNull);
});
test/model_tests/funds/fund_pledges_test.dart (2)

32-53: Enhance test coverage with edge cases.

While the basic assertions are good, the test coverage could be improved by:

  1. Testing DateTime parsing with invalid formats
  2. Validating required vs optional fields
  3. Testing empty lists for campaigns and pledgers

Consider adding these test cases:

test('fromJson should handle invalid date formats', () {
  final jsonData = {
    '_id': 'pledge123',
    'startDate': 'invalid-date',
    // ... other required fields
  };
  
  expect(
    () => Pledge.fromJson(jsonData),
    throwsA(isA<FormatException>()),
  );
});

test('fromJson should handle empty lists', () {
  final jsonData = {
    '_id': 'pledge123',
    'campaigns': [],
    'users': [],
    // ... other required fields
  };
  
  final pledge = Pledge.fromJson(jsonData);
  expect(pledge.campaigns, isEmpty);
  expect(pledge.pledgers, isEmpty);
});

54-81: Strengthen null handling tests.

While the current null handling test is good, consider these improvements:

  1. Test partial null scenarios (some fields null, others valid)
  2. Validate behavior for required fields (if any fields shouldn't accept null)

Add a test case for partial null handling:

test('fromJson should handle partial null values', () {
  final jsonData = {
    '_id': 'pledge123',
    'campaigns': null,
    'users': [
      {'firstName': 'Alice'}
    ],
    'startDate': '2024-01-01T00:00:00.000Z',
    'endDate': null,
    // ... mix of null and non-null values
  };
  
  final pledge = Pledge.fromJson(jsonData);
  expect(pledge.id, isNotNull);
  expect(pledge.campaigns, isNull);
  expect(pledge.pledgers, isNotNull);
  expect(pledge.startDate, isNotNull);
  expect(pledge.endDate, isNull);
});
lib/models/funds/fund_pledges.dart (3)

4-5: Enhance class documentation for better clarity.

The class documentation should be more comprehensive, including:

  • The purpose and role of pledges in fundraising campaigns
  • The relationship between pledges, campaigns, and users
  • Any important business rules or constraints

Example enhancement:

-/// The `Pledge` class represents a pledge for a fundraising campaign in the application.
+/// The `Pledge` class represents a financial commitment made by users (pledgers) 
+/// towards one or more fundraising campaigns in the application.
+///
+/// A pledge tracks the promised amount, timeline (start and end dates), and maintains
+/// relationships with both the campaigns it supports and the users who made the pledge.
+/// The amount is stored with its currency to support international fundraising efforts.

64-90: Add date validation and improve field documentation.

  1. Add validation to ensure endDate is after startDate
  2. Enhance field documentation with more context and constraints

Example improvements for documentation and validation:

+  /// Validates that the pledge dates are in correct order.
+  void _validateDates() {
+    if (startDate != null && endDate != null && endDate!.isBefore(startDate!)) {
+      throw ArgumentError('End date must be after start date');
+    }
+  }

-  /// The amount pledged.
+  /// The amount pledged in the smallest currency unit (e.g., cents for USD).
+  /// Must be a positive integer.
   final int? amount;

-  /// The currency of the pledged amount.
+  /// The currency of the pledged amount in ISO 4217 format (e.g., 'USD', 'EUR').
   final String? currency;

Consider adding this validation to the constructor:

   Pledge({/*...*/}) {
+    _validateDates();
   }

1-90: Consider adding essential model capabilities.

For a robust model class that handles financial data, consider adding:

  1. toJson() method for API requests
  2. toString() method for debugging
  3. operator == and hashCode for equality comparison
  4. copyWith() method for immutable updates

These additions will make the model more maintainable and easier to use in the application. Would you like me to provide implementations for these methods?

lib/models/funds/fund_campaign.dart (3)

3-4: Enhance class documentation with more context.

While the documentation is present, it would be more helpful to include:

  • The role of campaigns in the broader fundraising system
  • The relationship between campaigns, funds, and pledges
  • Any business rules or constraints (e.g., date validations, funding goal requirements)
-/// The `Campaign` class represents a fundraising campaign in the application.
+/// The `Campaign` class represents a fundraising campaign within the application's
+/// fundraising system. A campaign belongs to a specific fund and can receive multiple
+/// pledges from users. Each campaign has a defined timeline (start and end dates) and
+/// a funding goal in a specified currency.
+///
+/// Campaigns are used to organize and track fundraising efforts, allowing users to
+/// contribute pledges towards specific causes or projects.

61-89: Enhance property documentation with format specifications.

The documentation could be more specific about:

  • Format and constraints for id and fundId
  • Expected currency format (e.g., ISO 4217)
  • Date format expectations
-  /// The unique identifier of the campaign.
+  /// The unique identifier of the campaign in MongoDB ObjectId format.
   final String? id;

-  /// The identifier of the fund to which the campaign belongs.
+  /// The MongoDB ObjectId of the fund to which this campaign belongs.
   final String? fundId;

-  /// The currency used for the campaign.
+  /// The ISO 4217 currency code used for the campaign (e.g., 'USD', 'EUR').
   final String? currency;

4-90: Consider adding business logic methods to the Campaign model.

The current implementation is purely data-focused. Consider adding methods for common operations:

  • Calculating total pledged amount
  • Checking if campaign is active
  • Validating if new pledges can be accepted
  • Computing campaign progress percentage

This would help encapsulate business logic and promote code reuse.

Example additions:

class Campaign {
  // ... existing code ...

  /// Returns true if the campaign is currently active
  bool isActive() {
    final now = DateTime.now();
    return startDate != null && 
           endDate != null && 
           now.isAfter(startDate!) && 
           now.isBefore(endDate!);
  }

  /// Calculates the total amount pledged
  double getTotalPledged() {
    return pledges?.fold(0.0, (sum, pledge) => sum + (pledge.amount ?? 0.0)) ?? 0.0;
  }

  /// Returns the campaign progress as a percentage
  double getProgressPercentage() {
    if (fundingGoal == null || fundingGoal == 0) return 0.0;
    return (getTotalPledged() / fundingGoal!) * 100;
  }
}
lib/models/funds/fund.dart (1)

4-91: Consider adding validation and data consistency measures

As this model is central to the fundraising feature:

  1. Consider adding validation rules (e.g., minimum/maximum amounts, valid date ranges)
  2. Implement toJson() method for serialization
  3. Consider adding methods to validate fund state (e.g., isValid(), canAcceptDonations())
  4. Add proper error handling for invalid data scenarios
lib/views/after_auth_screens/events/manage_agenda_items_screen.dart (3)

63-63: Consider optimizing the refresh mechanism.

The addition of await model.fetchAgendaItems(); after deletion creates a double network call scenario. The deletion operation likely returns the updated list of items, which could be utilized directly.

Consider these improvements:

- await model.deleteAgendaItem(item.id!);
- await model.fetchAgendaItems();
+ final updatedItems = await model.deleteAgendaItem(item.id!);
+ model.updateLocalItems(updatedItems); // Add this method to your view model

Line range hint 63-89: Enhance error handling and user feedback.

The current implementation assumes the deletion and refresh operations always succeed. Consider adding error handling and appropriate user feedback.

Consider this improvement:

- await model.deleteAgendaItem(item.id!);
- await model.fetchAgendaItems();
+ try {
+   await model.deleteAgendaItem(item.id!);
+   await model.fetchAgendaItems();
+   if (context.mounted) {
      DelightToastBar(
        autoDismiss: true,
        snackbarDuration: const Duration(seconds: 2),
        builder: (context) => ToastCard(
-         title: const Text(
-           "Agenda item removed",
+         title: const Text(
+           "Agenda item removed successfully",
          style: TextStyle(
            color: Colors.white,
            fontSize: 12,
          ),
        ),
-       leading: const Icon(
-         Icons.error_outline,
-         color: Colors.redAccent,
-       ),
+       leading: const Icon(
+         Icons.check_circle_outline,
+         color: Colors.greenAccent,
+       ),
        color: Colors.black.withOpacity(0.8),
      ),
    ).show(context);
+   }
+ } catch (e) {
+   if (context.mounted) {
+     DelightToastBar(
+       autoDismiss: true,
+       snackbarDuration: const Duration(seconds: 2),
+       builder: (context) => ToastCard(
+         title: Text(
+           "Failed to remove agenda item: ${e.toString()}",
+           style: TextStyle(color: Colors.white, fontSize: 12),
+         ),
+         leading: const Icon(
+           Icons.error_outline,
+           color: Colors.redAccent,
+         ),
+         color: Colors.black.withOpacity(0.8),
+       ),
+     ).show(context);
+   }
+ }

Line range hint 63-89: Consider adding loading state during operations.

The UI might appear unresponsive during the deletion and refresh operations. Consider adding a loading indicator to improve user experience.

Consider wrapping the operations with a loading state:

+ model.setLoading(true); // Add loading state to your view model
  try {
    await model.deleteAgendaItem(item.id!);
    await model.fetchAgendaItems();
    // ... toast notification ...
  } catch (e) {
    // ... error handling ...
  } finally {
+   model.setLoading(false);
  }
test/service_tests/image_service_test.dart (2)

143-144: Document magic numbers used for file format detection

While the test implementation is good, the magic numbers used for file format detection should be documented for better maintainability.

Consider adding comments explaining the magic numbers:

-final jpegBytes = [0xFF, 0xD8, 0xFF, 0xE0];
+// JPEG file signature: FF D8 FF E0 (SOI and APP0 markers)
+final jpegBytes = [0xFF, 0xD8, 0xFF, 0xE0];

-final pngBytes = [0x89, 0x50, 0x4E, 0x47];
+// PNG file signature: 89 50 4E 47 (PNG magic number)
+final pngBytes = [0x89, 0x50, 0x4E, 0x47];

-final unknownBytes = [0x00, 0x00, 0x00, 0x00];
+// Invalid/unknown file signature for testing
+final unknownBytes = [0x00, 0x00, 0x00, 0x00];

Also applies to: 154-155, 165-166


116-139: Add tests for empty and null input handling

The decodeBase64 test group should include additional edge cases.

Consider adding these test cases:

test('decodeBase64 handles empty string', () {
  final result = imageService.decodeBase64('');
  expect(result, isNull);
});

test('decodeBase64 handles null input', () {
  final result = imageService.decodeBase64(null);
  expect(result, isNull);
});
test/helpers/test_locator.dart (1)

Missing test coverage for fund-related components

The codebase has fund-related source files but incomplete test coverage:

  • Missing test file for lib/services/fund_service.dart
  • Missing test file for lib/view_model/after_auth_view_models/funds_view_models/organisation_fund_view_model.dart

While model tests exist (fund_test.dart, fund_campaign_test.dart, fund_pledges_test.dart), the service and view model components lack test coverage. These components should be tested to ensure reliability and maintainability.

🔗 Analysis chain

Line range hint 1-156: Verify test coverage for new services.

While the registrations look good, ensure that the test suite includes comprehensive tests for the new FundService and FundViewModel.


Let me search for the fund-related files and their test coverage using a different approach.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for test files covering the new fund-related components

# Test 1: Look for test files for FundService
echo "Checking for FundService tests..."
fd -e dart "fund_service_test" test/

# Test 2: Look for test files for FundViewModel
echo "Checking for FundViewModel tests..."
fd -e dart "fund.*view_model_test" test/

Length of output: 237


Script:

#!/bin/bash
# Search for fund-related source files and their corresponding test files

echo "Searching for fund-related source files..."
fd -e dart "fund" src/ lib/

echo -e "\nSearching for fund-related test files..."
fd -e dart "fund" test/

echo -e "\nChecking content of services/fund_service.dart if exists..."
rg -l "class FundService" --type dart

echo -e "\nChecking content of fund view model if exists..."
rg -l "class.*FundViewModel" --type dart

Length of output: 1306

lib/widgets/pledge_card.dart (2)

5-6: Documentation needs improvement.

The current documentation comments contain placeholder text and incomplete descriptions. Please update them with meaningful descriptions:

  • Add a proper class description explaining the purpose and usage of PledgeCard
  • Document the required parameters and their purposes
  • Complete the _formatDate method documentation with parameter and return value descriptions
-/// a_line_ending_with_end_punctuation.
+/// A widget that displays pledge information in a card format.
+/// 
+/// This widget shows pledge details including pledgers, amounts, and dates,
+/// with options to update or delete the pledge.

-/// a_line_ending_with_end_punctuation.
+/// The pledge data to display.

-/// a_line_ending_with_end_punctuation.
+/// Callback function triggered when the update button is pressed.

-/// a_line_ending_with_end_punctuation.
+/// Callback function triggered when the delete button is pressed.

-/// a_line_ending_with_end_punctuation.
-///
-/// more_info_if_required
-///
-/// **params**:
-/// * `date`: define_the_param
-///
-/// **returns**:
-/// * `String`: define_the_return
+/// Formats a DateTime object into a human-readable string.
+///
+/// Converts the date into 'MMM d, y' format (e.g., 'Jan 1, 2024').
+/// Returns 'N/A' if the date is null.
+///
+/// **params**:
+/// * `date`: The DateTime object to format
+///
+/// **returns**:
+/// * `String`: The formatted date string or 'N/A' if date is null

Also applies to: 14-15, 17-18, 20-21, 214-223


190-202: Enhance accessibility support.

The action buttons need semantic labels for screen readers and better contrast ratios for the text colors.

Add semantic labels and improve contrast:

 TextButton.icon(
   icon: const Icon(Icons.edit),
   label: const Text('Update'),
   onPressed: onUpdate,
+  semanticsLabel: 'Update pledge',
 ),
 const SizedBox(width: 8),
 TextButton.icon(
   icon: const Icon(Icons.delete),
   label: const Text('Delete'),
   onPressed: onDelete,
-  style: TextButton.styleFrom(foregroundColor: Colors.red),
+  style: TextButton.styleFrom(
+    foregroundColor: Colors.red.shade700, // Better contrast
+  ),
+  semanticsLabel: 'Delete pledge',
 ),
lib/widgets/add_pledge_dialogue_box.dart (4)

1-26: Enhance widget documentation with usage examples.

The class documentation is good but could be more helpful with usage examples and parameter descriptions.

Add the following to the class documentation:

 /// A dialog widget for creating a pledge, allowing the user to input pledge details.
+///
+/// Example usage:
+/// ```dart
+/// showDialog(
+///   context: context,
+///   builder: (context) => AddPledgeDialog(
+///     onSubmit: (data) => print(data),
+///     model: fundViewModel,
+///     campaignId: 'campaign123',
+///   ),
+/// );
+/// ```

36-116: Improve widget structure and accessibility.

The UI structure could be improved by:

  1. Breaking down complex widget trees into separate methods or widgets
  2. Adding semantic labels for accessibility

Consider extracting the pledger selection UI into a separate widget:

+class PledgerSelectionField extends StatelessWidget {
+  final List<User> selectedPledgers;
+  final List<User> availablePledgers;
+  final ValueChanged<User> onPledgerSelected;
+  final ValueChanged<User> onPledgerRemoved;
+
+  // ... implement the widget
+}

 @override
 Widget build(BuildContext context) {
   return AlertDialog(
     title: const Text('Create Pledge'),
+    semanticLabel: 'Create Pledge Dialog',
     content: SingleChildScrollView(

156-203: Improve date selection UX and validation.

The date selection could be enhanced by:

  1. Using a single DateRangePicker instead of two separate pickers
  2. Adding clear error messages for invalid date selections

Consider using DateRangePicker for a better UX:

TextButton(
  onPressed: () async {
    final DateTimeRange? picked = await showDateRangePicker(
      context: context,
      firstDate: DateTime.now(),
      lastDate: DateTime.now().add(const Duration(days: 365)),
      initialDateRange: _startDate != null && _endDate != null
          ? DateTimeRange(start: _startDate!, end: _endDate!)
          : null,
    );
    if (picked != null) {
      setState(() {
        _startDate = picked.start;
        _endDate = picked.end;
      });
    }
  },
  child: Text(_startDate != null && _endDate != null
      ? 'Date Range: ${DateFormat('MMM d').format(_startDate!)} - ${DateFormat('MMM d').format(_endDate!)}'
      : 'Select Date Range'),
)

241-271: Optimize PopupMenuItem builder implementation.

The current implementation has:

  1. Unnecessary Column widget that only contains a single child
  2. Hard-coded values that should be constants

Optimize the implementation:

+const double _avatarSize = 18.0;
+const double _spacing = 8.0;

 Widget _buildPopupMenuItem(User user) {
-  return Column(
-    children: [
-      Row(
-        children: [
-          CircleAvatar(
-            backgroundImage:
-                user.image != null ? NetworkImage(user.image!) : null,
-            backgroundColor:
-                user.image == null ? Colors.blue : Colors.transparent,
-            child: user.image == null
-                ? const Icon(
-                    Icons.person_3,
-                    size: 18,
-                  )
-                : null,
-          ),
-          const SizedBox(width: 8),
-          Text('${user.firstName} ${user.lastName}'),
-        ],
-      ),
-    ],
-  );
+  return Row(
+    children: [
+      CircleAvatar(
+        backgroundImage:
+            user.image != null ? NetworkImage(user.image!) : null,
+        backgroundColor:
+            user.image == null ? Colors.blue : Colors.transparent,
+        child: user.image == null
+            ? const Icon(
+                Icons.person_3,
+                size: _avatarSize,
+              )
+            : null,
+      ),
+      const SizedBox(width: _spacing),
+      Text('${user.firstName} ${user.lastName}'),
+    ],
+  );
 }
lib/views/after_auth_screens/funds/funds_screen.dart (3)

11-11: Complete the class documentation.

The current documentation comment is a placeholder. Please provide a meaningful description of the FundScreen widget's purpose and functionality.

-/// a_line_ending_with_end_punctuation.
+/// A screen widget that displays and manages funds within the organization.
+/// 
+/// This screen provides functionality to view, search, and sort funds,
+/// with options to navigate to individual fund campaigns.

53-69: Enhance accessibility with semantic labels.

Add semantic labels to improve accessibility for screen readers.

 TextField(
   onChanged: (value) => model.searchFunds(value),
   decoration: InputDecoration(
     hintText: 'Search Funds',
+    semanticsLabel: 'Search funds text field',
     prefixIcon: const Icon(Icons.search, color: Colors.green),
     border: OutlineInputBorder(

240-252: Complete method documentation.

The helper methods have placeholder documentation. Please provide meaningful descriptions, parameters, and return value documentation.

Example for _buildInfoRow:

-/// a_line_ending_with_end_punctuation.
-///
-/// more_info_if_required
-///
-/// **params**:
-/// * `context`: define_the_param
-/// * `icon`: define_the_param
-/// * `label`: define_the_param
-/// * `value`: define_the_param
-///
-/// **returns**:
-/// * `Widget`: define_the_return
+/// Builds a row displaying fund information with an icon, label, and value.
+///
+/// This helper method creates a consistent layout for displaying various
+/// fund properties in the card.
+///
+/// **params**:
+/// * `context`: The build context for accessing theme data
+/// * `icon`: The icon to display before the label
+/// * `label`: The description label for the information
+/// * `value`: The actual information value to display
+///
+/// **returns**:
+/// * `Widget`: A row widget containing the formatted information

Also applies to: 287-296, 316-325

lib/services/event_service.dart (2)

Line range hint 300-303: Critical: Another instance of using mutation for fetching data

Similar to the previous issue, fetchAgendaCategories incorrectly uses gqlAuthMutation for fetching data. This should be a query operation.

Apply this diff to fix the issue:

-    final result = await _dbFunctions.gqlAuthMutation(
+    final result = await _dbFunctions.gqlAuthQuery(
       EventQueries().fetchAgendaItemCategoriesByOrganization(orgId),
     );

Line range hint 62-81: Enhance error handling consistency across methods

While fetchDataFromApi includes basic error handling, it could be more descriptive and consistent with other methods like fetchVolunteerGroupsByEvent. Consider:

  1. Adding more specific error messages
  2. Including try-catch blocks consistently
  3. Logging errors before rethrowing

Example improvement:

-    final result = await _dbFunctions.gqlAuthMutation(mutation);
+    try {
+      final result = await _dbFunctions.gqlAuthQuery(mutation);
+
+      if (result.data == null) {
+        throw Exception('Failed to fetch events: Empty response data');
+      }
+
+      if (result.hasException) {
+        throw Exception('Failed to fetch events: ${result.exception?.graphqlErrors.map((e) => e.message).join(", ")}');
+      }

       final eventsJson =
           result.data!["eventsByOrganizationConnection"] as List<dynamic>;
+    } catch (e) {
+      print('Error fetching events: $e');
+      rethrow;
+    }
lib/views/after_auth_screens/events/create_agenda_item_page.dart (1)

Line range hint 12-24: Consider adding validation and limits for better user experience.

The current implementation could benefit from additional validations:

  1. Duration format validation
  2. URL format validation
  3. Attachment size and count limits

Consider these improvements:

  1. Add duration format validation:
validator: (value) {
  if (value == null || value.isEmpty) {
    return AppLocalizations.of(context)!
        .strictTranslate('Please enter a duration');
  }
  // Add mm:ss format validation
  final durationRegex = RegExp(r'^([0-5][0-9]):([0-5][0-9])$');
  if (!durationRegex.hasMatch(value)) {
    return AppLocalizations.of(context)!
        .strictTranslate('Please enter duration in mm:ss format');
  }
  return null;
},
  1. Add URL validation:
void _addUrl() {
  if (urlController.text.isNotEmpty) {
    try {
      final uri = Uri.parse(urlController.text);
      if (!uri.hasScheme || !uri.hasAuthority) {
        throw FormatException('Invalid URL');
      }
      setState(() {
        urls.add(urlController.text);
        urlController.clear();
      });
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text(
            AppLocalizations.of(context)!
                .strictTranslate('Please enter a valid URL'),
          ),
        ),
      );
    }
  }
}
  1. Add attachment limits:
Future<void> _pickAttachment({bool fromCamera = false}) async {
  if (attachments.length >= 10) {  // Arbitrary limit of 10
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(
          AppLocalizations.of(context)!
              .strictTranslate('Maximum number of attachments reached'),
        ),
      ),
    );
    return;
  }
  final File? pickedFile =
      await _multiMediaPickerService.getPhotoFromGallery(camera: fromCamera);
  if (pickedFile != null) {
    if (await pickedFile.length() > 5 * 1024 * 1024) {  // 5MB limit
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text(
            AppLocalizations.of(context)!
                .strictTranslate('File size should be less than 5MB'),
          ),
        ),
      );
      return;
    }
    final base64PickedFile = await imageService.convertToBase64(pickedFile);
    setState(() {
      attachments.add(base64PickedFile);
    });
  }
}
lib/view_model/main_screen_view_model.dart (1)

263-266: Consider updating the navigation key name.

While the icon and label changes are appropriate for the funds functionality, the key keyBNProfile still references "Profile". Consider renaming it to keyBNFunds for better code clarity and maintainability.

- key: keyBNProfile,
+ key: keyBNFunds,
lib/services/image_service.dart (3)

113-119: Complete the return documentation in _getMimeType method

The return documentation currently contains a placeholder. Please update it to accurately describe what the method returns.

Here's the suggested change:

   /// **returns**:
-  /// * `String?`: define_the_return
+  /// * `String?`: The MIME type of the image (e.g., 'image/png'), or `null` if the type is unknown.

108-109: Use a logging mechanism instead of print statements

Directly using print for error handling is not recommended in production code. Consider using a logging framework to record errors.

Apply this diff:

       } catch (error) {
-        print("Error decoding base64: $error");
+        // Use a logging framework instead of print
+        logger.error("Error decoding base64: $error");
         return null;
       }

87-89: Clarify error handling in convertToBase64

Returning an empty string on error may lead to ambiguity for the caller. Consider returning null to indicate failure, or throw an exception for more explicit error handling.

Update the method to return null on error:

   Future<String?> convertToBase64(File file) async {
     try {
       final List<int> bytes = await file.readAsBytes();

       // Existing code...

     } catch (error) {
-      return '';
+      return null;
     }
   }

Also, update the method's return type in the signature and documentation:

-  /// * `Future<String>`: image in string format
+  /// * `Future<String?>`: Image in string format, or `null` if an error occurs
lib/utils/fund_queries.dart (3)

1-2: Consider adding a file-level documentation comment

Including a brief description at the top of the file can provide context and improve maintainability.


48-66: Align method and query names for clarity

The method fetchCampaignsByFund contains a GraphQL query named GetFundById. For better readability and maintainability, consider aligning the query name with the method name.


241-247: Include confirmation message in deletePledge mutation response

Returning a confirmation message upon successful deletion can improve user feedback and error handling.

lib/services/fund_service.dart (1)

28-28: Correct typographical errors in documentation comments

The word "option" is misspelled as "opton" in the parameter descriptions for the orderBy parameter in multiple methods. Please correct the spelling to improve readability.

Apply this diff:

- /// * `orderBy`: This variable decides the sorting opton.
+ /// * `orderBy`: This variable decides the sorting option.

Also applies to: 63-63, 108-108

lib/view_model/after_auth_view_models/funds_view_models/organisation_fund_view_model.dart (3)

92-93: Typo in documentation: Correct 'pldeges' to 'pledges'

There is a typographical error in the documentation comment. The word "pldeges" should be corrected to "pledges".

Apply this diff:

-/// getter for pldeges sorting option.
+/// Getter for pledges sorting option.

168-281: Fix typos: Replace 'methoud' with 'method' in documentation comments

The term "methoud" is misspelled throughout the documentation comments. It should be corrected to "method" for clarity and professionalism.

Please review and correct the typos in the following sections:

  • Lines 168-175: Comments for sortFunds method.
  • Lines 182-189: Comments for sortCampaigns method.
  • Lines 198-205: Comments for sortPledges method.
  • Lines 214-221: Comments for searchFunds method.
  • Lines 227-234: Comments for searchCampaigns method.
  • Lines 240-247: Comments for searchPledgers method.
  • Lines 252-259: Comments for _applyFundFilters method.
  • Lines 265-272: Comments for _applyCampaignFilters method.

243-244: Correct parameter description in 'searchPledgers' method documentation

The parameter description mentions "query to search for Funds" instead of "query to search for Pledges".

Apply this diff:

-/// * `query`: query to search for Funds.
+/// * `query`: query to search for Pledges.
lib/widgets/update_pledge_dialogue_box.dart (3)

104-117: Avoid code duplication by refactoring the avatar widget

The code for building the CircleAvatar is duplicated. Refactor this into a separate method to improve maintainability and reduce redundancy.

Implement a dedicated method for the user avatar:

Widget _buildUserAvatar(User user) {
  return CircleAvatar(
    backgroundImage: user.image != null ? NetworkImage(user.image!) : null,
    backgroundColor: user.image == null ? Colors.blue : Colors.transparent,
    child: user.image == null
        ? const Icon(
            Icons.person_3,
            size: 18,
          )
        : null,
  );
}

Then replace the duplicated code with _buildUserAvatar(user).

Also applies to: 296-307


162-163: Review the usage of setState in changeCurrency

Passing setState to widget.model.changeCurrency may not be the best practice. Consider revising the implementation to manage state within the method or use callbacks.


255-277: Improve user feedback on update action

When the update button is disabled, the user may not understand why. Consider providing visual cues or messages indicating the required fields or changes needed to enable the update.

lib/views/after_auth_screens/funds/fund_pledges_screen.dart (3)

223-223: Use theme colors for consistent dropdown styling.

Consider using theme-based colors for the dropdown background to ensure consistent styling across the app, particularly to accommodate both light and dark themes.

Apply this diff to use the theme's scaffold background color:

-      dropdownColor: const Color.fromARGB(255, 49, 49, 49),
+      dropdownColor: Theme.of(context).scaffoldBackgroundColor,

Alternatively, choose a color from the theme palette that aligns with your design guidelines.


352-352: Fix typo in method documentation.

There is a typo in the documentation comment: "pldege" should be "pledge." Also, for consistency, consider starting the comment with "Shows" to match the tense used in other method comments.

Apply this diff to correct the typo and adjust the phrasing:

-  /// Show the confirmation dialogue to delete the pldege.
+  /// Shows the confirmation dialog to delete the pledge.

196-210: Implement debouncing in the search functionality.

To prevent performance issues due to rapid consecutive calls to searchPledgers, consider implementing debouncing on the search input.

You can use a Timer to debounce the input:

Timer? _debounce;

void _onSearchChanged(String query) {
  if (_debounce?.isActive ?? false) _debounce!.cancel();
  _debounce = Timer(const Duration(milliseconds: 300), () {
    model.searchPledgers(query);
  });
}

// In the TextField:
onChanged: _onSearchChanged,

Remember to dispose of the _debounce timer in the dispose method:

@override
void dispose() {
  _debounce?.cancel();
  super.dispose();
}
lib/views/after_auth_screens/funds/fundraising_campaigns_screen.dart (2)

175-175: Extract common style constants for consistency and maintainability

The BorderRadius values are repeatedly set to BorderRadius.circular(15) in multiple places. Consider extracting these values into constants or theme definitions to promote consistency and ease future maintenance.

Also applies to: 192-193, 319-320


41-41: Use theme colors instead of hardcoding color values

Colors.green is used directly in multiple places. Consider using theme colors or defining a custom color palette to maintain consistency and support theming.

Also applies to: 76-76, 85-85, 335-335, 350-350

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 38179c8 and 070a162.

📒 Files selected for processing (27)
  • lib/locator.dart (5 hunks)
  • lib/models/funds/fund.dart (1 hunks)
  • lib/models/funds/fund_campaign.dart (1 hunks)
  • lib/models/funds/fund_pledges.dart (1 hunks)
  • lib/services/event_service.dart (1 hunks)
  • lib/services/fund_service.dart (1 hunks)
  • lib/services/image_service.dart (3 hunks)
  • lib/utils/fund_queries.dart (1 hunks)
  • lib/view_model/after_auth_view_models/funds_view_models/organisation_fund_view_model.dart (1 hunks)
  • lib/view_model/main_screen_view_model.dart (3 hunks)
  • lib/views/after_auth_screens/events/create_agenda_item_page.dart (1 hunks)
  • lib/views/after_auth_screens/events/edit_agenda_item_page.dart (1 hunks)
  • lib/views/after_auth_screens/events/manage_agenda_items_screen.dart (1 hunks)
  • lib/views/after_auth_screens/feed/organization_feed.dart (2 hunks)
  • lib/views/after_auth_screens/funds/fund_pledges_screen.dart (1 hunks)
  • lib/views/after_auth_screens/funds/fundraising_campaigns_screen.dart (1 hunks)
  • lib/views/after_auth_screens/funds/funds_screen.dart (1 hunks)
  • lib/views/after_auth_screens/profile/profile_page.dart (0 hunks)
  • lib/widgets/add_pledge_dialogue_box.dart (1 hunks)
  • lib/widgets/agenda_item_tile.dart (2 hunks)
  • lib/widgets/pledge_card.dart (1 hunks)
  • lib/widgets/update_pledge_dialogue_box.dart (1 hunks)
  • test/helpers/test_locator.dart (4 hunks)
  • test/model_tests/funds/fund_campaign_test.dart (1 hunks)
  • test/model_tests/funds/fund_pledges_test.dart (1 hunks)
  • test/model_tests/funds/fund_test.dart (1 hunks)
  • test/service_tests/image_service_test.dart (4 hunks)
💤 Files with no reviewable changes (1)
  • lib/views/after_auth_screens/profile/profile_page.dart
🔇 Additional comments (34)
test/model_tests/funds/fund_test.dart (2)

1-4: LGTM! All necessary imports are present.


5-69: Verify validation rules in Fund model.

The tests focus on successful parsing but don't cover validation rules or error cases.

test/model_tests/funds/fund_campaign_test.dart (2)

1-4: LGTM! Imports are properly structured.

All necessary dependencies are imported and follow Dart conventions.


1-64: Verify alignment with fundraising campaign requirements.

The test coverage looks good overall, but let's verify that all required fundraising campaign features mentioned in issue #2593 are covered by the model and tests.

✅ Verification successful

Test coverage aligns well with the Campaign model implementation

The test file comprehensively covers all the fields and functionality present in the Campaign model:

  • All model fields are tested: id, fundId, name, startDate, endDate, fundingGoal, currency, pledges
  • Both valid and null scenarios are verified
  • The model structure matches its usage in Fund model and UI implementations
  • Tests align with the actual implementation in fund_campaign.dart

The test coverage is thorough and appropriate for the current state of the fundraising campaign feature.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if all required campaign features are implemented

# Look for the Campaign model implementation
echo "Checking Campaign model implementation..."
rg -l "class Campaign" "lib/"

# Check for any additional campaign-related models
echo "Checking related models..."
rg -l "campaign" "lib/models/"

# Look for UI implementations using the Campaign model
echo "Checking UI implementations..."
rg -l "Campaign" "lib/views/"

Length of output: 698


Script:

#!/bin/bash
# Let's examine the Campaign model implementation and related files

# Check Campaign model implementation details
echo "=== Campaign Model Details ==="
rg -A 20 "class Campaign" "lib/models/funds/fund_campaign.dart"

# Check Fund model for campaign relationships
echo -e "\n=== Fund Model Campaign Relations ==="
rg "campaign" "lib/models/funds/fund.dart"

# Check FundraisingCampaignsScreen implementation
echo -e "\n=== Campaign Screen Implementation ==="
rg "Campaign" "lib/views/after_auth_screens/funds/fundraising_campaigns_screen.dart"

# Check issue requirements if available
echo -e "\n=== Issue Requirements ==="
gh issue view 2593 || echo "Issue not accessible"

Length of output: 3577

test/model_tests/funds/fund_pledges_test.dart (2)

1-5: LGTM! Import statements are well-organized.

The imports are correctly structured and include all necessary dependencies for the test file.


1-83: Verify integration with fundraising campaign feature.

As this is part of the larger fundraising campaigns feature, we should verify the integration points with other components.

✅ Verification successful

Integration with fundraising campaign feature is properly implemented

The code inspection reveals a well-structured integration between the fundraising components:

  • Fund model maintains proper relationship with Campaigns through a list of Campaign objects
  • Campaign model correctly links to both Fund (via fundId) and Pledges
  • Pledge model appropriately connects to Campaigns and Users
  • All models have consistent date handling, currency support, and proper null safety
  • Test coverage validates both the happy path and null scenarios for all models
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for related test files and models
echo "Checking for related fundraising test files..."
fd -e dart "fund.*test" test/

echo "\nChecking for related model implementations..."
fd -e dart "fund" lib/models/

echo "\nChecking for service layer tests..."
rg -l "fund.*service.*test" test/

Length of output: 619


Script:

#!/bin/bash
# Let's examine the fund model implementations and their relationships

echo "Checking Fund model implementation..."
cat lib/models/funds/fund.dart

echo -e "\nChecking FundCampaign model implementation..."
cat lib/models/funds/fund_campaign.dart

echo -e "\nChecking FundPledges model implementation..."
cat lib/models/funds/fund_pledges.dart

# Let's also check the campaign test implementation
echo -e "\nChecking Fund Campaign test implementation..."
cat test/model_tests/funds/fund_campaign_test.dart

echo -e "\nChecking Fund test implementation..."
cat test/model_tests/funds/fund_test.dart

Length of output: 14018

lib/models/funds/fund.dart (2)

62-91: LGTM! Well-documented and properly structured fields

The field declarations follow best practices:

  • All fields are final (immutable)
  • Clear and concise documentation
  • Logical organization

1-5: Verify the Campaign type import and usage

There seems to be a potential type mismatch. The code uses Campaign type but imports from fund_campaign.dart. Ensure that the imported type name matches the usage.

test/service_tests/image_service_test.dart (3)

24-28: LGTM: Mock implementation is appropriate

The mock implementation of decodeBase64 follows good testing practices with proper error handling and test data.


116-139: LGTM: Comprehensive test coverage for base64 decoding

The test group provides excellent coverage of various scenarios including:

  • Valid base64 strings with MIME type
  • Base64 strings without MIME type
  • Invalid input handling

Line range hint 1-2: Verify documentation ignore directives

The file ignores documentation-related lints. Given that this is part of a significant feature (fundraising campaigns), consider if proper documentation would be beneficial for maintainability.

✅ Verification successful

Documentation ignore directives are a common pattern in test files

The ignore directives for documentation are consistently used across many test files in the project, indicating this is an established pattern. Since this appears to be a deliberate project-wide convention for test files, and test files typically focus on verifying functionality rather than serving as documentation, these ignore directives are acceptable.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if other test files follow similar documentation patterns
rg "ignore.*talawa.*doc" "test/**/*_test.dart"

Length of output: 108


Script:

#!/bin/bash
# Let's first find the test files correctly using fd
fd "_test\.dart$" "test"

# Then check documentation patterns in those files
fd "_test\.dart$" "test" -x rg -l "ignore.*talawa.*doc" {}

Length of output: 15472

lib/widgets/agenda_item_tile.dart (3)

3-3: LGTM: Import required for dependency injection.

The addition of the locator import is necessary for accessing the imageService instance.


132-133: LGTM: Good refactoring to use service-based approach.

Moving the base64 decoding logic to a dedicated service improves code organization and maintainability.


Line range hint 1-200: Verify the relationship with fundraising campaigns.

This widget appears to be an agenda item display component, but the PR's main objective is to introduce fundraising campaigns. Could you clarify how this widget relates to the fundraising feature?

test/helpers/test_locator.dart (3)

13-13: LGTM! Import statements follow existing patterns.

The new imports for fund_service.dart and organisation_fund_view_model.dart are appropriately placed within their respective service and view model import groups.

Also applies to: 38-38


100-100: LGTM! Service registration follows singleton pattern.

The FundService is correctly registered as a lazy singleton, consistent with other service registrations in the file.


127-127: LGTM! ViewModel registration follows factory pattern.

The FundViewModel is correctly registered as a factory, consistent with other view model registrations in the file.

lib/locator.dart (4)

11-11: LGTM: Import statements are properly structured.

The new imports for fund-related services and view models follow the project's established patterns and directory structure.

Also applies to: 37-37


130-130: LGTM: Service registration follows best practices.

The FundService is correctly registered as a lazy singleton, consistent with other service registrations in the application.


160-160: LGTM: ViewModel registration follows best practices.

The FundViewModel is correctly registered as a factory, consistent with other ViewModel registrations in the application.


Line range hint 104-160: Consider initialization order and migration steps.

Since this PR introduces breaking changes:

  1. Ensure FundService initialization happens after its dependencies (if any).
  2. Consider adding migration steps or documentation for existing consumers of the application.
  3. Update the CHANGELOG.md to document these breaking changes.

Let's verify the dependencies:

✅ Verification successful

Initialization order is correct, but documentation updates needed

The FundService initialization is properly ordered as:

  • It's registered as lazy singleton, so instantiation happens only when first requested
  • Its dependencies (UserConfig, DataBaseMutationFunctions) are registered before FundService in the locator setup
  • The service has proper null-safety handling with late initialization

Action items:

  • Add breaking changes to CHANGELOG.md since this PR is marked as having breaking changes
  • Document the FundService dependencies in code comments for maintainability
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for FundService dependencies and consumers
rg -l "FundService" --type dart

Length of output: 200


Script:

#!/bin/bash
# Check FundService implementation and dependencies
rg "class FundService" -A 10 lib/services/fund_service.dart

# Check if FundService has any constructor dependencies
ast-grep --pattern 'class FundService {
  $$$
  FundService($$$) {
    $$$
  }
  $$$
}'

# Check organization fund view model implementation
rg "class OrganisationFundViewModel" -A 10 lib/view_model/after_auth_view_models/funds_view_models/organisation_fund_view_model.dart

# Check test locator setup for comparison
rg "FundService" test/helpers/test_locator.dart

Length of output: 685

lib/views/after_auth_screens/feed/organization_feed.dart (2)

7-7: LGTM: Import statement is correctly placed.

The import for ProfilePage is appropriately placed with other view imports and supports the new profile navigation feature.


86-115: 🛠️ Refactor suggestion

⚠️ Potential issue

Several improvements needed for the profile navigation implementation.

  1. Use the navigation service instead of direct Navigator calls for consistency:
-                  Navigator.push(
-                    context,
-                    MaterialPageRoute(
-                      builder: (context) => ProfilePage(
-                        key: const Key('UserProfile'),
-                        homeModel: widget.homeModel,
-                      ),
-                    ),
-                  );
+                  navigationService.pushScreen(
+                    '/profile',
+                    arguments: {'homeModel': widget.homeModel},
+                  );
  1. Add proper error handling and loading states for the user image:
                 icon: CircleAvatar(
                   radius: 16,
-                  backgroundImage: userConfig.currentUser.image != null
-                      ? NetworkImage(userConfig.currentUser.image!)
-                      : null,
+                  backgroundImage: userConfig.currentUser?.image != null
+                      ? NetworkImage(
+                          userConfig.currentUser!.image!,
+                          errorBuilder: (context, error, stackTrace) =>
+                              const Icon(Icons.error),
+                        )
+                      : null,
-                  backgroundColor: userConfig.currentUser.image == null
+                  backgroundColor: userConfig.currentUser?.image == null
                       ? Colors.blue
                       : Colors.transparent,
-                  child: userConfig.currentUser.image == null
+                  child: userConfig.currentUser?.image == null
                       ? const Icon(
                           Icons.person_3,
                           size: 26,
                         )
                       : null,
  1. Consider extracting the profile avatar widget to improve maintainability:
class UserProfileAvatar extends StatelessWidget {
  const UserProfileAvatar({Key? key}) : super(key: key);
  
  @override
  Widget build(BuildContext context) {
    return CircleAvatar(
      // ... extracted avatar implementation
    );
  }
}

Additionally, verify that the navigation service has a route defined for the profile page:

✅ Verification successful

Navigation implementation needs to use the correct route constant

The review comment's suggestion to use navigation service is correct, but it should use the defined route constant:

-                  Navigator.push(
-                    context,
-                    MaterialPageRoute(
-                      builder: (context) => ProfilePage(
-                        key: const Key('UserProfile'),
-                        homeModel: widget.homeModel,
-                      ),
-                    ),
-                  );
+                  navigationService.pushScreen(
+                    Routes.profilePage,
+                    arguments: {'homeModel': widget.homeModel}
+                  );

The codebase shows:

  • navigationService is already being used in the file for other navigations
  • Routes are properly defined in routing_constants.dart with profilePage = "/profilePage"
  • The pattern of using Routes constants is established (e.g., Routes.editProfilePage usage in settings)

The other suggestions about error handling and widget extraction remain valid.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if profile route is registered in the navigation service
rg -A 5 "route.*profile" lib/services/navigation_service.dart

Length of output: 63


Script:

#!/bin/bash
# Let's first check if NavigationService exists and how it's implemented
rg -l "class.*NavigationService" lib/

# Then check for any profile-related navigation patterns in the codebase
rg -A 3 "push.*[Pp]rofile" lib/

Length of output: 560


Script:

#!/bin/bash
# Let's check how Routes are defined and if there's a profile route
rg -A 5 "class.*Routes" lib/

# Check how profile navigation is implemented in app_settings_page.dart
rg -B 5 -A 10 "pushScreen.*editProfilePage" lib/views/after_auth_screens/app_settings/app_settings_page.dart

# Check if there's a navigation service instance being used in the file
rg "navigationService" lib/views/after_auth_screens/feed/organization_feed.dart

Length of output: 1508


Script:

#!/bin/bash
# Let's check for editProfilePage route definition
rg "editProfilePage" lib/constants/routing_constants.dart

# Check for any other profile-related routes
rg "profile" lib/constants/routing_constants.dart

# Let's see how navigationService is initialized in organization_feed.dart
rg -B 5 "navigationService" lib/views/after_auth_screens/feed/organization_feed.dart

Length of output: 904

lib/widgets/pledge_card.dart (1)

1-227: Verify widget integration with parent components.

Please ensure:

  1. The Pledge model structure matches the widget's expectations
  2. Parent components handle the onUpdate and onDelete callbacks appropriately

Let's verify the integration:

✅ Verification successful

Let me gather more information about the Pledge model and its usage to ensure complete verification.


Widget integration is properly implemented

The verification confirms:

  1. The Pledge model structure matches the widget's expectations:

    • All properties used in PledgeCard (pledgers, amount, startDate, endDate) are present in the Pledge model
    • Proper null handling is implemented in both the model and widget
  2. Parent component (PledgesScreen) correctly:

    • Implements the widget with required properties
    • Handles onUpdate through _showUpdatePledgeDialog
    • Handles onDelete through _showDeleteConfirmationDialog
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check Pledge model structure
echo "Checking Pledge model structure..."
rg -t dart "class Pledge" -A 10

# Check PledgeCard usage
echo "Checking PledgeCard usage..."
rg -t dart "PledgeCard\(" -A 5

Length of output: 3531


Script:

#!/bin/bash
# Check complete Pledge model structure and its properties
echo "Checking complete Pledge model..."
cat lib/models/funds/fund_pledges.dart

# Check for pledge update and delete methods
echo "Checking pledge update and delete methods..."
rg -t dart "_showUpdatePledgeDialog|_showDeleteConfirmationDialog" -A 10

Length of output: 6280

lib/views/after_auth_screens/funds/funds_screen.dart (1)

201-211: Verify navigation integration with campaign screen.

The navigation to CampaignsScreen should be verified to ensure proper integration with the overall navigation flow and state management.

✅ Verification successful

Navigation integration with CampaignsScreen is correctly implemented

The verification confirms that:

  • The CampaignsScreen class exists in lib/views/after_auth_screens/funds/fundraising_campaigns_screen.dart
  • The class correctly accepts the required parameters fundId and fundName as final String properties
  • The screen is properly integrated with MVVM pattern using BaseView<FundViewModel>
  • Navigation implementation uses standard MaterialPageRoute with correct parameter passing
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify CampaignsScreen integration
# Test: Check if CampaignsScreen exists and accepts required parameters

# Check if CampaignsScreen exists
rg -l "class CampaignsScreen"

# Check parameter usage
ast-grep --pattern 'class CampaignsScreen extends StatefulWidget {
  $$$
  final String fundId;
  final String fundName;
  $$$
}'

Length of output: 232


Script:

#!/bin/bash
# Let's verify the actual class name and its parameters in the fundraising_campaigns_screen.dart
cat lib/views/after_auth_screens/funds/fundraising_campaigns_screen.dart

Length of output: 13607

lib/view_model/main_screen_view_model.dart (3)

15-15: LGTM: Import statement correctly added.

The import for the funds screen is properly added and follows the project's path conventions.


Line range hint 263-289: Update tour functionality for funds screen.

The app tour functionality still contains profile-related targets (e.g., in tourProfile method) but the profile page has been replaced with the funds screen. This needs to be updated to:

  1. Remove or update profile-related tour targets
  2. Add new tour targets for the funds functionality
#!/bin/bash
# Description: Check for remaining profile references in tour functionality
# Test: Look for profile-related keys and targets that need updating
rg "Profile|profile" --type dart

287-289: Verify FundScreen integration and navigation.

The FundScreen is correctly added to the pages list. However, let's verify its integration with the navigation system.

✅ Verification successful

Let me generate new scripts to verify the FundScreen integration.


Let me try a different search pattern to locate the FundScreen implementation.


Based on the search results, I can now provide a conclusive verification of the FundScreen integration.

FundScreen is properly integrated and follows the navigation pattern.

The verification shows:

  • FundScreen is correctly implemented at lib/views/after_auth_screens/funds/funds_screen.dart
  • It's properly imported in the main screen view model
  • The screen is integrated with the required view models and services
  • The navigation setup follows the established pattern with proper key naming
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify FundScreen integration
# Test 1: Check if FundScreen is properly exported
rg -l "^export.*FundScreen" "lib/views/after_auth_screens/funds/"

# Test 2: Check if FundScreen has required setup
ast-grep --pattern 'class FundScreen extends StatelessWidget {
  $$$
}'

Length of output: 33948


Script:

#!/bin/bash
# Check if FundScreen exists and its implementation
fd -e dart "fund_screen|Fund.*.dart" 

# Search for FundScreen class definition
ast-grep --pattern 'class FundScreen extends StatelessWidget {
  $$$
}'

# Search for FundScreen imports
rg "import.*FundScreen" -l

Length of output: 141


Script:

#!/bin/bash
# Search for any file containing "fund" in its name
fd -e dart -i "fund"

# Search for any class definition containing "Fund" in its name
ast-grep --pattern 'class $_(Fund|fund)$_ {
  $$$
}'

# Search for any imports or references containing "fund" case-insensitive
rg -i "fund" --type dart

Length of output: 30948

lib/utils/fund_queries.dart (4)

77-103: Verify variable usage in fetchPledgesByCampaign

Ensure that all variables declared in the query are used appropriately. The variable \$pledgeOrderBy is declared but may not be correctly applied within the query.

Please confirm that \$pledgeOrderBy is used as intended in the query or remove it if unnecessary.


139-164: Handle optional variables appropriately in fetchUserPledges

Ensure that optional variables like \$where and \$orderBy are handled correctly in the query to prevent potential runtime errors.


207-231: Ensure optional fields are correctly managed in updatePledge mutation

Verify that optional fields in the mutation, such as amount and currency, are handled appropriately when they are null or not provided.


52-54: ⚠️ Potential issue

Ensure consistent variable naming in GraphQL query

There's a mismatch in variable names between the query definition and usage. The variable \$orderBy is declared but not used correctly in the getFundById query.

Apply this diff to correct the variable name:

52           \$orderBy: CampaignOrderByInput
...
54         getFundById(id: \$fundId, where: \$where, 
-          orderBy: \$campaignOrderBy) {
+          orderBy: \$orderBy) {

Likely invalid or redundant comment.

lib/services/fund_service.dart (1)

87-88: ⚠️ Potential issue

Verify the correctness of the data access path

In the getCampaigns method, ensure that the key 'getFundById' used to access data from result.data! matches the actual structure returned by the GraphQL query. An incorrect key can lead to runtime errors and unexpected behavior.

Please verify that the GraphQL query fetchCampaignsByFund returns data with the key 'getFundById'. If the key is different, update the code accordingly.

lib/widgets/update_pledge_dialogue_box.dart (1)

227-228: Ensure correct date selection logic

In the end date picker, the initialDate is set to _endDate ?? (_startDate ?? DateTime.now()), and firstDate is _startDate ?? DateTime.now(). Ensure that the date picker's logic prevents selecting an end date earlier than the start date.

Run the following script to verify date logic consistency:

✅ Verification successful

Date selection logic is properly implemented

The code shows that the end date picker's configuration correctly prevents selecting dates before the start date:

  • The firstDate parameter is set to _startDate ?? DateTime.now(), which means the minimum selectable date is either the chosen start date or today if no start date is selected
  • The initialDate is properly defaulting to either the existing end date, the start date, or today's date: _endDate ?? (_startDate ?? DateTime.now())
  • Both pickers have a reasonable lastDate limit set to one year from now

This implementation ensures users cannot select an end date that precedes the start date.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the usage of start and end dates in date pickers.

# Expected: The end date picker should not allow dates before the start date.
# As this is a code logic check, please manually review the date picker configurations.

Length of output: 0


Script:

#!/bin/bash
# Let's examine the full context of the date picker implementation
rg -A 10 -B 10 "showDatePicker" lib/widgets/update_pledge_dialogue_box.dart

Length of output: 2033

'firstName': 'John',
'lastName': 'Doe',
},
'campaigns': [Campaign(id: 'campaign1')],
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix test data structure for campaigns field.

The test data should contain a JSON representation of the campaign instead of a Campaign object. This doesn't accurately test the JSON deserialization.

-        'campaigns': [Campaign(id: 'campaign1')],
+        'campaigns': [{
+          '_id': 'campaign1',
+          // Add other required campaign fields
+        }],

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +5 to +34
void main() {
group('Test Campaign Model', () {
test('Test Campaign fromJson', () {
final campaignJson = {
'_id': 'campaign1',
'fundId': 'fund1',
'name': 'Annual Fundraiser',
'startDate': '2024-01-01T00:00:00Z',
'endDate': '2024-12-31T23:59:59Z',
'fundingGoal': 10000.0,
'currency': 'USD',
'pledges': [Pledge(id: 'pledge1')],
'createdAt': '2024-01-01T00:00:00Z',
'updatedAt': '2024-01-02T00:00:00Z',
};

final campaign = Campaign.fromJson(campaignJson);

expect(campaign.id, 'campaign1');
expect(campaign.fundId, 'fund1');
expect(campaign.name, 'Annual Fundraiser');
expect(campaign.startDate, DateTime.parse('2024-01-01T00:00:00Z'));
expect(campaign.endDate, DateTime.parse('2024-12-31T23:59:59Z'));
expect(campaign.fundingGoal, 10000.0);
expect(campaign.currency, 'USD');
expect(campaign.pledges?.length, 1);
expect(campaign.pledges?[0].id, 'pledge1');
expect(campaign.createdAt, DateTime.parse('2024-01-01T00:00:00Z'));
expect(campaign.updatedAt, DateTime.parse('2024-01-02T00:00:00Z'));
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add test cases for edge cases and validation.

The test suite should include additional cases:

  1. Invalid currency codes
  2. Negative funding goals
  3. End date before start date
  4. Malformed date strings

Would you like me to provide example test cases for these scenarios?

Comment on lines +10 to +30
final jsonData = {
'_id': 'pledge123',
'campaigns': [
Campaign(name: 'Education Fund'),
Campaign(name: 'Health Fund'),
],
'users': [
<String, dynamic>{
'firstName': 'Alice',
},
<String, dynamic>{
'firstName': 'Bob',
},
],
'startDate': '2024-01-01T00:00:00.000Z',
'endDate': '2024-12-31T23:59:59.999Z',
'amount': 500,
'currency': 'USD',
'createdAt': '2024-01-01T00:00:00.000Z',
'updatedAt': '2024-06-30T00:00:00.000Z',
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Test data structure needs revision.

The test data has several issues that should be addressed:

  1. The campaigns array contains direct Campaign objects instead of JSON objects, which doesn't reflect real API responses
  2. The test doesn't validate important edge cases for monetary values

Consider updating the test data structure:

 final jsonData = {
   '_id': 'pledge123',
   'campaigns': [
-    Campaign(name: 'Education Fund'),
-    Campaign(name: 'Health Fund'),
+    {
+      'name': 'Education Fund',
+      'id': 'campaign1'
+    },
+    {
+      'name': 'Health Fund',
+      'id': 'campaign2'
+    }
   ],
   // ... rest of the test data
 };

Also, consider adding additional test cases for:

  • Negative amounts
  • Different currency formats
  • Maximum/minimum amount boundaries

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +8 to +27
/// [id] is the unique identifier of the pledge.
/// [campaigns] is a list of campaign identifiers associated with the pledge.
/// [users] is a list of user identifiers associated with the pledge.
/// [startDate] is the start date of the pledge.
/// [endDate] is the end date of the pledge.
/// [amount] is the amount pledged.
/// [currency] is the currency of the pledged amount.
/// [createdAt] is the timestamp of when the pledge was created.
/// [updatedAt] is the timestamp of when the pledge was last updated.
Pledge({
this.id,
this.campaigns,
this.pledgers,
this.startDate,
this.endDate,
this.amount,
this.currency,
this.createdAt,
this.updatedAt,
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix constructor parameter documentation and consider required fields.

  1. The parameter documentation refers to users but the parameter is named pledgers.
  2. Consider making essential fields required to prevent invalid states.

Suggested improvements:

-  /// [users] is a list of user identifiers associated with the pledge.
+  /// [pledgers] is a list of users who made this pledge.
   ///
   Pledge({
-    this.id,
-    this.campaigns,
-    this.pledgers,
-    this.startDate,
-    this.endDate,
-    this.amount,
-    this.currency,
+    required this.campaigns,
+    required this.pledgers,
+    required this.startDate,
+    required this.endDate,
+    required this.amount,
+    required this.currency,
+    this.id,
     this.createdAt,
     this.updatedAt,
   });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// [id] is the unique identifier of the pledge.
/// [campaigns] is a list of campaign identifiers associated with the pledge.
/// [users] is a list of user identifiers associated with the pledge.
/// [startDate] is the start date of the pledge.
/// [endDate] is the end date of the pledge.
/// [amount] is the amount pledged.
/// [currency] is the currency of the pledged amount.
/// [createdAt] is the timestamp of when the pledge was created.
/// [updatedAt] is the timestamp of when the pledge was last updated.
Pledge({
this.id,
this.campaigns,
this.pledgers,
this.startDate,
this.endDate,
this.amount,
this.currency,
this.createdAt,
this.updatedAt,
});
/// [id] is the unique identifier of the pledge.
/// [campaigns] is a list of campaign identifiers associated with the pledge.
/// [pledgers] is a list of users who made this pledge.
/// [startDate] is the start date of the pledge.
/// [endDate] is the end date of the pledge.
/// [amount] is the amount pledged.
/// [currency] is the currency of the pledged amount.
/// [createdAt] is the timestamp of when the pledge was created.
/// [updatedAt] is the timestamp of when the pledge was last updated.
Pledge({
required this.campaigns,
required this.pledgers,
required this.startDate,
required this.endDate,
required this.amount,
required this.currency,
this.id,
this.createdAt,
this.updatedAt,
});

Comment on lines +34 to +62
factory Pledge.fromJson(Map<String, dynamic> json) {
return Pledge(
id: json['_id'] as String?,
campaigns: (json['campaigns'] as List<dynamic>?)
?.map((e) => e as Campaign)
.toList(),
pledgers: json['users'] == null
? null
: (json['users'] as List<dynamic>?)
?.map(
(e) => User.fromJson(e as Map<String, dynamic>, fromOrg: true),
)
.toList(),
startDate: json['startDate'] != null
? DateTime.parse(json['startDate'] as String)
: null,
endDate: json['endDate'] != null
? DateTime.parse(json['endDate'] as String)
: null,
amount: json['amount'] as int?,
currency: json['currency'] as String?,
createdAt: json['createdAt'] != null
? DateTime.parse(json['createdAt'] as String)
: null,
updatedAt: json['updatedAt'] != null
? DateTime.parse(json['updatedAt'] as String)
: null,
);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Address potential runtime errors in JSON deserialization.

Several issues need attention:

  1. Unsafe casting of campaigns could cause runtime errors
  2. Missing validation for amount and currency
  3. Inconsistent field mapping between JSON and class properties

Suggested improvements:

   factory Pledge.fromJson(Map<String, dynamic> json) {
+    // Validate amount
+    final amount = json['amount'] as int?;
+    if (amount != null && amount <= 0) {
+      throw FormatException('Pledge amount must be positive');
+    }
+
+    // Validate currency format
+    final currency = json['currency'] as String?;
+    if (currency != null && currency.length != 3) {
+      throw FormatException('Currency must be a 3-letter ISO code');
+    }
+
     return Pledge(
       id: json['_id'] as String?,
       campaigns: (json['campaigns'] as List<dynamic>?)
-          ?.map((e) => e as Campaign)
+          ?.map((e) => Campaign.fromJson(e as Map<String, dynamic>))
           .toList(),
       pledgers: json['users'] == null
           ? null
           : (json['users'] as List<dynamic>?)
               ?.map(
                 (e) => User.fromJson(e as Map<String, dynamic>, fromOrg: true),
               )
               .toList(),
       startDate: json['startDate'] != null
           ? DateTime.parse(json['startDate'] as String)
           : null,
       endDate: json['endDate'] != null
           ? DateTime.parse(json['endDate'] as String)
           : null,
-      amount: json['amount'] as int?,
-      currency: json['currency'] as String?,
+      amount: amount,
+      currency: currency,
       createdAt: json['createdAt'] != null
           ? DateTime.parse(json['createdAt'] as String)
           : null,
       updatedAt: json['updatedAt'] != null
           ? DateTime.parse(json['updatedAt'] as String)
           : null,
     );
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
factory Pledge.fromJson(Map<String, dynamic> json) {
return Pledge(
id: json['_id'] as String?,
campaigns: (json['campaigns'] as List<dynamic>?)
?.map((e) => e as Campaign)
.toList(),
pledgers: json['users'] == null
? null
: (json['users'] as List<dynamic>?)
?.map(
(e) => User.fromJson(e as Map<String, dynamic>, fromOrg: true),
)
.toList(),
startDate: json['startDate'] != null
? DateTime.parse(json['startDate'] as String)
: null,
endDate: json['endDate'] != null
? DateTime.parse(json['endDate'] as String)
: null,
amount: json['amount'] as int?,
currency: json['currency'] as String?,
createdAt: json['createdAt'] != null
? DateTime.parse(json['createdAt'] as String)
: null,
updatedAt: json['updatedAt'] != null
? DateTime.parse(json['updatedAt'] as String)
: null,
);
}
factory Pledge.fromJson(Map<String, dynamic> json) {
// Validate amount
final amount = json['amount'] as int?;
if (amount != null && amount <= 0) {
throw FormatException('Pledge amount must be positive');
}
// Validate currency format
final currency = json['currency'] as String?;
if (currency != null && currency.length != 3) {
throw FormatException('Currency must be a 3-letter ISO code');
}
return Pledge(
id: json['_id'] as String?,
campaigns: (json['campaigns'] as List<dynamic>?)
?.map((e) => Campaign.fromJson(e as Map<String, dynamic>))
.toList(),
pledgers: json['users'] == null
? null
: (json['users'] as List<dynamic>?)
?.map(
(e) => User.fromJson(e as Map<String, dynamic>, fromOrg: true),
)
.toList(),
startDate: json['startDate'] != null
? DateTime.parse(json['startDate'] as String)
: null,
endDate: json['endDate'] != null
? DateTime.parse(json['endDate'] as String)
: null,
amount: amount,
currency: currency,
createdAt: json['createdAt'] != null
? DateTime.parse(json['createdAt'] as String)
: null,
updatedAt: json['updatedAt'] != null
? DateTime.parse(json['updatedAt'] as String)
: null,
);
}

Comment on lines +377 to +380
onPressed: () {
model.deletePledge(pledge.id!, widget.campaign.id!);
Navigator.of(context).pop();
},
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Handle deletion success before closing the dialog.

The confirmation dialog is closed immediately after initiating the deletion. It would be better to wait for the deletion operation to complete and handle any errors before closing the dialog.

Modify the onPressed callback to handle the deletion result:

onPressed: () async {
  final success = await model.deletePledge(pledge.id!, widget.campaign.id!);
  if (success) {
    Navigator.of(context).pop();
    // Optionally, refresh the pledges list or show a success message
  } else {
    // Stay in the dialog and display an error message
  }
},

Ensure that deletePledge returns a Future<bool> indicating whether the deletion was successful.

Comment on lines +31 to +34
onModelReady: (model) {
model.fetchPledges(widget.campaign.id!);
model.getCurrentOrgUsersList();
},
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Handle potential null values for widget.campaign.id.

While the null assertion operator (!) is used, it's safer to ensure that widget.campaign.id is not null before calling methods that depend on it to prevent runtime exceptions.

You can add a null check before using the ID:

onModelReady: (model) {
  final campaignId = widget.campaign.id;
  if (campaignId != null) {
    model.fetchPledges(campaignId);
  } else {
    // Handle the null case appropriately, perhaps show an error message
  }
  model.getCurrentOrgUsersList();
},

Alternatively, ensure that Campaign's id is non-nullable if it's required.

Comment on lines 136 to 139
final totalRaised = model.pledges.fold(0, (int sum, pledge) {
const amountDonated = 0;
return sum + amountDonated;
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix the incorrect calculation of totalRaised in the progress indicator.

The totalRaised variable is currently calculated using a constant amountDonated = 0, which means totalRaised will always be zero. This is likely a mistake. You should sum the actual raised amounts from the pledges. If the Pledge model has an amountRaised or amountDonated field, use that field in the calculation.

Apply this diff to fix the totalRaised calculation:

-    final totalRaised = model.pledges.fold(0, (int sum, pledge) {
-      const amountDonated = 0;
-      return sum + amountDonated;
-    });
+    final totalRaised = model.pledges.fold(0, (int sum, pledge) {
+      final amountDonated = pledge.amountDonated ?? 0;
+      return sum + amountDonated;
+    });

If the Pledge model does not have an amountDonated field, consider adding it or adjusting the logic accordingly to reflect the actual raised amounts.

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 374 to 376
double _fetchRaisedAmount(Campaign campaign) {
return 10.0;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Implement _fetchRaisedAmount method to retrieve actual raised amount

The method _fetchRaisedAmount currently returns a hardcoded value of 10.0. This is likely a placeholder. Please implement this method to fetch the actual raised amount for the campaign.

Would you like assistance in implementing this method?

Comment on lines 331 to 332
// Navigate to campaign details
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Implement navigation to campaign details page

The onPressed callback for the 'Details' button is currently empty or contains a placeholder comment. Please implement the navigation to the campaign details page when the button is pressed.

Would you like assistance in implementing the navigation?

Copy link
Member

@noman2002 noman2002 left a comment

Choose a reason for hiding this comment

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

Do the suggested changes by coderabbit.

Copy link

@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: 7

🧹 Outside diff range and nitpick comments (18)
test/utils/fund_queries_test.dart (2)

192-194: Remove extra whitespace.

There's unnecessary whitespace after _id field.


4-213: Consider enhancing test coverage.

While the basic query string validation is good, consider adding the following test cases:

  1. Error scenarios (e.g., invalid dates, negative amounts)
  2. Variable type validation for mutations
  3. Edge cases (e.g., empty user lists, maximum amounts)

Example test structure:

test('createPledge should validate amount is positive', () {
  expect(
    () => FundQueries().validateCreatePledgeVariables(amount: -100),
    throwsA(isA<ArgumentError>()),
  );
});
test/service_tests/fund_service_test.dart (3)

1-10: Consider addressing dynamic calls instead of ignoring the lint.

The avoid_dynamic_calls lint is disabled file-wide. While this works, it's better to address dynamic calls explicitly to maintain type safety. Consider using strong typing for GraphQL responses using generated types or explicit interfaces.


18-59: Enhance test coverage with edge cases.

While the basic happy path is covered, consider adding tests for:

  • Empty fund list response
  • Error responses from the GraphQL endpoint
  • Pagination handling (if supported)
  • Invalid fund data structure

189-220: Expand updatePledge test scenarios.

The test only covers updating the pledge amount. Consider adding tests for:

  • Updating other pledge properties
  • Attempting to update non-existent pledges
  • Validation of update permissions
  • Concurrent update handling
.github/workflows/pull-request.yml (1)

Line range hint 1-300: Consider enhancing workflow for breaking changes.

Given that this PR introduces breaking changes (as mentioned in the PR objectives), consider enhancing the workflow to:

  1. Automatically add a "breaking-change" label to PRs that modify certain paths
  2. Require additional approvals for breaking changes
  3. Add automated checks for version bumps in relevant files

Example workflow enhancement:

jobs:
  detect-breaking-changes:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Check for breaking changes
        run: |
          # Check for changes in critical paths
          BREAKING_CHANGES=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep -E '^(lib/services/|lib/models/)')
          if [ ! -z "$BREAKING_CHANGES" ]; then
            echo "::set-output name=has_breaking::true"
          fi
      - name: Label PR
        if: steps.check.outputs.has_breaking == 'true'
        uses: actions/github-script@v6
        with:
          script: |
            github.rest.issues.addLabels({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              labels: ['breaking-change']
            })
test/helpers/test_helpers.dart (2)

239-251: Consider adding specific mock behaviors

While the function correctly implements the basic service registration pattern, consider adding specific mock behaviors for common FundService operations, similar to how ChatService and EventService are configured. This would make the mock more useful for testing scenarios.

Example addition:

 FundService getAndRegisterFundService() {
   _removeRegistrationIfExists<FundService>();
   final service = MockFundService();
+  // Mock common operations
+  when(service.getFundsList()).thenAnswer((_) async => []);
+  when(service.createFund(any)).thenAnswer((_) async => true);
   locator.registerSingleton<FundService>(service);
   return service;
 }

Line range hint 915-929: Add FundService to unregisterServices()

The FundService is registered but not unregistered. Add it to the unregisterServices() function to ensure proper cleanup during tests.

Apply this diff:

 void unregisterServices() {
   locator.unregister<NavigationService>();
   locator.unregister<GraphqlConfig>();
   locator.unregister<UserConfig>();
   locator.unregister<PostService>();
   locator.unregister<EventService>();
   locator.unregister<MultiMediaPickerService>();
   locator.unregister<Connectivity>();
   locator.unregister<ConnectivityService>();
   locator.unregister<DataBaseMutationFunctions>();
   locator.unregister<OrganizationService>();
   locator.unregister<CommentService>();
   locator.unregister<ImageCropper>();
   locator.unregister<ImagePicker>();
+  locator.unregister<FundService>();
 }
lib/view_model/after_auth_view_models/funds_view_models/organisation_fund_view_model.dart (6)

93-93: Typographical Error: Correct 'pldeges' to 'pledges' in Comment

There's a misspelling in the documentation comment. 'pldeges' should be 'pledges'.

Apply this diff:

-/// getter for pldeges sorting option.
+/// Getter for pledges sorting option.

168-168: Typographical Error: Correct 'methoud' to 'method' in Documentation Comments

There are several instances where 'methoud' is misspelled in the documentation comments. Please correct 'methoud' to 'method' for clarity.

Apply this diff for the affected lines:

-/// methoud to sort funds.
+/// Method to sort funds.

-/// methoud to sort Campaigns.
+/// Method to sort Campaigns.

-/// methoud to sort Pledges.
+/// Method to sort Pledges.

-/// methoud to search Funds.
+/// Method to search Funds.

-/// methoud to search Campaigns.
+/// Method to search Campaigns.

-/// methoud to apply the filtering logic for funds.
+/// Method to apply the filtering logic for funds.

-/// methoud to apply the filtering logic for Campaigns.
+/// Method to apply the filtering logic for Campaigns.

Also applies to: 182-182, 198-198, 214-214, 227-227, 261-261, 274-274


137-140: Use a Logging Framework Instead of 'print' Statements for Error Handling

Using print statements is not recommended for production code as it does not provide sufficient control over logging levels and outputs. Consider using a logging package to handle error messages.

Apply this diff to replace print statements with a logging mechanism:

} catch (e) {
-  print('Error fetching funds: $e');
+  logger.error('Error fetching funds', e);
  _isFetchingFunds = false;
  notifyListeners();
}

...

} catch (e) {
-  print('Error fetching campaigns: $e');
+  logger.error('Error fetching campaigns', e);
  _isFetchingCampaigns = false;
  notifyListeners();
}

...

} catch (e) {
-  print('Error creating pledge: $e');
+  logger.error('Error creating pledge', e);
}

...

} catch (e) {
-  print('Error updating pledge: $e');
+  logger.error('Error updating pledge', e);
}

...

} catch (e) {
-  print('Error updating pledge: $e');
+  logger.error('Error deleting pledge', e);
}

Ensure that a suitable logging package is imported and configured in your project.

Also applies to: 162-165, 359-360, 375-376, 393-393


293-294: Missing Return Type Documentation

The method fetchPledges lacks documentation for the return type in the comments. For consistency and clarity, please include the return type in the method documentation.

Apply this diff:

 /// **returns**:
-///   None
+///   Future<void>

393-393: Incorrect Error Message in 'deletePledge' Method

In the deletePledge method, the error message in the print statement references updating a pledge instead of deleting one. This can cause confusion during debugging.

Apply this diff:

-print('Error updating pledge: $e');
+print('Error deleting pledge: $e');

293-294: Specify Return Types in Documentation

Several methods lack proper return type documentation in their comments. Including the return type enhances code readability and helps other developers understand the method's behavior.

Review the method comments and ensure the return types are correctly specified.

lib/views/after_auth_screens/funds/fundraising_campaigns_screen.dart (3)

141-141: Localize user-facing strings

The text 'No campaigns for this fund.' is hardcoded and displayed to the user. Please use the AppLocalizations class to localize this string for internationalization support.


359-359: Localize user-facing strings

The text 'Not set' is hardcoded and displayed to the user. Please use the AppLocalizations class to localize this string for internationalization support.


366-366: Localize user-facing strings

The text 'Unknown' is hardcoded and displayed to the user. Please use the AppLocalizations class to localize this string for internationalization support.

test/helpers/test_helpers.mocks.dart (1)

Line range hint 1-4701: Consider Excluding Generated Files from Version Control

The file test_helpers.mocks.dart appears to be auto-generated by Mockito. Including generated files in version control can lead to merge conflicts, increased repository size, and maintenance overhead. It's recommended to exclude such files by adding them to .gitignore and regenerating them as part of the build or test process.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 070a162 and e4c97ab.

📒 Files selected for processing (9)
  • .github/workflows/pull-request.yml (1 hunks)
  • lib/services/fund_service.dart (1 hunks)
  • lib/view_model/after_auth_view_models/funds_view_models/organisation_fund_view_model.dart (1 hunks)
  • lib/views/after_auth_screens/funds/fund_pledges_screen.dart (1 hunks)
  • lib/views/after_auth_screens/funds/fundraising_campaigns_screen.dart (1 hunks)
  • test/helpers/test_helpers.dart (4 hunks)
  • test/helpers/test_helpers.mocks.dart (15 hunks)
  • test/service_tests/fund_service_test.dart (1 hunks)
  • test/utils/fund_queries_test.dart (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/services/fund_service.dart
  • lib/views/after_auth_screens/funds/fund_pledges_screen.dart
🔇 Additional comments (11)
test/utils/fund_queries_test.dart (2)

1-3: LGTM! Clean and appropriate imports.


4-5: LGTM! Well-organized test structure.

test/service_tests/fund_service_test.dart (2)

74-74: Verify sorting behavior in getCampaigns test.

The test includes pledgeOrderBy: 'endDate_DESC' but doesn't validate if the results are actually sorted by end date. Consider adding assertions to verify the sorting order.


1-250: Overall test implementation looks good with room for improvement.

The test suite provides a solid foundation for testing the FundService functionality. The structure is consistent and follows testing best practices. To make it more robust, consider:

  1. Adding error handling tests across all methods
  2. Including edge cases and boundary conditions
  3. Testing authorization and permission scenarios
  4. Adding integration tests for complex workflows
.github/workflows/pull-request.yml (1)

51-51: LGTM! File exclusions align with new fundraising features.

The addition of fundraising-related files to the line counting exclusions is consistent with the PR's objective of implementing fundraising campaigns.

test/helpers/test_helpers.dart (3)

23-23: LGTM: Import statement is correctly placed

The import for fund_service.dart follows the established pattern and is correctly placed alphabetically among other service imports.


75-75: LGTM: MockSpec follows established pattern

The MockSpec for FundService is correctly configured with OnMissingStub.returnDefault and follows the same pattern as other service mocks.


907-907: LGTM: Service registration is correctly added

The FundService registration is properly added to the registerServices() function.

lib/view_model/after_auth_view_models/funds_view_models/organisation_fund_view_model.dart (3)

53-54: Inconsistent Naming Convention: Rename 'parentcampaignId' to 'parentCampaignId'

Variable names should follow camelCase conventions for readability and consistency. The variable parentcampaignId should be renamed to parentCampaignId.


115-118: Await Asynchronous Call in 'initialise()' Method

The method getCurrentOrgUsersList() is asynchronous but is not awaited in the initialise() method. This may lead to unexpected behavior if the subsequent methods depend on its completion. Consider making initialise() asynchronous and awaiting the call.


343-345: Undefined Variables: 'organizationService' and 'userConfig' Are Not Declared

The variables organizationService and userConfig are used but not defined within the scope of this class. This will lead to runtime errors.

Comment on lines +99 to +100
getFundraisingCampaigns(where: \$where, campaignOrderby: \$campaignOrderBy) {
_id
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

⚠️ Potential issue

Fix inconsistent parameter naming in GraphQL queries

The review is correct. There's an inconsistency in the parameter naming where:

  • The variable is defined as $campaignOrderBy (correct camelCase)
  • But used as campaignOrderby (incorrect) in the query parameter

This inconsistency appears in both:

  • lib/utils/fund_queries.dart
  • test/utils/fund_queries_test.dart
🔗 Analysis chain

Fix typo in 'campaignOrderby' parameter.

The parameter name should be 'campaignOrderBy' to maintain consistency with camelCase naming convention and match other similar parameters.


Let me gather more context about the GraphQL schema and queries to verify this thoroughly.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for consistent parameter naming
rg "campaignOrderBy|campaignOrderby" --type dart

Length of output: 429


Script:

#!/bin/bash
# Search for GraphQL schema definitions or query definitions related to fundraising campaigns
rg "type.*Campaign|input.*Campaign|query.*Campaign|fragment.*Campaign" -A 5

# Also check for any other occurrences of similar parameter naming patterns
ast-grep --pattern 'getFundraisingCampaigns($$$)'

Length of output: 2604

Comment on lines +19 to +21
name
refrenceNumber
taxDeductible
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

⚠️ Potential issue

The field 'refrenceNumber' is not part of the Fund model and should be removed

Looking at the Fund model definition, there is no field named 'referenceNumber' or 'refrenceNumber'. The field appears to be incorrectly included in the test query and should be removed as it's not part of the model's schema.

  • Remove the refrenceNumber field from test/utils/fund_queries_test.dart
🔗 Analysis chain

Fix typo in 'refrenceNumber' field.

The field name contains a typo that should be corrected to 'referenceNumber'.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if this typo exists in other files
rg "refrenceNumber" --type dart

Length of output: 429


Script:

#!/bin/bash
# Search for both spellings to see all occurrences
rg "refr?erenceNumber" --type dart

Length of output: 107


Script:

#!/bin/bash
# Let's check the schema or type definitions to see the correct field name
rg -A 5 "type Fund" --type graphql || rg -A 5 "class Fund" --type dart

Length of output: 2794


Script:

#!/bin/bash
# Let's check the Fund model definition to see the correct field name
cat lib/models/funds/fund.dart

Length of output: 3050

Comment on lines +108 to +154
test('Test getPledgesByCampaign Method', () async {
final dataBaseMutationFunctions = locator<DataBaseMutationFunctions>();
const campaignId = "campaign_123";
final query = FundQueries().fetchPledgesByCampaign();

when(
dataBaseMutationFunctions.gqlAuthQuery(
query,
variables: {
'where': {
'id': campaignId,
},
'pledgeOrderBy': 'endDate_DESC',
},
),
).thenAnswer(
(_) async => QueryResult(
options: QueryOptions(document: gql(query)),
data: {
'getFundraisingCampaigns': [
{
'pledges': [
{
'_id': 'pledge_1',
'amount': 100,
},
{
'_id': 'pledge_2',
'amount': 200,
},
],
},
],
},
source: QueryResultSource.network,
),
);

final service = FundService();
final pledges = await service.getPledgesByCampaign(campaignId);

expect(pledges.length, 2);
expect(pledges[0].id, 'pledge_1');
expect(pledges[0].amount, 100);
expect(pledges[1].id, 'pledge_2');
expect(pledges[1].amount, 200);
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Expand pledge property validation.

The test only validates pledge ID and amount. Consider testing additional important pledge properties that might be relevant for fundraising campaigns, such as:

  • Pledge status
  • Creation date
  • End date
  • Associated user information

Comment on lines +156 to +187
test('Test createPledge Method', () async {
final dataBaseMutationFunctions = locator<DataBaseMutationFunctions>();
final query = FundQueries().createPledge();
final variables = {
'campaignId': 'campaign_123',
'amount': 50,
};

when(
dataBaseMutationFunctions.gqlAuthMutation(
query,
variables: variables,
),
).thenAnswer(
(_) async => QueryResult(
options: QueryOptions(document: gql(query)),
data: {
'createPledge': {
'id': 'pledge_123',
'amount': 50,
},
},
source: QueryResultSource.network,
),
);

final service = FundService();
final result = await service.createPledge(variables);

expect(result.data?['createPledge']['id'], 'pledge_123');
expect(result.data?['createPledge']['amount'], 50);
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add input validation tests for createPledge.

Consider adding tests for:

  • Minimum/maximum pledge amounts
  • Invalid campaign IDs
  • Required fields validation
  • Duplicate pledge detection (if applicable)

Comment on lines +222 to +248
test('Test deletePledge Method', () async {
final dataBaseMutationFunctions = locator<DataBaseMutationFunctions>();
final query = FundQueries().deletePledge();
const pledgeId = 'pledge_123';

when(
dataBaseMutationFunctions.gqlAuthMutation(
query,
variables: {'id': pledgeId},
),
).thenAnswer(
(_) async => QueryResult(
options: QueryOptions(document: gql(query)),
data: {
'deletePledge': {
'id': pledgeId,
},
},
source: QueryResultSource.network,
),
);

final service = FundService();
final result = await service.deletePledge(pledgeId);

expect(result.data?['deletePledge']['id'], pledgeId);
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling tests for deletePledge.

Consider adding tests for:

  • Deleting non-existent pledges
  • Permission-based deletion restrictions
  • Cascade deletion effects (if any)
  • Deletion failure scenarios

Comment on lines 306 to 308
_isFetchingPledges = false;
notifyListeners();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Empty Catch Block in 'fetchPledges' Method

The catch block in the fetchPledges method is empty. This can suppress exceptions and make debugging difficult. Proper error handling should be implemented.

Apply this diff to handle errors:

} catch (e) {
+  logger.error('Error fetching pledges', e);
  _isFetchingPledges = false;
  notifyListeners();
}

Ensure that you have a logging mechanism in place.

Committable suggestion skipped: line range outside the PR's diff.

children: [
LinearPercentIndicator(
lineHeight: 20,
percent: progress,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure progress percentage is within bounds

The progress value may exceed 1.0 if the raisedAmount surpasses the goalAmount, which could cause unintended behavior in the LinearPercentIndicator. Consider clamping the progress value between 0.0 and 1.0 to prevent any issues.

Apply this diff to clamp the progress value:

- final progress = goalAmount > 0 ? raisedAmount / goalAmount : 0.0;
+ final progress = goalAmount > 0 ? (raisedAmount / goalAmount).clamp(0.0, 1.0) : 0.0;

Committable suggestion skipped: line range outside the PR's diff.

@palisadoes palisadoes added the ignore-sensitive-files-pr The contributor has a legitimate reason for editiing protected files label Nov 10, 2024
@palisadoes
Copy link
Contributor

Please fix the failing tests.

Copy link

@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: 16

🧹 Outside diff range and nitpick comments (15)
test/service_tests/image_service_test.dart (1)

141-174: Consider extracting magic numbers as constants

While the MIME type detection tests are well-structured and include proper cleanup, the magic numbers used for file signatures could be extracted as named constants for better readability and maintenance.

+ // File signature constants
+ static const List<int> JPEG_SIGNATURE = [0xFF, 0xD8, 0xFF, 0xE0];
+ static const List<int> PNG_SIGNATURE = [0x89, 0x50, 0x4E, 0x47];
+ static const List<int> UNKNOWN_SIGNATURE = [0x00, 0x00, 0x00, 0x00];

  test('correctly identifies JPEG image', () async {
-   final jpegBytes = [0xFF, 0xD8, 0xFF, 0xE0];
+   final jpegBytes = JPEG_SIGNATURE;
    // ... rest of the test
  });
lib/locator.dart (1)

Line range hint 1-189: Consider implementing caching for fundraising data.

Since this is part of a larger fundraising feature, consider these architectural recommendations:

  1. Implement caching in FundService for frequently accessed campaign data to reduce API calls
  2. Consider implementing offline support through the existing CacheService
  3. Add error boundaries in the ViewModel to handle API failures gracefully
  4. Consider implementing a separate service for pledge-specific operations if the feature grows

This will help ensure the fundraising feature remains performant and maintainable as it scales.

test/view_model_tests/after_auth_view_model_tests/fund_view_models/fund_view_model_test.dart (4)

14-22: Consider adding error handling in setup and teardown

While the setup follows best practices, consider adding try-catch blocks to handle potential initialization failures gracefully.

 setUpAll(() {
+  try {
     TestWidgetsFlutterBinding.ensureInitialized();
     testSetupLocator();
     registerServices();
+  } catch (e) {
+    print('Test setup failed: $e');
+    rethrow;
+  }
 });

31-40: Enhance initialization test coverage

Consider adding test cases for:

  • Error handling during initialization
  • State after failed initialization
  • Re-initialization scenarios
test('Test initialization failure handling', () async {
  // Mock a failure scenario
  when(mockFundService.someFailingMethod()).thenThrow(Exception('Init failed'));
  
  model.initialise();
  
  expect(model.hasError, true);
  expect(model.errorMessage, isNotEmpty);
});

270-309: Add pagination and performance tests for organization users

Consider adding tests for:

  • Paginated user list fetching
  • Performance with large user lists
  • Caching behavior
test('Test paginated org users fetch', () async {
  final mockUsers = List.generate(100, 
    (i) => User(id: '$i', firstName: 'User $i'));
  
  when(organizationService.getOrgMembersListPaginated(
    userConfig.currentOrg.id!,
    page: 1,
    pageSize: 20
  )).thenAnswer((_) async => mockUsers.take(20).toList());
  
  await model.getCurrentOrgUsersListPaginated(page: 1);
  
  expect(model.orgMembersList.length, 20);
  expect(model.hasMoreUsers, true);
});

1-310: Overall test coverage is good but could be enhanced

The test suite provides good coverage of the basic functionality, but consider developing a comprehensive test plan that includes:

  1. Error handling and recovery scenarios
  2. Business rule validation
  3. Performance testing with large datasets
  4. Security and authorization testing
  5. Integration tests with dependent services

This will ensure the robustness of the fundraising campaign feature.

Would you like assistance in creating a comprehensive test plan that covers these aspects?

lib/views/after_auth_screens/funds/fund_pledges_screen.dart (2)

306-306: Remove debug print statements from production code.

Debug print statements should be removed before pushing to production.

-    print(model.filteredPledges.length);

Also applies to: 329-329


217-218: Enhance search functionality with case-insensitivity and debouncing.

Consider improving the search functionality by:

  1. Adding case-insensitive search
  2. Implementing debouncing to prevent excessive API calls
-              onChanged: (value) => model.searchPledgers(value),
+              onChanged: (value) => model.searchPledgers(value.toLowerCase()),

Consider adding a debounce utility:

Timer? _debounce;

void _onSearchChanged(String value) {
  if (_debounce?.isActive ?? false) _debounce!.cancel();
  _debounce = Timer(const Duration(milliseconds: 500), () {
    model.searchPledgers(value.toLowerCase());
  });
}
lib/views/after_auth_screens/funds/funds_screen.dart (1)

11-11: Complete the placeholder documentation comments

Several documentation comments are placeholders (e.g., "/// a_line_ending_with_end_punctuation."). Please provide meaningful descriptions for classes and methods to enhance code readability and maintainability.

Also applies to: 136-136, 140-140, 240-240, 287-287

lib/view_model/after_auth_view_models/funds_view_models/fund_view_model.dart (3)

55-55: Inconsistent variable naming: Use camelCase for parentCampaignId

The variable parentcampaignId does not follow Dart's camelCase naming convention. It should be renamed to parentCampaignId for consistency.

Apply this diff to correct the variable name:

-late String parentcampaignId;
+late String parentCampaignId;

Ensure that all usages of parentcampaignId are updated accordingly throughout the codebase.


62-62: Unnecessary use of late with immediately initialized variable

The variable orgMembersList is declared as late but is initialized immediately. In Dart, the late modifier is unnecessary when a variable is assigned at declaration.

Apply this diff to remove the unnecessary late modifier:

-late List<User> orgMembersList = [];
+List<User> orgMembersList = [];

173-173: Correct typographical errors in method comments

There are typographical errors in the method comments where "methoud" is used instead of "method".

Apply this diff to correct the spelling:

- /// methoud to sort funds.
+ /// Method to sort funds.

- /// methoud to sort Campaigns.
+ /// Method to sort Campaigns.

- /// methoud to sort Pledges.
+ /// Method to sort Pledges.

- /// methoud to search Funds.
+ /// Method to search Funds.

- /// methoud to search Campaigns.
+ /// Method to search Campaigns.

- /// methoud to apply the filtering logic for funds.
+ /// Method to apply the filtering logic for funds.

- /// methoud to apply the filtering logic for Campaigns.
+ /// Method to apply the filtering logic for Campaigns.

Also applies to: 187-187, 203-203, 219-219, 232-232, 262-262, 275-275

test/view_model_tests/main_screen_view_model_test.dart (3)

176-176: Clarify or remove ambiguous comment

The comment // May need to change is ambiguous and doesn't provide useful information. Consider removing it or providing more specific context.


179-179: Correct grammatical error in test description

The test description is grammatically incorrect. Please update it for clarity.

Apply this diff to correct the test description:

-              "When an index is passed that, current index should equal that index",
+              "When an index is passed, current index should equal that index",

186-186: Improve test description for clarity

The test description could be clearer. Consider rephrasing it for better understanding.

Apply this diff to improve the test description:

-          test("When called function should notify listeners of tab change", () {
+          test("Function should notify listeners of tab change when called", () {
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between e4c97ab and 4230b69.

📒 Files selected for processing (12)
  • lib/locator.dart (5 hunks)
  • lib/view_model/after_auth_view_models/funds_view_models/fund_view_model.dart (1 hunks)
  • lib/view_model/main_screen_view_model.dart (3 hunks)
  • lib/views/after_auth_screens/funds/fund_pledges_screen.dart (1 hunks)
  • lib/views/after_auth_screens/funds/fundraising_campaigns_screen.dart (1 hunks)
  • lib/views/after_auth_screens/funds/funds_screen.dart (1 hunks)
  • lib/widgets/add_pledge_dialogue_box.dart (1 hunks)
  • lib/widgets/update_pledge_dialogue_box.dart (1 hunks)
  • test/helpers/test_locator.dart (4 hunks)
  • test/service_tests/image_service_test.dart (4 hunks)
  • test/view_model_tests/after_auth_view_model_tests/fund_view_models/fund_view_model_test.dart (1 hunks)
  • test/view_model_tests/main_screen_view_model_test.dart (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • lib/view_model/main_screen_view_model.dart
  • lib/widgets/update_pledge_dialogue_box.dart
  • test/helpers/test_locator.dart
🔇 Additional comments (14)
test/service_tests/image_service_test.dart (4)

23-28: LGTM: Mock implementation is well-structured

The mock implementation properly handles both success and error cases with appropriate return values.


38-38: LGTM: Improved test organization

The renamed test group better reflects the broader scope of the test suite.


88-113: Well-structured tests, but consider using test fixtures

The test organization is good, covering both success and error cases. However, the hardcoded asset path issue mentioned in the previous review still needs to be addressed.


116-139: LGTM: Comprehensive test coverage for base64 decoding

The tests thoroughly cover all scenarios including valid base64 strings, strings without MIME type prefixes, and invalid inputs.

lib/locator.dart (4)

11-11: LGTM: Imports are properly organized.

The new imports for FundService and FundViewModel are correctly placed and necessary for the fundraising feature implementation.

Also applies to: 37-37


95-97: Skipping duplicate comment about typo.

A previous review already identified the typo in 'fundServcie'.


130-130: LGTM: Service registration follows best practices.

The FundService is correctly registered as a lazy singleton, consistent with other service registrations in the file.


160-160: Verify ViewModel integration with existing components.

The FundViewModel registration follows the correct pattern. However, since this PR introduces breaking changes, we should verify its integration.

✅ Verification successful

Let me analyze the FundViewModel integration further by checking its base class and dependencies.


Based on the comprehensive analysis of the FundViewModel implementation and its integration, I can now provide the final response:

FundViewModel integration is properly implemented and follows established patterns

The verification shows that:

  • FundViewModel correctly extends BaseModel and follows the MVVM pattern
  • It's properly integrated with all necessary fund management screens and widgets
  • All dependencies are correctly imported and used across the codebase
  • The factory registration in locator.dart matches the test locator setup
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify FundViewModel integration and usage

# Check for FundViewModel usage in the codebase
echo "Checking FundViewModel usage patterns:"
rg -p "FundViewModel" --type dart

# Check for potential breaking changes in existing view models
echo "Checking for potential breaking changes in existing ViewModels:"
rg -p "extends.*ViewModel" --type dart

Length of output: 2158


Script:

#!/bin/bash
# Check BaseModel implementation and potential breaking changes
echo "Checking BaseModel implementation:"
rg -p "class BaseModel" -A 10 --type dart

# Check FundViewModel implementation details
echo "Checking FundViewModel implementation:"
ast-grep --pattern 'class FundViewModel extends BaseModel {
  $$$
}'

# Check for any direct dependencies in FundViewModel
echo "Checking FundViewModel dependencies:"
rg -p "import.*fund_view_model" --type dart

Length of output: 45031

lib/widgets/add_pledge_dialogue_box.dart (3)

1-26: LGTM! Well-structured class with proper documentation.

The class structure follows Flutter best practices with clear documentation and proper parameter definitions.


28-34: Skip comment as previously covered.

A past review comment already addresses the TextEditingController disposal issue.


241-271: LGTM! Well-implemented helper method.

The _buildPopupMenuItem method is well documented and efficiently handles user avatar display with proper null checks.

lib/views/after_auth_screens/funds/fundraising_campaigns_screen.dart (3)

1-25: LGTM! Well-structured class declaration with proper documentation.

The imports are appropriate, and the class is well-documented with clear parameter declarations.


434-454: LGTM! Well-implemented date formatting and status calculation.

The date formatting and campaign status calculation are properly implemented with good error handling.


175-175: ⚠️ Potential issue

Implement actual raised amount calculation

The raised amount is hardcoded to 0.0. This should be calculated based on actual pledges.

Comment on lines 42 to 89
group('Fund Tests', () {
test('Test fetchFunds success', () async {
final mockFundService = locator<FundService>();
final mockFunds = [
Fund(id: '1', name: 'Fund 1'),
Fund(id: '2', name: 'Fund 2'),
];

when(mockFundService.getFunds(orderBy: 'createdAt_DESC'))
.thenAnswer((_) async => mockFunds);

await model.fetchFunds();

expect(model.funds.length, 2);
expect(model.funds[0].id, '1');
expect(model.isFetchingFunds, false);
verify(mockFundService.getFunds(orderBy: 'createdAt_DESC')).called(1);
});

test('Test searchFunds filters correctly', () async {
final mockFundService = locator<FundService>();
final mockFunds = [
Fund(id: '1', name: 'Education Fund'),
Fund(id: '2', name: 'Health Fund'),
];

when(mockFundService.getFunds(orderBy: 'createdAt_DESC'))
.thenAnswer((_) async => mockFunds);

await model.fetchFunds();
model.searchFunds('health');

expect(model.filteredFunds.length, 1);
expect(model.filteredFunds[0].name, 'Health Fund');
});

test('Test sortFunds changes sort option and refetches', () async {
final mockFundService = locator<FundService>();

when(mockFundService.getFunds(orderBy: 'name_ASC'))
.thenAnswer((_) async => []);

model.sortFunds('name_ASC');

expect(model.fundSortOption, 'name_ASC');
verify(mockFundService.getFunds(orderBy: 'name_ASC')).called(1);
});
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling test cases for Fund operations

The Fund tests should include scenarios for:

  • Network failures during fetch
  • Empty search results
  • Invalid sort options
  • Service exceptions
test('Test fetchFunds handles network error', () async {
  final mockFundService = locator<FundService>();
  
  when(mockFundService.getFunds(orderBy: 'createdAt_DESC'))
      .thenThrow(NetworkException());
      
  await model.fetchFunds();
  
  expect(model.isFetchingFunds, false);
  expect(model.hasError, true);
  expect(model.funds, isEmpty);
});

Comment on lines +91 to +141
group('Campaign Tests', () {
test('Test fetchCampaigns success', () async {
final mockFundService = locator<FundService>();
final mockCampaigns = [
Campaign(id: '1', name: 'Campaign 1'),
Campaign(id: '2', name: 'Campaign 2'),
];

when(mockFundService.getCampaigns('1', orderBy: 'endDate_DESC'))
.thenAnswer((_) async => mockCampaigns);

await model.fetchCampaigns('1');

expect(model.campaigns.length, 2);
expect(model.campaigns[0].id, '1');
expect(model.isFetchingCampaigns, false);
verify(mockFundService.getCampaigns('1', orderBy: 'endDate_DESC'))
.called(1);
});

test('Test searchCampaigns filters correctly', () async {
final mockFundService = locator<FundService>();
final mockCampaigns = [
Campaign(id: '1', name: 'Summer Campaign'),
Campaign(id: '2', name: 'Winter Campaign'),
];

when(mockFundService.getCampaigns('1', orderBy: 'endDate_DESC'))
.thenAnswer((_) async => mockCampaigns);

await model.fetchCampaigns('1');
model.searchCampaigns('summer');

expect(model.filteredCampaigns.length, 1);
expect(model.filteredCampaigns[0].name, 'Summer Campaign');
});

test('Test sortCampaigns changes sort option and refetches', () async {
model.parentFundId = '1';
final mockFundService = locator<FundService>();

when(mockFundService.getCampaigns('1', orderBy: 'name_ASC'))
.thenAnswer((_) async => []);

model.sortCampaigns('name_ASC');
await model.fetchCampaigns('1');
expect(model.campaignSortOption, 'name_ASC');
verify(mockFundService.getCampaigns('1', orderBy: 'name_ASC'))
.called(1);
});
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add business rule validation tests for Campaigns

Consider adding tests for campaign-specific rules:

  • Campaign date validation (start date < end date)
  • Campaign target amount validation
  • Campaign status transitions
test('Test campaign date validation', () async {
  final invalidCampaign = Campaign(
    id: '1',
    startDate: DateTime(2024, 2, 1),
    endDate: DateTime(2024, 1, 1)
  );
  
  expect(() => model.validateCampaign(invalidCampaign), 
         throwsA(isA<ValidationException>()));
});

Comment on lines +143 to +268
),
).called(1);
});

test('Test createPledge success', () async {
final mockFundService = locator<FundService>();
final pledgeData = {'amount': 100, 'campaignId': '1'};
model.parentcampaignId = '1';

when(mockFundService.createPledge(pledgeData)).thenAnswer(
(_) async => QueryResult(
options: QueryOptions(document: gql('')),
data: {'createPledge': pledgeData},
source: QueryResultSource.network,
),
);

await model.createPledge(pledgeData);

verify(mockFundService.createPledge(pledgeData)).called(1);
verify(
mockFundService.getPledgesByCampaign(
'1',
orderBy: 'endDate_DESC',
),
).called(1);
});

test('Test updatePledge success', () async {
final mockFundService = locator<FundService>();
final pledgeData = {'id': '1', 'amount': 200};
model.parentcampaignId = '1';

when(mockFundService.updatePledge(pledgeData)).thenAnswer(
(_) async => QueryResult(
options: QueryOptions(document: gql('')),
data: {'updatePledge': pledgeData},
source: QueryResultSource.network,
),
);

await model.updatePledge(pledgeData);

verify(mockFundService.updatePledge(pledgeData)).called(1);
verify(
mockFundService.getPledgesByCampaign(
'1',
orderBy: 'endDate_DESC',
),
).called(1);
});

test('Test deletePledge success', () async {
final mockFundService = locator<FundService>();
model.parentcampaignId = '1';

when(mockFundService.deletePledge('1')).thenAnswer(
(_) async => QueryResult(
options: QueryOptions(document: gql('')),
data: {
'deletePledge': {'id': '1'},
},
source: QueryResultSource.network,
),
);

await model.deletePledge('1', '1');

verify(mockFundService.deletePledge('1')).called(1);
verify(
mockFundService.getPledgesByCampaign(
'1',
orderBy: 'endDate_DESC',
),
).called(1);
});

test('Test searchPledgers filters correctly', () async {
final mockFundService = locator<FundService>();
final mockPledges = [
Pledge(
id: '1',
pledgers: [User(id: userConfig.currentUser.id, firstName: 'John')],
),
Pledge(
id: '2',
pledgers: [User(id: userConfig.currentUser.id, firstName: 'Jane')],
),
];

when(mockFundService.getPledgesByCampaign('1', orderBy: 'endDate_DESC'))
.thenAnswer((_) async => mockPledges);

await model.fetchPledges('1');
model.searchPledgers('john');

expect(model.filteredPledges.length, 1);
expect(model.filteredPledges[0].pledgers![0].firstName, 'John');
});
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add validation and authorization tests for Pledges

The pledge tests should include:

  • Pledge amount validation (minimum/maximum)
  • User authorization checks
  • Duplicate pledge prevention
test('Test pledge amount validation', () async {
  final mockFundService = locator<FundService>();
  final invalidPledgeData = {'amount': -100, 'campaignId': '1'};
  
  expect(() => model.validatePledgeAmount(invalidPledgeData), 
         throwsA(isA<ValidationException>()));
});

test('Test duplicate pledge prevention', () async {
  // Setup existing pledge
  final existingPledge = {'amount': 100, 'campaignId': '1'};
  when(mockFundService.hasExistingPledge('1')).thenReturn(true);
  
  expect(() => model.createPledge(existingPledge),
         throwsA(isA<DuplicatePledgeException>()));
});

Comment on lines +184 to +192
final picked = await showDatePicker(
context: context,
initialDate:
_endDate ?? (_startDate ?? DateTime.now()),
firstDate: _startDate ?? DateTime.now(),
lastDate:
DateTime.now().add(const Duration(days: 365)),
);
if (picked != null) setState(() => _endDate = picked);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add date range validation.

The end date picker doesn't validate that the selected date is after the start date. While the firstDate parameter prevents selecting dates before the start date, explicit validation would provide better user feedback.

 onPressed: () async {
   final picked = await showDatePicker(
     context: context,
     initialDate: _endDate ?? (_startDate ?? DateTime.now()),
     firstDate: _startDate ?? DateTime.now(),
     lastDate: DateTime.now().add(const Duration(days: 365)),
   );
-  if (picked != null) setState(() => _endDate = picked);
+  if (picked != null) {
+    if (_startDate != null && picked.isBefore(_startDate!)) {
+      ScaffoldMessenger.of(context).showSnackBar(
+        const SnackBar(
+          content: Text('End date must be after start date'),
+        ),
+      );
+      return;
+    }
+    setState(() => _endDate = picked);
+  }
 },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
final picked = await showDatePicker(
context: context,
initialDate:
_endDate ?? (_startDate ?? DateTime.now()),
firstDate: _startDate ?? DateTime.now(),
lastDate:
DateTime.now().add(const Duration(days: 365)),
);
if (picked != null) setState(() => _endDate = picked);
final picked = await showDatePicker(
context: context,
initialDate:
_endDate ?? (_startDate ?? DateTime.now()),
firstDate: _startDate ?? DateTime.now(),
lastDate:
DateTime.now().add(const Duration(days: 365)),
);
if (picked != null) {
if (_startDate != null && picked.isBefore(_startDate!)) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('End date must be after start date'),
),
);
return;
}
setState(() => _endDate = picked);
}

Comment on lines +220 to +227
widget.onSubmit({
'campaignId': widget.campaignId,
'amount': double.parse(_amountController.text),
'startDate': DateFormat('yyyy-MM-dd').format(_startDate!),
'endDate': DateFormat('yyyy-MM-dd').format(_endDate!),
'userIds': _selectedPledgers.map((user) => user.id).toList(),
'currency': widget.model.donationCurrency,
});
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Based on the complete context, I can now generate the final response:

Amount validation and user handling need improvements

The current implementation has several security concerns:

  • Amount validation only checks for non-empty value without validating numeric format or range
  • Direct parsing of amount without sanitization could lead to runtime errors
  • No maximum limit check on the pledge amount
  • User IDs are properly handled through the UI selection mechanism

Required changes:

  • Add numeric format validation for amount: ^[0-9]+(\.[0-9]{1,2})?$
  • Add reasonable maximum amount limit
  • Add try-catch around double.parse() to handle parsing errors gracefully
  • Consider adding minimum amount validation
🔗 Analysis chain

Verify amount parsing and user ID handling.

The pledge submission involves financial data and user IDs. Let's verify:

  1. Amount parsing safety
  2. User ID sanitization in the API layer

Let me generate additional scripts to check for amount validation before parsing:

The previous results show the amount parsing and GraphQL mutation, but we need to verify if there's any validation before the parsing occurs to ensure security. These scripts will help us check for input validation in the widget.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for proper amount validation and user ID handling in the API layer

# Check for amount validation in the codebase
rg -A 5 "amount.*parse" --type dart

# Check for user ID sanitization in the API layer
rg -A 10 "createPledge|addPledge" --type dart

Length of output: 16733


Script:

#!/bin/bash
# Check for amount validation in the widget
ast-grep --pattern 'TextFormField $_controller = _amountController $$$'

# Check for any validation methods related to amount
rg -A 5 "validate.*amount|amount.*validate" --type dart

# Check the entire add_pledge_dialogue_box.dart file
cat lib/widgets/add_pledge_dialogue_box.dart

Length of output: 11301

Comment on lines +195 to +200
if (option != _campaignSortOption) {
_campaignSortOption = option;
if (_campaigns.isNotEmpty) {
fetchCampaigns(parentFundId);
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Ensure campaigns are fetched when sort option changes

In the sortCampaigns method, campaigns are only fetched if _campaigns is not empty. This means that if _campaigns is empty and the sort option changes, campaigns won't be fetched, and the user may not see the sorted data.

Consider fetching campaigns regardless of whether _campaigns is empty when the sort option changes:

if (option != _campaignSortOption) {
  _campaignSortOption = option;
-   if (_campaigns.isNotEmpty) {
      fetchCampaigns(parentFundId);
-   }
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (option != _campaignSortOption) {
_campaignSortOption = option;
if (_campaigns.isNotEmpty) {
fetchCampaigns(parentFundId);
}
}
if (option != _campaignSortOption) {
_campaignSortOption = option;
fetchCampaigns(parentFundId);
}

Comment on lines +254 to +258
_filteredPledges = _userPledges.where((pledge) {
return pledge.pledgers!.any(
(user) => user.firstName!.toLowerCase().contains(_pledgerSearchQuery),
);
}).toList();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid using the null assertion operator without null checks

In the searchPledgers method, you're using pledge.pledgers! and user.firstName! without checking for null values. This can lead to runtime exceptions if these values are null.

Consider adding null checks or using null-aware operators to handle potential null values safely.

Apply this diff to improve null safety:

_filteredPledges = _userPledges.where((pledge) {
+  final pledgers = pledge.pledgers;
+  if (pledgers == null) return false;
-   return pledge.pledgers!.any(
-     (user) => user.firstName!.toLowerCase().contains(_pledgerSearchQuery),
+  return pledgers.any(
+    (user) {
+      final firstName = user.firstName ?? '';
+      return firstName.toLowerCase().contains(_pledgerSearchQuery);
+    },
   );
}).toList();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
_filteredPledges = _userPledges.where((pledge) {
return pledge.pledgers!.any(
(user) => user.firstName!.toLowerCase().contains(_pledgerSearchQuery),
);
}).toList();
_filteredPledges = _userPledges.where((pledge) {
final pledgers = pledge.pledgers;
if (pledgers == null) return false;
return pledgers.any(
(user) {
final firstName = user.firstName ?? '';
return firstName.toLowerCase().contains(_pledgerSearchQuery);
},
);
}).toList();

Comment on lines +141 to +145
} catch (e) {
print('Error fetching funds: $e');
_isFetchingFunds = false;
notifyListeners();
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling to notify users of failures

In the catch block of fetchFunds, the error is only printed to the console. Consider adding user-friendly error messages or proper error handling mechanisms to notify users when fetching funds fails.

For example, you could set an error message variable and display it in the UI, or use a Snackbar to inform the user:

try {
  _funds = await _fundService.getFunds(orderBy: _fundSortOption);
  _applyFundFilters();
  _isFetchingFunds = false;
  notifyListeners();
} catch (e) {
-   print('Error fetching funds: $e');
+   _isFetchingFunds = false;
+   _errorMessage = 'Failed to fetch funds. Please try again later.';
+   notifyListeners();
}

Ensure similar error handling improvements are applied in other methods like fetchCampaigns and fetchPledges.

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 231 to 240
test('Test for showHome method', () {
final model = getModel();

await tester.pump();
model.showHome(
TargetFocus(
identify: "keyDrawerLeaveCurrentOrg",
keyTarget: MainScreenViewModel.keyDrawerLeaveCurrentOrg,
),
);
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add assertions to validate the showHome method

The test for the showHome method does not contain any assertions to verify the expected behavior. Consider adding assertions to confirm that showHome functions as intended.

Comment on lines 342 to 344
model2.targets[1].next!();
// ignore: avoid_dynamic_calls
model2.targets[5].next!();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure safe access to model2.targets elements

Accessing model2.targets[1] and model2.targets[5] without checking the list's length can lead to runtime exceptions if the list has fewer elements. Please add checks to ensure these indices exist before accessing them.

Apply this diff to add safety checks:

                  // ignore: avoid_dynamic_calls
-                 model2.targets[1].next!();
-                 // ignore: avoid_dynamic_calls
-                 model2.targets[5].next!();
+                 if (model2.targets.length > 1) {
+                   model2.targets[1].next!();
+                 }
+                 if (model2.targets.length > 5) {
+                   model2.targets[5].next!();
+                 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
model2.targets[1].next!();
// ignore: avoid_dynamic_calls
model2.targets[5].next!();
if (model2.targets.length > 1) {
model2.targets[1].next!();
}
if (model2.targets.length > 5) {
model2.targets[5].next!();
}

Copy link

@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

🧹 Outside diff range and nitpick comments (3)
test/views/after_auth_screens/profile/profile_page_test.dart (3)

Line range hint 1-43: Consider enhancing test setup for fundraising features.

Since this PR introduces fundraising campaigns functionality, the test setup should include mocking of fund-related services and dependencies.

Consider adding these to the createProfilePage helper:

 Widget createProfilePage({required MainScreenViewModel mainScreenViewModel}) {
+  // Mock fund-related services
+  final mockFundService = locator<FundService>();
+  final mockFundViewModel = locator<FundViewModel>();
+  
+  // Setup expected behaviors
+  when(mockFundService.getFunds()).thenAnswer((_) async => []);
+  when(mockFundViewModel.isLoading).thenReturn(false);
+
   return BaseView<AppLanguage>(

Line range hint 90-134: Enhance donation-related test assertions.

The donation test case could be more comprehensive to cover the new fundraising campaign features.

Consider adding these test scenarios:

 testWidgets('Test donate bottom sheet', (tester) async {
   // ... existing test code ...
+  // Test campaign-specific donations
+  final campaignDonateBtn = find.byKey(const Key('campaign_donate'));
+  expect(campaignDonateBtn, findsOneWidget);
+  await tester.tap(campaignDonateBtn);
+  await tester.pumpAndSettle();
+
+  // Verify campaign donation form
+  expect(find.text('Campaign Details'), findsOneWidget);
+  expect(find.byKey(const Key('campaign_amount')), findsOneWidget);
+
+  // Test pledge functionality
+  final pledgeBtn = find.byKey(const Key('make_pledge'));
+  await tester.tap(pledgeBtn);
+  await tester.pumpAndSettle();
+
+  // Verify pledge confirmation
+  expect(find.text('Pledge Confirmed'), findsOneWidget);
 });

Fix incorrect test verifications and timing issues in profile_page_test.dart

The test file has the following issues:

  • The settings page navigation test incorrectly verifies only the navigator key access instead of the actual navigation
  • Inconsistent widget stabilization using pump() vs pumpAndSettle() may cause flaky tests
  • Missing proper verification of navigation results

To fix:

  • Replace verify(navigationService.navigatorKey) with verify(navigationService.pushScreen('/appSettingsPage')) in the settings test
  • Use pumpAndSettle() consistently after actions that trigger animations or navigation
  • Add proper expectations after navigation actions
🔗 Analysis chain

Line range hint 44-134: Address failing tests mentioned in PR comments.

The PR comments indicate failing tests. Let's verify the test failures and their root causes.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for potential sources of test failures
echo "Checking for common test failure patterns..."

# Check for timing-sensitive tests
rg "pump|pumpAndSettle|Future|async" test/

# Check for widget key conflicts
rg "Key\(" test/

# Check for missing mocks
rg "when\(|verify\(" test/

Length of output: 541280


Script:

#!/bin/bash
# Search for PR comments mentioning test failures
echo "Searching for PR comments with test failures..."
gh pr view --json comments | jq -r '.comments[].body' | grep -A 5 -B 5 "test.*fail"

# Also check commit comments
echo "Searching commit comments..."
gh pr commits | xargs -I {} gh api /repos/PalisadoesFoundation/talawa/commits/{}/comments | jq -r '.[].body' | grep -A 5 -B 5 "test.*fail"

Length of output: 696


Script:

#!/bin/bash
# Check if test file exists and show its structure
echo "Checking test file location and content..."
find . -name "profile_page_test.dart" -type f -exec cat {} \;

# Check for any syntax errors in the test file
echo "Checking for potential syntax issues..."
find . -name "profile_page_test.dart" -type f -exec grep -n "^[[:space:]]*test" {} \;

Length of output: 6006

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 4230b69 and 75e2675.

📒 Files selected for processing (3)
  • lib/views/after_auth_screens/events/create_agenda_item_page.dart (1 hunks)
  • test/view_model_tests/after_auth_view_model_tests/fund_view_models/fund_view_model_test.dart (1 hunks)
  • test/views/after_auth_screens/profile/profile_page_test.dart (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/views/after_auth_screens/events/create_agenda_item_page.dart
  • test/view_model_tests/after_auth_view_model_tests/fund_view_models/fund_view_model_test.dart
🔇 Additional comments (1)
test/views/after_auth_screens/profile/profile_page_test.dart (1)

Line range hint 44-89: Verify test coverage after drawer test removal.

The removal of the left drawer test case might impact the overall test coverage. Consider adding alternative test cases to maintain coverage levels.

Copy link

codecov bot commented Nov 12, 2024

Codecov Report

Attention: Patch coverage is 23.82353% with 777 lines in your changes missing coverage. Please review.

Project coverage is 88.90%. Comparing base (19fe815) to head (75e2675).

Files with missing lines Patch % Lines
lib/widgets/update_pledge_dialogue_box.dart 0.00% 134 Missing ⚠️
.../after_auth_screens/funds/fund_pledges_screen.dart 0.00% 132 Missing ⚠️
...th_screens/funds/fundraising_campaigns_screen.dart 0.00% 128 Missing ⚠️
...b/views/after_auth_screens/funds/funds_screen.dart 0.85% 116 Missing ⚠️
lib/widgets/add_pledge_dialogue_box.dart 0.00% 114 Missing ⚠️
lib/widgets/pledge_card.dart 0.00% 89 Missing ⚠️
...view_models/funds_view_models/fund_view_model.dart 78.04% 27 Missing ⚠️
lib/services/fund_service.dart 74.24% 17 Missing ⚠️
lib/services/image_service.dart 73.07% 7 Missing ⚠️
...ews/after_auth_screens/feed/organization_feed.dart 50.00% 6 Missing ⚠️
... and 5 more
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #2635      +/-   ##
===========================================
- Coverage    95.52%   88.90%   -6.62%     
===========================================
  Files          187      199      +12     
  Lines         9884    10892    +1008     
===========================================
+ Hits          9442     9684     +242     
- Misses         442     1208     +766     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ignore-sensitive-files-pr The contributor has a legitimate reason for editiing protected files
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants