Skip to content

Conversation

@IronJam11
Copy link
Contributor

@IronJam11 IronJam11 commented Oct 26, 2025

  • Add verifiers section
  • Add recent trees pages section
  • Updated contract
  • Added planting proposals and verifications cection for organisation page
  • Tabulated organisation page
Screenshot 2025-10-25 at 12 53 02 AM Screenshot 2025-10-25 at 12 52 40 AM Screenshot 2025-10-25 at 12 52 46 AM Screenshot 2025-10-25 at 12 53 51 AM image image image Screenshot 2025-10-25 at 12 53 51 AM

Summary by CodeRabbit

Release Notes

  • New Features

    • Added user profile pages to view other users' tree NFTs and verifications.
    • Added organization support for NFT minting with member management.
    • Added Planter token checking in settings.
  • Improvements

    • Enhanced transaction dialogs with hash display and copy functionality.
    • Improved navigation with back buttons and loading indicators.
    • Enhanced tree details page with better verifier management and infinite scrolling.
    • Refined UI consistency and theming across the app.
    • Better wallet connection flow with clearer feedback.

@coderabbitai
Copy link

coderabbitai bot commented Oct 26, 2025

Walkthrough

This PR introduces an organization-based NFT minting flow, new user profile viewing capabilities, enhanced contract functions with pagination support, updated contract ABIs, and widespread theming improvements across UI components. Environment variables are updated to Pinata-specific keys.

Changes

Cohort / File(s) Summary
Environment & Configuration
.env.stencil
API credentials updated to use Pinata-specific naming: PINATA_API_KEY and PINATA_API_SECRET replace generic API_KEY and API_SECRET.
Routing
lib/main.dart, lib/utils/constants/route_constants.dart
Added two new routes: /user-profile/:address and /mint-nft-organisation with respective page imports and route builders; added corresponding path constants.
Scaffolding & Navigation Components
lib/components/universal_navbar.dart, lib/components/bottom_navigation_widget.dart, lib/widgets/basic_scaffold.dart
Extended UniversalNavbar and BaseScaffold with back button, loading indicator, and reload button capabilities; replaced hardcoded colors with theme-driven colors via getThemeColors(context).
Dialog Components
lib/components/transaction_dialog.dart, lib/components/wallet_connect_dialog.dart
Introduced new TransactionDialog for unified success/error feedback with optional transaction hash display; refactored WalletConnectDialog to use custom themed layout with wallet options and improved styling.
Theming & Constants
lib/utils/constants/ui/color_constants.dart, lib/utils/constants/tree_species_constants.dart
Added new theme color entries (secondaryBorder, shadow); introduced TreeSpeciesConstants with species management API (getAllSpecies, getSpeciesByCategory, getCategories).
Contract ABIs
lib/utils/constants/contract_abis/organisation_contract_details.dart, lib/utils/constants/contract_abis/organisation_factory_contract_details.dart, lib/utils/constants/contract_abis/tree_nft_contract_details.dart
Updated ABIs with expanded JSON formatting, pagination parameters (offset/limit), renamed fields (e.g., photoIpfsHashorganisationPhoto), and new tuple structures for complex return types.
Contract Read Functions
lib/utils/services/contract_functions/organisation_contract/organisation_read_functions.dart, lib/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_read_services.dart, lib/utils/services/contract_functions/planter_token_contract/planter_token_read_services.dart
Introduced OrganisationContractReadFunctions (getOrganisationsByUser, getVerificationRequestsByStatus, getTreePlantingProposalsByStatus); extended tree NFT read functions with pagination and profile retrieval; added PlanterTokenReadFunctions for token data fetching.
Contract Write Functions
lib/utils/services/contract_functions/organisation_contract/organisation_write_functions.dart, lib/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_write_functions.dart
Introduced OrganisationContractWriteFunctions (addMember, removeMember, plantTreeProposal, requestVerification); added numberOfTrees parameter to ContractWriteFunctions.mintNft.
Provider & State Management
lib/providers/mint_nft_provider.dart, lib/providers/wallet_provider.dart
Extended MintNftProvider with organisation address and number of trees tracking; strengthened WalletProvider session validation with early guards for expired/invalid sessions.
Mint NFT Flow
lib/pages/mint_nft/mint_nft_details.dart, lib/pages/mint_nft/mint_nft_coordinates.dart, lib/pages/mint_nft/mint_nft_images.dart, lib/pages/mint_nft/mint_nft_organisation.dart, lib/pages/mint_nft/submit_nft_page.dart
Refactored minting flow to include organisation selection step; added species dropdown and tree count picker; enhanced coordinate parsing with detailed validation and fallback geohash generation; updated to use TransactionDialog for feedback; integrated organisation minting path with proposal submission.
Organisation Pages
lib/pages/organisations_pages/organisation_details_page.dart, lib/pages/organisations_pages/create_organisation.dart, lib/pages/organisations_pages/user_organisations_page.dart
Converted OrganisationDetailsPage to stateful with tab-based layout (Info, Members, Verifications, Proposals); added member management and dynamic data fetching; refactored to use new back button API; updated import paths for contract functions.
User Profile Pages
lib/pages/user_profile_page.dart, lib/pages/home_page.dart, lib/pages/register_user_page.dart, lib/pages/settings_page.dart, lib/pages/trees_page.dart
Introduced UserProfilePage for viewing user profiles by address; enhanced SettingsPage with token lookup and user profile navigation; refactored AllTreesPage to stateful with wallet-connection check and prompt; updated home page to pass user address to profile widget; replaced ad-hoc dialogs with TransactionDialog.
Profile & User Widgets
lib/widgets/profile_widgets/profile_section_widget.dart, lib/widgets/profile_widgets/user_profile_viewer_widget.dart
Extended ProfileSectionWidget to accept user address parameter and display verifier tokens with expandable details; introduced UserProfileViewerWidget for fetching and displaying remote user profiles with token pagination and IPFS fallback.
Organisation Detail Tabs
lib/widgets/organisation_details_page/tabs/info_tab.dart, lib/widgets/organisation_details_page/tabs/members_tab.dart, lib/widgets/organisation_details_page/tabs/planting_proposals_tab.dart, lib/widgets/organisation_details_page/tabs/verification_requests_tab.dart, lib/widgets/organisation_details_page/tabs/tabs.dart
Introduced four new tab widgets for organisation details pages with dedicated UIs for info display, member management, proposal viewing, and verification request tracking; created barrel export file.
NFT Display & Tree Details
lib/widgets/nft_display_utils/recent_trees_widget.dart, lib/widgets/nft_display_utils/tree_nft_details_verifiers_widget.dart, lib/widgets/nft_display_utils/tree_nft_view_details_with_map.dart, lib/widgets/nft_display_utils/user_nfts_widget.dart, lib/pages/tree_details_page.dart
Introduced RecentTreesWidget with paginated tree loading and rich card display; enhanced verifier display with TransactionDialog feedback and pagination; restructured tree detail layout with organisation banner and improved card styling; updated user NFT widget to normalize location data and adjust headers based on ownership.
Utilities & Services
lib/utils/services/ipfs_services.dart, lib/utils/services/clipboard_service.dart, lib/widgets/map_widgets/flutter_map_widget.dart, lib/widgets/map_widgets/static_map_display_widget.dart
Updated IPFS service to read Pinata credentials from new env var names; added clipboard service utility for address copying with feedback; updated map widgets to use theme colors and adjusted initial zoom level.

Sequence Diagram

sequenceDiagram
    actor User
    participant App as Flutter App
    participant Wallet as WalletProvider
    participant Contract as Smart Contract
    participant Dialog as TransactionDialog

    User->>App: Initiate NFT Mint
    App->>Wallet: Check wallet connection
    Wallet-->>App: Connected
    
    App->>App: Show Mint Org Selection
    User->>App: Select Organisation
    App->>App: Store org address in provider
    
    App->>App: Show Details (Coordinates, Species, Trees)
    User->>App: Enter Details
    
    App->>App: Show Images Upload
    User->>App: Upload Images
    
    App->>Wallet: Get session
    Wallet-->>App: Session valid
    
    alt Organisation Minting
        App->>Contract: Call plantTreeProposal
        Contract-->>App: Proposal submitted (hash)
    else Individual Minting
        App->>Contract: Call mintNft
        Contract-->>App: NFT minted (hash)
    end
    
    App->>Dialog: Show TransactionDialog.showSuccess
    Dialog-->>User: Display success with hash
    App->>App: Clear mint provider data
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • Organisation minting integration: Verify the conditional logic between individual vs. organisation minting paths in submit_nft_page.dart and data flow through MintNftProvider is correctly sequenced.
  • Contract ABI changes: Review the expanded ABI structures for organisation_contract_details.dart, organisation_factory_contract_details.dart, and tree_nft_contract_details.dart to ensure pagination parameters and renamed fields align with contract implementation.
  • Pagination logic: Verify offset/limit handling in getRecentTreesPaginated, getVerificationRequestsByStatus, and getTreePlantingProposalsByStatus matches expected contract behavior and the UI load-more implementation.
  • Profile data model changes: Confirm the refactored UserProfileData and new VerificationDetails models in profile_section_widget.dart correctly parse contract responses and handle optional fields.
  • TransactionDialog integration: Ensure all dialog invocations in submit_nft_page.dart, register_user_page.dart, tree_details_page.dart, and organisation_details_page.dart pass correct parameters and handle async contexts safely.
  • Theming consistency: Verify theme color application in newly styled components (mint_nft_coordinates.dart, wallet_connect_dialog.dart, etc.) uses correct getThemeColors(context) keys.

Possibly related PRs

  • PR Organisations page #16: Adds and modifies organisation-related pages, ABIs, and contract read/write helpers with overlapping code-level changes.
  • PR Consistent UI  #15: Both PRs extend theming utilities (color_constants.dart) and convert multiple UI files to use theme-driven colors.
  • PR NFT Display page #13: Introduces overlapping NFT/tree verification features including verifier data models, read/write functions, and verifier UI components.

Suggested reviewers

  • ceilican
  • Zahnentferner
  • bhavik-mangla

Poem

🐰 Hops through contracts with glee,
Organisations now mint with spree!
Profiles and tokens aligned with care,
Themed colors dance everywhere!
From pending to approved, the proposals soar—
This rabbit's work opens many a door! 🌳✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The pull request title "Merged org trees" is vague and uses non-descriptive terminology that fails to convey meaningful information about the changeset. While the title references "org" and "trees" which appear in the codebase, the term "Merged" is ambiguous—it's unclear whether this refers to branch merging, data combination, or feature integration. Given the extensive changes documented in the PR objectives (new organization pages with tabs, verifiers section, planting proposals, transaction dialogs, recent trees widget, and contract updates), a teammate scanning the git history would not understand the primary purpose or scope of this pull request. Consider revising the title to be more specific and descriptive. For example: "Add organization-based tree minting with tabulated details and verifications" or "Implement organization features with tabs, verifiers, and proposals" would better communicate the key changes to future reviewers. The revised title should clearly indicate that this PR introduces new organization management features, rather than using ambiguous terminology like "Merged."
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch mergedOrgTrees

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

❤️ Share

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

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (10)
lib/utils/services/contract_functions/organisation_factory_contract.dart/organisation_factory_contract_read_functions.dart (1)

45-45: Fix inconsistent error messages.

Error messages reference "NFTs" instead of "organisations", which is misleading for this organisation-specific read function.

Apply this diff:

      if (!walletProvider.isConnected) {
        logger.e("Wallet not connected for reading organisations");
        return ContractReadResult.error(
-         errorMessage: 'Please connect your wallet before reading NFTs.',
+         errorMessage: 'Please connect your wallet before reading organisations.',
        );
      }
      // ... 
    } catch (e) {
      logger.e("Error reading organisations", error: e);
      return ContractReadResult.error(
-       errorMessage: 'Failed to read NFTs: ${e.toString()}',
+       errorMessage: 'Failed to read organisations: ${e.toString()}',
      );
    }

Also applies to: 86-86

lib/pages/organisations_pages/create_organisation.dart (2)

105-106: Remove unused variable.

The result variable is declared but never used. Either remove it or handle the result appropriately.

Apply this diff:

      final walletProvider =
          // ignore: use_build_context_synchronously
          Provider.of<WalletProvider>(context, listen: false);
-     // ignore: unused_local_variable
-     final result =
-         await OrganisationFactoryContractWriteFunctions.createOrganisation(
+     await OrganisationFactoryContractWriteFunctions.createOrganisation(
              walletProvider: walletProvider,
              name: name,
              description: description,
              organisationPhotoHash: _uploadedImageHash ?? "");

120-121: Add mounted check before navigation.

After the async submitDetails() operation completes, the widget may be unmounted. Verify the widget is still mounted before calling context.push().

Apply this diff:

    }
    // ignore: use_build_context_synchronously
+   if (!mounted) return;
    context.push('/organisations');
  }
lib/pages/mint_nft/mint_nft_images.dart (3)

40-50: Don’t use BuildContext after await; get Provider before awaiting.

This removes the need to suppress use_build_context_synchronously.

Apply this diff:

 Future<void> _pickAndUploadImages() async {
   try {
-    final List<XFile> images = await _picker.pickMultiImage();
+    final provider = context.read<MintNftProvider>();
+    final List<XFile> images = await _picker.pickMultiImage();
     if (images.isEmpty) return;
 
     logger.d('Selected ${images.length} images for upload');
 
-    // ignore: use_build_context_synchronously
-    final provider = Provider.of<MintNftProvider>(context, listen: false);
+    // provider captured before await; safe to use

57-71: Add mounted guard during per‑image updates.

Prevents setState on a disposed widget if the user navigates away mid‑upload.

Apply this diff:

 for (int i = 0; i < images.length; i++) {
-  setState(() {
+  if (!mounted) return;
+  setState(() {
     _uploadingIndex = i;
   });
   try {
     File imageFile = File(images[i].path);
     String? hash = await uploadToIPFS(imageFile, (isUploading) {});
 
     if (hash != null) {
       newHashes.add(hash);
-      setState(() {
+      if (!mounted) return;
+      setState(() {
         _uploadedHashes.add(hash);
       });

270-286: Spinner and overlay color are the same; progress indicator becomes invisible.

Use a semi‑transparent overlay and contrasting spinner color.

Apply this diff:

-  Container(
+  Container(
     width: 120,
     height: 120,
     decoration: BoxDecoration(
-      color: getThemeColors(context)['primary'],
+      color: getThemeColors(context)['secondaryBackground']?.withOpacity(0.7) ?? Colors.black54,
       borderRadius: BorderRadius.circular(8),
     ),
     child: Center(
-      child: CircularProgressIndicator(
-        color: getThemeColors(context)['primary'],
-      ),
+      child: CircularProgressIndicator(
+        color: getThemeColors(context)['textPrimary'] ?? Colors.white,
+      ),
     ),
   ),
lib/components/universal_navbar.dart (1)

319-333: Incorrect use of Flexible inside SizedBox causes runtime assertion

Flexible must be a descendant of a Flex (e.g., Row) and cannot be wrapped by SizedBox. This will assert at runtime and also constrains width to 10px, making the address unreadable.

-              SizedBox(
-                width: 10,
-                child: Flexible(
-                  child: Text(
-                    formatAddress(walletProvider.currentAddress!),
-                    ...
-                    overflow: TextOverflow.ellipsis,
-                  ),
-                ),
-              ),
+              ConstrainedBox(
+                constraints: const BoxConstraints(maxWidth: 90),
+                child: Text(
+                  formatAddress(walletProvider.currentAddress!),
+                  style: TextStyle(
+                    color: Colors.green[700],
+                    fontWeight: FontWeight.w600,
+                    fontSize: 10,
+                  ),
+                  overflow: TextOverflow.ellipsis,
+                ),
+              ),
lib/widgets/nft_display_utils/tree_nft_details_verifiers_widget.dart (1)

56-58: Guard substring on short/invalid addresses

shortAddress assumes length >= 10; will throw on short strings.

-  String get shortAddress {
-    return "${address.substring(0, 6)}...${address.substring(address.length - 4)}";
-  }
+  String get shortAddress {
+    if (address.length <= 10) return address;
+    return "${address.substring(0, 6)}...${address.substring(address.length - 4)}";
+  }
lib/pages/mint_nft/mint_nft_details.dart (1)

313-317: Fix hint text contrast (currently same as background, effectively invisible) in TextField and Dropdown. This is an accessibility/readability issue.

-              hintStyle: TextStyle(
-                color: getThemeColors(context)['background'],
-                fontSize: 14,
-              ),
+              hintStyle: TextStyle(
+                color: getThemeColors(context)['textSecondary'],
+                fontSize: 14,
+              ),

And similarly for the dropdown’s InputDecoration.hintStyle.

Also applies to: 373-385

lib/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_read_services.dart (1)

100-121: Validate the provided currentAddress, not walletProvider.currentAddress; current code checks an unused variable and may mask bad input.

-      final String address = walletProvider.currentAddress.toString();
-      if (!address.startsWith('0x')) {
+      if (!currentAddress.startsWith('0x')) {
         return ContractReadResult.error(
           errorMessage: 'Invalid wallet address format',
         );
       }
-      final EthereumAddress userAddress =
-          EthereumAddress.fromHex(currentAddress);
+      final EthereumAddress userAddress = EthereumAddress.fromHex(currentAddress);
🧹 Nitpick comments (52)
lib/utils/services/ipfs_services.dart (1)

6-7: LGTM! Correctly updated to use Pinata-specific environment variables.

The changes align perfectly with the .env.stencil updates and maintain consistency across the codebase.

Consider improving error handling for missing credentials. Currently, empty fallbacks will cause authentication failures that may be difficult to debug. You could add validation:

-String apiKey = dotenv.get('PINATA_API_KEY', fallback: "");
-String apiSecret = dotenv.get('PINATA_API_SECRET', fallback: "");
+String _getRequiredEnv(String key) {
+  final value = dotenv.get(key, fallback: "");
+  if (value.isEmpty) {
+    throw Exception('Required environment variable $key is not set');
+  }
+  return value;
+}
+
+String apiKey = _getRequiredEnv('PINATA_API_KEY');
+String apiSecret = _getRequiredEnv('PINATA_API_SECRET');

This would fail fast with a clear error message rather than silently using empty credentials.

lib/widgets/map_widgets/static_map_display_widget.dart (1)

92-98: Make initial zoom configurable.

Hardcoding 10.0 reduces reuse. Expose an optional initialZoom prop with a sensible default.

-class StaticCoordinatesMap extends StatefulWidget {
-  final double lat;
-  final double lng;
-  const StaticCoordinatesMap({super.key, required this.lat, required this.lng});
+class StaticCoordinatesMap extends StatefulWidget {
+  final double lat;
+  final double lng;
+  final double initialZoom;
+  const StaticCoordinatesMap({
+    super.key,
+    required this.lat,
+    required this.lng,
+    this.initialZoom = 10.0,
+  });
-            initialZoom: 10.0,
+            initialZoom: widget.initialZoom,
lib/utils/constants/contract_abis/tree_nft_contract_details.dart (1)

4-1633: Consider moving large ABI strings to assets.

Embedding a ~1.6k‑line string increases diff noise and risk of manual drift. Store JSON under assets/abis/*.json and load at init; keep a small typed accessor.

lib/utils/constants/contract_abis/organisation_contract_details.dart (1)

3-1144: ABI growth: adopt assets + generated types.

Like the factory/tree ABIs, consider moving to asset JSON and generating bindings (e.g., via build_runner) to reduce manual errors and ease refactors.

lib/utils/services/contract_functions/organisation_factory_contract.dart/organisation_factory_contract_read_functions.dart (2)

57-58: Consider making pagination parameters configurable.

The offset and limit values are hardcoded, which limits flexibility for pagination use cases. Consider accepting these as method parameters with defaults.

Apply this diff to make pagination configurable:

  static Future<ContractReadResult> getOrganisationsByUser({
    required WalletProvider walletProvider,
+   int offset = 0,
+   int limit = 100,
  }) async {
    try {
      if (!walletProvider.isConnected) {
        logger.e("Wallet not connected for reading organisations");
        return ContractReadResult.error(
          errorMessage: 'Please connect your wallet before reading NFTs.',
        );
      }
      final String address = walletProvider.currentAddress.toString();
      if (!address.startsWith('0x')) {
        return ContractReadResult.error(
          errorMessage: 'Invalid wallet address format',
        );
      }
      final EthereumAddress userAddress = EthereumAddress.fromHex(address);
      final List<dynamic> args = [
        userAddress,
-       BigInt.from(0), // offset
-       BigInt.from(100), // limit - fetch up to 100 organisations
+       BigInt.from(offset),
+       BigInt.from(limit),
      ];

72-74: Add defensive null checks for totalCount parsing.

The parsing logic assumes result[1] exists and can be converted to int without error. Consider adding defensive checks.

Apply this diff:

      final organisations = result.length > 0 ? result[0] ?? [] : [];
-     final totalCount =
-         result.length > 1 ? int.parse(result[1].toString()) : 0;
+     final totalCount = result.length > 1 && result[1] != null
+         ? int.tryParse(result[1].toString()) ?? 0
+         : 0;
      logger.d(organisations);
lib/widgets/map_widgets/flutter_map_widget.dart (1)

586-605: Consider applying consistent theming to StaticDisplayMap.

The StaticDisplayMap widget's bottom bar still uses hardcoded Colors.blue (line 593), while CoordinatesMap now uses theme colors. For consistency, consider updating this as well.

Apply this diff:

        Positioned(
          bottom: 8,
          left: 8,
          right: 8,
          child: Container(
            padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
            decoration: BoxDecoration(
-             color: Colors.blue,
+             color: getThemeColors(context)['primary'],
              borderRadius: BorderRadius.circular(4),
            ),
            child: const Text(
              "Static display • Use zoom buttons or pinch to zoom",
              style: TextStyle(
                color: Colors.white,
                fontSize: 11,
              ),
              textAlign: TextAlign.center,
            ),
          ),
        ),
lib/utils/constants/ui/color_constants.dart (1)

59-64: LGTM! Consider consolidating duplicate color definitions.

The new secondaryBorder and shadow theme colors properly support both dark and light modes. However, secondaryBorder and BNWBorder have identical values in both modes, which suggests potential redundancy.

Consider consolidating these duplicate definitions or documenting their distinct semantic purposes if they're intentionally separate.

lib/main.dart (1)

95-103: Simplify path parameter handling.

Line 99 calls .toString() on a value that's already a String?, then checks isNotEmpty. The null-coalescing and empty check can be simplified.

Apply this diff:

        GoRoute(
          path: '/user-profile/:address',
          name: 'user_profile',
          builder: (BuildContext context, GoRouterState state) {
-           final address = state.pathParameters['address'].toString();
+           final address = state.pathParameters['address'] ?? '';
            return UserProfilePage(
-               userAddress: address.isNotEmpty ? address : '');
+               userAddress: address);
          },
        ),
lib/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_write_functions.dart (1)

93-101: Consider including numberOfTrees in the result data.

The success result includes latitude, longitude, species, photos, and geoHash, but omits numberOfTrees. For consistency and completeness, consider adding it to the returned data map.

Apply this diff:

      return ContractWriteResult.success(
        transactionHash: txHash,
        data: {
          'latitude': latitude,
          'longitude': longitude,
          'species': species,
          'photos': photos,
          'geoHash': geoHash,
+         'numberOfTrees': numberOfTrees,
        },
      );
lib/utils/services/clipboard_service.dart (1)

5-14: LGTM with minor optimization opportunity.

The clipboard utility is well-implemented with proper user feedback via SnackBar and consistent theming.

Consider adding const keywords for compile-time optimizations:

   ScaffoldMessenger.of(context).showSnackBar(
     SnackBar(
-      content: Text('Address copied to clipboard'),
+      content: const Text('Address copied to clipboard'),
       backgroundColor: getThemeColors(context)['primary'],
-      duration: Duration(seconds: 2),
+      duration: const Duration(seconds: 2),
     ),
   );
lib/pages/user_profile_page.dart (1)

27-35: Consider responsive layout instead of fixed dimensions.

The hardcoded widths (400) and height (600) may cause layout issues on smaller screens or wider displays:

  • On mobile devices <400px width, content will overflow
  • The fixed height on UserNftsWidget inside SingleChildScrollView may interfere with natural scrolling behavior

Consider using responsive constraints:

  child: Column(
    children: [
-     SizedBox(
-         width: 400,
-         child: ProfileSectionWidget(userAddress: userAddress)),
+     ConstrainedBox(
+       constraints: const BoxConstraints(maxWidth: 600),
+       child: ProfileSectionWidget(userAddress: userAddress),
+     ),
-     SizedBox(
-       width: 400,
-       height: 600,
+     ConstrainedBox(
+       constraints: const BoxConstraints(maxWidth: 600),
        child: UserNftsWidget(
            isOwnerCalling: true, userAddress: userAddress),
      ),
    ],
  ),
lib/pages/settings_page.dart (2)

34-39: Validate Ethereum address format before querying.

Reject clearly invalid inputs to save a network call and produce clearer errors.

Apply this diff:

-    if (_tokenAddressController.text.trim().isEmpty) {
+    final addr = _tokenAddressController.text.trim();
+    if (addr.isEmpty) {
       setState(() {
         _tokenError = 'Please enter a token contract address';
       });
       return;
     }
+    final isEth = RegExp(r'^0x[a-fA-F0-9]{40}$').hasMatch(addr);
+    if (!isEth) {
+      setState(() {
+        _tokenError = 'Invalid address format (expected 0x + 40 hex chars)';
+      });
+      return;
+    }

88-89: Prefer route constants over hardcoded strings.

Use a centralized constant to avoid drift during route refactors.

lib/pages/mint_nft/mint_nft_images.dart (1)

382-413: Use a route constant for navigation to the submit page.

Prevents hardcoded path drift.

lib/utils/constants/tree_species_constants.dart (1)

115-117: Return categories in a stable (sorted) order.

Improves UX for dropdowns and tests relying on order.

Apply this diff:

-  static List<String> getCategories() {
-    return speciesByCategory.keys.toList();
-  }
+  static List<String> getCategories() {
+    final list = speciesByCategory.keys.toList();
+    list.sort();
+    return list;
+  }
lib/pages/mint_nft/mint_nft_organisation.dart (1)

564-573: Substring without length guard can throw if the string is shorter than expected.

Use a safe formatter (like the one in SettingsPage) or guard the length.

Apply this diff:

-  '${organisation.substring(0, 6)}...${organisation.substring(organisation.length - 4)}',
+  (organisation.length > 10)
+      ? '${organisation.substring(0, 6)}...${organisation.substring(organisation.length - 4)}'
+      : organisation,

Or extract a shared address‑shortening util and reuse it across pages.

lib/utils/services/contract_functions/organisation_contract/organisation_write_functions.dart (1)

155-164: Return richer context in success payload.

Include numberOfTrees (and optionally metadata) to simplify caller UX.

       return ContractWriteResult.success(
         transactionHash: txHash,
         data: {
           'latitude': latitude,
           'longitude': longitude,
           'species': species,
           'photos': photos,
           'geoHash': geoHash,
+          'numberOfTrees': numberOfTrees,
+          'metadata': metadata,
         },
       );
lib/providers/mint_nft_provider.dart (2)

12-13: Encapsulate organisationAddress; add getter for consistency.

Avoid public mutable field to prevent bypassing notifications and invariants.

-  String organisationAddress = "";
+  String _organisationAddress = "";
+  String getOrganisationAddress() => _organisationAddress;
@@
-  void setOrganisationAddress(String address) {
-    organisationAddress = address;
+  void setOrganisationAddress(String address) {
+    _organisationAddress = address;
     notifyListeners();
   }
@@
-    organisationAddress = "";
+    _organisationAddress = "";

Also applies to: 15-24, 40-43, 83-86


70-73: Guard against invalid tree counts.

Prevent negatives (and optionally cap excessively large values).

-  void setNumberOfTrees(int numberOfTrees) {
-    _numberOfTrees = numberOfTrees;
+  void setNumberOfTrees(int numberOfTrees) {
+    _numberOfTrees = numberOfTrees < 0 ? 0 : numberOfTrees;
     notifyListeners();
   }
lib/pages/trees_page.dart (3)

56-59: Use a route constant.

Avoid hard-coded paths; reduces typo risk and eases refactors.

-                  context.push('/mint-nft');
+                  context.push(RouteConstants.mintNft);

138-169: Clean up async context use.

You already guard with mounted. Remove // ignore: use_build_context_synchronously and prefer if (!context.mounted) return; for consistency.

-                  await walletProvider.connectWallet();
-                  if (!mounted) return;
+                  await walletProvider.connectWallet();
+                  if (!context.mounted) return;
                   if (walletProvider.isConnected) {
-                    // ignore: use_build_context_synchronously
                     ScaffoldMessenger.of(context).showSnackBar(
                       SnackBar(
                         content: const Text('Wallet connected successfully!'),
-                        // ignore: use_build_context_synchronously
                         backgroundColor: getThemeColors(context)['primary'],
                         behavior: SnackBarBehavior.floating,
                       ),
                     );
                   }
@@
-                  if (!mounted) return;
-                  // ignore: use_build_context_synchronously
+                  if (!context.mounted) return;
                   ScaffoldMessenger.of(context).showSnackBar(

24-27: UX nudge (optional): allow viewing trees without a wallet.

Consider showing RecentTreesWidget to all; gate only write actions (mint/interact). Helps discovery.

lib/widgets/organisation_details_page/tabs/planting_proposals_tab.dart (2)

396-449: Layout: avoid fixed height + outer scroll.

SingleChildScrollView + SizedBox(height: 500) can clip on small/large screens and fight inner ListView scrolling. Prefer filling available space.

-  @override
-  Widget build(BuildContext context) {
-    return SingleChildScrollView(
-      child: Column(
+  @override
+  Widget build(BuildContext context) {
+    return Column(
         children: [
@@
-          SizedBox(
-            height: 500, // Fixed height for the TabBarView
-            child: TabBarView(
+          Expanded(
+            child: TabBarView(
               controller: _statusTabController,
               children: [
                 _buildStatusTabContent(0), // Pending
                 _buildStatusTabContent(1), // Approved
                 _buildStatusTabContent(2), // Rejected
               ],
             ),
           ),
         ],
-      ),
-    );
+    );
   }

44-49: Load tabs in parallel (optional).

You can cut load time by fetching all statuses concurrently.

-  Future<void> _loadAllProposals() async {
-    for (int status = 0; status <= 2; status++) {
-      await _loadProposalsByStatus(status);
-    }
-  }
+  Future<void> _loadAllProposals() async {
+    await Future.wait([0,1,2].map(_loadProposalsByStatus));
+  }
lib/widgets/organisation_details_page/tabs/members_tab.dart (2)

239-248: Border theming consistency for “Add Member” button

Hardcoded black border deviates from the rest of the component using theme border.

-                      side: const BorderSide(color: Colors.black, width: 1),
+                      side: BorderSide(
+                        color: getThemeColors(context)['border']!,
+                        width: 1,
+                      ),

208-211: List item keys for better diffing/reorder safety

Map-iterated widgets lack Keys, which can cause state mix-ups on updates.

-...organisationOwners
-    .map((owner) => _buildMemberTile(context, owner, true)),
+...organisationOwners
+    .map((owner) => KeyedSubtree(key: ValueKey('owner_$owner'),
+        child: _buildMemberTile(context, owner, true))),
@@
-...organisationMembers
-    .map((member) => _buildMemberTile(context, member, false))
+...organisationMembers
+    .map((member) => KeyedSubtree(key: ValueKey('member_$member'),
+        child: _buildMemberTile(context, member, false)))

Also applies to: 254-256

lib/widgets/nft_display_utils/recent_trees_widget.dart (2)

146-151: Color.withValues is not widely supported; use withOpacity

withValues may not exist on older stable channels; withOpacity is portable.

-                      color: Colors.black.withValues(alpha: 0.2),
+                      color: Colors.black.withOpacity(0.2),

563-572: Infinite-scroll trigger and loading indicator duplication

  • Equality check against maxScrollExtent is brittle; use a threshold.
  • Loading indicator is rendered both in the list and below it.
  • itemCount arithmetic includes a dead term and doesn’t reserve space for the loader when hasMore.
-                        onNotification: (ScrollNotification scrollInfo) {
-                          if (!_isLoading &&
-                              _hasMore &&
-                              scrollInfo.metrics.pixels ==
-                                  scrollInfo.metrics.maxScrollExtent) {
-                            _loadTrees(loadMore: true);
-                          }
-                          return false;
-                        },
+                        onNotification: (ScrollNotification sn) {
+                          if (!_isLoading && _hasMore &&
+                              sn.metrics.pixels >= sn.metrics.maxScrollExtent - 200) {
+                            _loadTrees(loadMore: true);
+                          }
+                          return false;
+                        },
@@
-                          itemCount: _trees.length +
-                              (_hasMore && !_isLoading ? 0 : 0) +
-                              (_isLoading ? 1 : 0),
+                          itemCount: _trees.length + (_isLoading && _hasMore ? 1 : 0),
@@
-                            } else if (_isLoading) {
+                            } else if (_isLoading && _hasMore) {
                               return _buildLoadingIndicator();
                             }
                             return const SizedBox.shrink();

And remove the extra bottom indicator:

-        // Loading indicator at bottom when loading more
-        if (_isLoading && _trees.isNotEmpty) _buildLoadingIndicator(),

Also applies to: 574-586, 590-592

lib/utils/services/contract_functions/planter_token_contract/planter_token_read_services.dart (1)

129-136: Null-safety on read results and reduce latency via parallel reads

  • You check success flags but not data presence; accessing data[0] may throw if provider returns empty data on success=false edge cases.
  • The four reads can be parallelized.
-      // Call owner function
-      final ownerResult = await walletProvider.readContract( ... );
-      // Call getPlanterAddress function
-      final planterResult = await walletProvider.readContract( ... );
-      // Call name function
-      final nameResult = await walletProvider.readContract( ... );
-      // Call symbol function
-      final symbolResult = await walletProvider.readContract( ... );
+      final futures = await Future.wait([
+        walletProvider.readContract(
+          contractAddress: tokenContractAddress,
+          abi: contractAbi,
+          functionName: 'owner',
+          params: [],
+        ),
+        walletProvider.readContract(
+          contractAddress: tokenContractAddress,
+          abi: contractAbi,
+          functionName: 'getPlanterAddress',
+          params: [],
+        ),
+        walletProvider.readContract(
+          contractAddress: tokenContractAddress,
+          abi: contractAbi,
+          functionName: 'name',
+          params: [],
+        ),
+        walletProvider.readContract(
+          contractAddress: tokenContractAddress,
+          abi: contractAbi,
+          functionName: 'symbol',
+          params: [],
+        ),
+      ]);
+      final ownerResult = futures[0];
+      final planterResult = futures[1];
+      final nameResult = futures[2];
+      final symbolResult = futures[3];
@@
-      if (!ownerResult.success ||
-          !planterResult.success ||
-          !nameResult.success ||
-          !symbolResult.success) {
+      if (!ownerResult.success || ownerResult.data == null ||
+          !planterResult.success || planterResult.data == null ||
+          !nameResult.success || nameResult.data == null ||
+          !symbolResult.success || symbolResult.data == null) {
         return PlanterTokenReadResult.error(
           errorMessage: 'Failed to fetch token details from contract.',
         );
       }

Also applies to: 144-152

lib/pages/organisations_pages/organisation_details_page.dart (2)

114-130: Avoid using BuildContext after await without mounted check

The lints are suppressed, but these calls can throw if the page is disposed mid-await.

-      if (result.success) {
-        // ignore: use_build_context_synchronously
-        TransactionDialog.showSuccess(
+      if (!mounted) return;
+      if (result.success) {
+        TransactionDialog.showSuccess(
           context,
           ...
         );
-      } else {
-        // ignore: use_build_context_synchronously
-        TransactionDialog.showError(
+      } else {
+        if (!mounted) return;
+        TransactionDialog.showError(
           context,
           ...
         );

Also applies to: 161-177


301-307: Validate entered address before submitting addMember

Prevent cheap contract reverts and improve UX by rejecting invalid addresses locally.

-                      if (address.isNotEmpty) {
+                      final isValid = RegExp(r'^0x[a-fA-F0-9]{40}$').hasMatch(address);
+                      if (isValid) {
                         Navigator.of(context).pop();
                         addMember(address.toString());
+                      } else {
+                        ScaffoldMessenger.of(context).showSnackBar(
+                          SnackBar(content: const Text('Enter a valid 0x wallet address'),
+                            backgroundColor: getThemeColors(context)['error'],),
+                        );
                       }
lib/pages/mint_nft/submit_nft_page.dart (1)

21-23: Remove unused state.

isLoading is declared but not used.

-  bool isLoading = false;
   bool isMinting = false;
lib/utils/services/contract_functions/organisation_contract/organisation_read_functions.dart (2)

51-57: Use canonical address access and validation.

currentAddress.toString() + startsWith('0x') is brittle. Prefer EthereumAddress.hex and validate inputs with EthereumAddress.fromHex to catch malformed addresses early.

-      final String address = walletProvider.currentAddress.toString();
-      if (!address.startsWith('0x')) {
+      final String address = walletProvider.currentAddress.hexEip55;
+      if (address.isEmpty) {
         return ContractReadResult.error(
           errorMessage: 'Invalid wallet address format',
         );
       }

Optionally, validate organisationContractAddress similarly before readContract.


132-170: Pagination args: add basic bounds.

Clamp offset/limit (e.g., offset ≥ 0, 1 ≤ limit ≤ 1000) to avoid large reads from untrusted callers.

lib/widgets/organisation_details_page/tabs/verification_requests_tab.dart (1)

45-49: Load tabs in parallel to reduce latency.

Requests are awaited serially. Use Future.wait to fetch all three statuses concurrently.

-  Future<void> _loadAllRequests() async {
-    for (int status = 0; status <= 2; status++) {
-      await _loadRequestsByStatus(status);
-    }
-  }
+  Future<void> _loadAllRequests() async {
+    await Future.wait([0, 1, 2].map(_loadRequestsByStatus));
+  }
lib/widgets/nft_display_utils/tree_nft_view_details_with_map.dart (1)

382-401: Consider higher-contrast text on colored value chips.

Text uses textPrimary over a colored background; ensure theme guarantees contrast, or switch to textSecondary/white for readability.

lib/pages/mint_nft/mint_nft_details.dart (5)

35-41: Min count guard is good; consider also capping at the UI max (100) in submitDetails to keep business rules consistent if this widget is reused.

-    if (numberOfTrees <= 0) {
+    if (numberOfTrees <= 0 || numberOfTrees > 100) {
       _showCustomSnackBar(
-        "Please select at least 1 tree.",
+        "Please select between 1 and 100 trees.",
         isError: true,
       );
       return;
     }

43-49: Batch provider updates to avoid multiple notifyListeners-triggered rebuilds. Expose a single setDetails({...}) or wrap notifies.

-Provider.of<MintNftProvider>(context, listen: false).setDescription(description);
-Provider.of<MintNftProvider>(context, listen: false).setSpecies(selectedSpecies!);
-Provider.of<MintNftProvider>(context, listen: false).setNumberOfTrees(numberOfTrees);
+final mint = Provider.of<MintNftProvider>(context, listen: false);
+mint.setDetails(
+  description: description,
+  species: selectedSpecies!,
+  numberOfTrees: numberOfTrees,
+);

332-405: Dropdown theming mixes hard-coded Colors.white/FAEB96 with themed surfaces. Use theme tokens for dark/light support.

-        Container(
-          decoration: BoxDecoration(
-            color: Colors.white,
+        Container(
+          decoration: BoxDecoration(
+            color: getThemeColors(context)['background'],
             borderRadius: BorderRadius.circular(16),
-            border: Border.all(
-              color: const Color(0xFFFAEB96),
+            border: Border.all(
+              color: getThemeColors(context)['border']!,
               width: 2,
             ),

283-289: Avoid hard-coded brand colors in labels; route through theme tokens to keep skins consistent.

-              style: const TextStyle(
+              style: TextStyle(
                 fontSize: 16,
                 fontWeight: FontWeight.w600,
-                color: Color(0xFF1CD381),
+                color: getThemeColors(context)['primary'],
               ),

Apply similarly to “Tree Species”, “Number of Trees”, and the count label styles.

Also applies to: 350-357, 429-435, 493-504


450-471: Accessibility: add Semantics for the +/- buttons and the number display for screen readers.

-              child: ElevatedButton(
+              child: Semantics(
+                button: true,
+                label: 'Decrease number of trees',
+                child: ElevatedButton(
                   onPressed: () { ... }
                 ),
+              )
...
-              child: ElevatedButton(
+              child: Semantics(
+                button: true,
+                label: 'Increase number of trees',
+                child: ElevatedButton(
                   onPressed: () { ... }
                 ),
+              )

Also applies to: 511-532

lib/widgets/profile_widgets/user_profile_viewer_widget.dart (2)

163-171: Remove the Access-Control-Allow-Origin request header; it’s a response header and has no effect here.

-                      headers: {
-                        'Access-Control-Allow-Origin': '*',
-                      },

466-501: Token amount math divides BigInt-like values by 1e18 as double; large values will lose precision. Prefer formatting from BigInt with fixed decimals.

String formatAmountFromWei(BigInt raw, {int decimals = 18, int sig = 2}) {
  final s = raw.toString().padLeft(decimals + 1, '0');
  final i = s.substring(0, s.length - decimals);
  final f = s.substring(s.length - decimals).replaceFirst(RegExp(r'0+$'), '');
  final base = f.isEmpty ? i : '$i.$f';
  final d = double.tryParse(base) ?? 0;
  if (d >= 1e6) return '${(d/1e6).toStringAsFixed(1)}M';
  if (d >= 1e3) return '${(d/1e3).toStringAsFixed(1)}K';
  return d >= 1 ? d.toStringAsFixed(sig) : d.toStringAsFixed(4);
}

Use it in collapsed/expanded token renderers with BigInt input.

Also applies to: 550-568

lib/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_read_services.dart (2)

230-271: Guard against unexpected contract tuples (length/shape) to avoid RangeError when ABI changes.

-      final tree =
-          treeDetailsResult.length > 0 ? treeDetailsResult[0] ?? [] : [];
+      final tree = (treeDetailsResult.isNotEmpty && treeDetailsResult[0] is List)
+          ? treeDetailsResult[0]
+          : [];
...
-      final verifiers =
-          treeVerifiersResult.length > 0 ? treeVerifiersResult[0] ?? [] : [];
+      final verifiers = (treeVerifiersResult.isNotEmpty && treeVerifiersResult[0] is List)
+          ? treeVerifiersResult[0]
+          : [];

Similarly, verify tree list element counts before indexing in getRecentTreesPaginated.

Also applies to: 341-364


162-228: getProfileDetails and getProfileDetailsByAddress largely duplicate logic; consider a private helper that accepts a target address to reduce drift.

lib/widgets/profile_widgets/profile_section_widget.dart (6)

30-88: Parsing handles heterogeneous tuples; good fallback. Consider early-length checks to avoid noisy exceptions and logs.

-    try {
-      if (data is List && data.length >= 7) {
+    try {
+      if (data is! List || data.length < 7) {
+        throw Exception('Invalid VerificationDetails structure');
+      }

139-174: fromContractData requires ≥8 fields; good. Consider logging at debug level only; current logs are quite verbose and run on every profile load.


371-438: Avoid logging from within build (expensive and noisy). Remove logger.d calls inside builders unless behind a debug flag.


728-741: Token amount uses double division by 1e18; prefer a BigInt→decimal formatter to avoid precision loss (see earlier suggestion in user_profile_viewer_widget).


810-839: Nice expandable token bubbles. Add Semantics labels for copy actions and token amounts to improve accessibility.

-          Text(
+          Semantics(
+            label: 'Token amount $amount',
+            child: Text(
               formatAmount(amount),
               ...
-          ),
+            ),
+          ),

And add Semantics to the copy IconButton (label: 'Copy contract address').

Also applies to: 842-937


984-1006: Great not-registered state. Consider adding a small explanatory line about wallet connection if that’s a common cause.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7a477d8 and 34c4bb1.

📒 Files selected for processing (50)
  • .env.stencil (1 hunks)
  • lib/components/bottom_navigation_widget.dart (2 hunks)
  • lib/components/transaction_dialog.dart (1 hunks)
  • lib/components/universal_navbar.dart (4 hunks)
  • lib/components/wallet_connect_dialog.dart (2 hunks)
  • lib/main.dart (3 hunks)
  • lib/pages/home_page.dart (1 hunks)
  • lib/pages/mint_nft/mint_nft_coordinates.dart (9 hunks)
  • lib/pages/mint_nft/mint_nft_details.dart (12 hunks)
  • lib/pages/mint_nft/mint_nft_images.dart (4 hunks)
  • lib/pages/mint_nft/mint_nft_organisation.dart (1 hunks)
  • lib/pages/mint_nft/submit_nft_page.dart (10 hunks)
  • lib/pages/organisations_pages/create_organisation.dart (1 hunks)
  • lib/pages/organisations_pages/organisation_details_page.dart (1 hunks)
  • lib/pages/organisations_pages/user_organisations_page.dart (1 hunks)
  • lib/pages/register_user_page.dart (6 hunks)
  • lib/pages/settings_page.dart (2 hunks)
  • lib/pages/tree_details_page.dart (16 hunks)
  • lib/pages/trees_page.dart (1 hunks)
  • lib/pages/user_profile_page.dart (1 hunks)
  • lib/providers/mint_nft_provider.dart (4 hunks)
  • lib/providers/wallet_provider.dart (2 hunks)
  • lib/utils/constants/contract_abis/organisation_contract_details.dart (1 hunks)
  • lib/utils/constants/contract_abis/organisation_factory_contract_details.dart (1 hunks)
  • lib/utils/constants/contract_abis/tree_nft_contract_details.dart (1 hunks)
  • lib/utils/constants/route_constants.dart (1 hunks)
  • lib/utils/constants/tree_species_constants.dart (1 hunks)
  • lib/utils/constants/ui/color_constants.dart (1 hunks)
  • lib/utils/services/clipboard_service.dart (1 hunks)
  • lib/utils/services/contract_functions/organisation_contract/organisation_read_functions.dart (1 hunks)
  • lib/utils/services/contract_functions/organisation_contract/organisation_write_functions.dart (1 hunks)
  • lib/utils/services/contract_functions/organisation_factory_contract.dart/organisation_factory_contract_read_functions.dart (2 hunks)
  • lib/utils/services/contract_functions/planter_token_contract/planter_token_read_services.dart (1 hunks)
  • lib/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_read_services.dart (8 hunks)
  • lib/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_write_functions.dart (3 hunks)
  • lib/utils/services/ipfs_services.dart (1 hunks)
  • lib/widgets/basic_scaffold.dart (2 hunks)
  • lib/widgets/map_widgets/flutter_map_widget.dart (1 hunks)
  • lib/widgets/map_widgets/static_map_display_widget.dart (1 hunks)
  • lib/widgets/nft_display_utils/recent_trees_widget.dart (1 hunks)
  • lib/widgets/nft_display_utils/tree_nft_details_verifiers_widget.dart (16 hunks)
  • lib/widgets/nft_display_utils/tree_nft_view_details_with_map.dart (1 hunks)
  • lib/widgets/nft_display_utils/user_nfts_widget.dart (5 hunks)
  • lib/widgets/organisation_details_page/tabs/info_tab.dart (1 hunks)
  • lib/widgets/organisation_details_page/tabs/members_tab.dart (1 hunks)
  • lib/widgets/organisation_details_page/tabs/planting_proposals_tab.dart (1 hunks)
  • lib/widgets/organisation_details_page/tabs/tabs.dart (1 hunks)
  • lib/widgets/organisation_details_page/tabs/verification_requests_tab.dart (1 hunks)
  • lib/widgets/profile_widgets/profile_section_widget.dart (19 hunks)
  • lib/widgets/profile_widgets/user_profile_viewer_widget.dart (1 hunks)
🔇 Additional comments (40)
.env.stencil (1)

3-4: Verification complete—rename is safe and properly implemented.

The search confirms no references to the old generic API_KEY or API_SECRET environment variables exist in the codebase. The new PINATA_API_KEY and PINATA_API_SECRET are already correctly used in lib/utils/services/ipfs_services.dart (lines 6-7), and no other .env files require updating.

The change is complete and introduces no breaking dependencies.

lib/pages/organisations_pages/user_organisations_page.dart (2)

8-8: Original review comment is incorrect; no action required.

The import path is valid. File discovery confirms the directory is actually named organisation_factory_contract.dart, and Dart's package resolution correctly resolves the import to the existing file. While the .dart suffix on a directory is unconventional, it poses no technical issue. The suggested diff would break the import.

Likely an incorrect or invalid review comment.


111-118: Data structure is correct, but pagination parameters are hardcoded (not exposed).

The wrapper correctly maps ABI field orgs to the organisations key and returns totalCount as expected. The consumer code at line 115 properly accesses data['organisations']. However, getOrganisationsByUser() hardcodes offset to 0 and limit to 100 without exposing them as parameters—unlike other similar paginated functions in the codebase. If pagination control is needed later, the function signature must be updated to accept optional offset and limit parameters.

lib/utils/constants/contract_abis/organisation_factory_contract_details.dart (1)

3-839: Now let me search for the actual call sites where these organisation factory functions are invoked:Now let me search for the actual usage/call sites of these functions in the codebase:The web search isn't helping find the specific call sites. Let me run a script directly on the codebase to find where these functions are called:Based on my analysis of the provided script output and codebase context, I have enough information to generate a rewritten review comment. The script output shows that the ABI has been updated with pagination parameters and return structures, but I need to conclusively determine if there are actual integration issues or if the comment is speculative.

Verify client read functions after ABI pagination/renames.

Multiple organisation factory functions now require offset/limit parameters and return orgs (or structured details) alongside totalCount. However, from the codebase examination:

  1. ABI Updated: The organisation_factory_contract_details.dart ABI correctly reflects the new signatures with pagination inputs and outputs renamed to orgs/totalCount.

  2. Read Functions Updated: organisation_factory_contract_read_functions.dart correctly parses result[0] as organisations array and result[1] as totalCount (lines 72-80), returning them in the result data map.

  3. Consumer Code: Existing call sites in widgets (e.g., user_nfts_widget.dart, recent_trees_widget.dart) properly extract totalCount and handle pagination with hasMore checks. Similar patterns exist for tree NFT functions that already use paginated returns.

  4. Missing Bounds Validation: No explicit validation on limit parameter bounds is evident. When calling contract functions, ensure maximum page size is enforced (e.g., limit > 0 and limit <= maxPageSize).

  5. Key Names Consistency: Returned data keys match expectations (organisations, totalCount, etc.), though verify consumer code maps align with actual function return names (e.g., getAllOrganisations returns (orgs, totalCount) — confirm mappers use the correct keys).

Action: Add bounds validation for pagination limit parameter; verify all call sites pass offset and limit to the updated functions and handle the paginated response structure.

lib/utils/constants/contract_abis/organisation_contract_details.dart (1)

1145-1146: Remove unused constant reading from incorrect env var.

The organisationContractAddress constant (lines 1145–1146) is dead code that reads from ORGANISATION_FACTORY_CONTRACT_ADDRESS. Verification confirms the constant is never referenced anywhere in the codebase—all contract operations dynamically pass org addresses via function parameters. Remove it entirely.

-final String organisationContractAddress =
-    dotenv.env['ORGANISATION_FACTORY_CONTRACT_ADDRESS'] ?? '';
lib/utils/services/contract_functions/organisation_factory_contract.dart/organisation_factory_contract_read_functions.dart (1)

7-35: LGTM!

The ContractReadResult class provides a clean, standardized wrapper for contract read operations with proper success/error handling and optional data payload.

lib/pages/home_page.dart (1)

24-28: LGTM!

The ProfileSectionWidget now correctly receives the current wallet address as a parameter, aligning with the existing pattern used for UserNftsWidget. The null-safe fallback to an empty string is appropriate.

lib/widgets/map_widgets/flutter_map_widget.dart (1)

279-279: LGTM!

The bottom bar now uses dynamic theme colors via getThemeColors(context)['primary'], ensuring consistent theming across the application.

lib/main.dart (1)

104-110: LGTM!

The new organisation-based NFT minting route is properly configured using route constants and wired to MintNftOrganisationPage.

lib/pages/organisations_pages/create_organisation.dart (1)

164-166: LGTM!

The migration to BaseScaffold's back-button API (showBackButton, onBackPressed) and loading state (isLoading) is clean and aligns with the broader theming and navigation enhancements across the codebase.

lib/utils/constants/route_constants.dart (1)

5-5: LGTM!

The new route constants for organisation-based NFT minting follow the established pattern and are properly defined for both the route name and path.

Also applies to: 12-12

lib/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_write_functions.dart (1)

44-44: LGTM!

The numberOfTrees parameter is properly added to the mintNft method signature, converted to BigInt, and included in the contract call arguments. This aligns with the PR's enhancement of the minting flow to track tree quantities.

Also applies to: 67-67, 81-81

lib/components/bottom_navigation_widget.dart (1)

28-30: LGTM!

The migration to centralized theme colors via getThemeColors(context) improves consistency across the app and aligns with the broader theming refactor in this PR.

lib/widgets/nft_display_utils/user_nfts_widget.dart (4)

156-156: LGTM!

The addition of the userAddress parameter enables viewing NFTs for any user, not just the current wallet owner. This aligns with the new user profile viewing capabilities introduced in this PR.


362-373: LGTM!

The conditional header text based on address comparison provides clear context to users about whose trees they're viewing. This enhances UX when navigating between personal and other users' profiles.


441-443: LGTM!

The personalized empty state messaging improves user experience by distinguishing between viewing your own profile versus another user's profile.


255-255: Formula is correct but inconsistencies exist elsewhere in coordinate handling.

The transformation at line 255 correctly inverts the contract's storage formula. The contract stores coordinates as (value + offset) * 1e6, and line 255 properly reverses this with (value / 1000000) - offset.

However, verification revealed inconsistent implementations:

  • recent_trees_widget.dart (_convertCoordinate, line 96-98): Subtracts 90 from all coordinates, including longitude (should subtract 180 for longitude)
  • planting_proposals_tab.dart (_convertCoordinate, line 122-124): Applies no offset at all, only divides by 1e6
  • tree_details_page.dart (192-195) and user_nfts_widget.dart (line 255): Both correctly apply distinct offsets per coordinate

Verify that coordinate handling is intentionally different in these files or if recent_trees_widget.dart and planting_proposals_tab.dart require corrections to match the contract's storage contract.

lib/widgets/organisation_details_page/tabs/tabs.dart (1)

1-4: LGTM!

The barrel file provides a clean public API for the organisation details tabs, improving import organization and maintainability.

lib/pages/register_user_page.dart (2)

36-52: LGTM!

The migration to TransactionDialog for both success and error scenarios provides a consistent user experience across the app and properly displays transaction hashes for blockchain operations.


147-148: LGTM!

The updated BaseScaffold API with explicit showBackButton and isLoading parameters improves clarity and aligns with the scaffold refactoring in this PR.

lib/widgets/organisation_details_page/tabs/info_tab.dart (2)

15-19: LGTM!

The date formatting helper properly handles the edge case of timestamp == 0 by returning "Unknown", preventing display of incorrect dates.


66-68: LGTM!

Graceful handling of empty organization descriptions with a fallback message improves the user experience and prevents displaying an empty container.

lib/providers/wallet_provider.dart (2)

400-407: LGTM!

The condition fix from !sessions.isNotEmpty to sessions.isEmpty improves code readability by removing the double negative, though the logic remains functionally equivalent.


486-522: Excellent session validation guards!

The comprehensive session validation before transaction execution prevents common failure scenarios:

  • Detects disconnected wallets (empty sessions)
  • Identifies expired sessions
  • Validates account availability

This provides better error messages to users and prevents transaction failures downstream. The logging additions will aid in debugging connection issues.

lib/widgets/basic_scaffold.dart (1)

72-83: All new parameters verified in UniversalNavbar component.

Confirmed that UniversalNavbar (lib/components/universal_navbar.dart) defines and implements all five parameters: showBackButton, isLoading, onBackPressed, onReload, and showReloadButton. The pass-through from basic_scaffold.dart is correct and will compile without issues.

lib/pages/mint_nft/mint_nft_organisation.dart (1)

10-10: The import path is valid and correct. The file exists at the exact location specified: lib/utils/services/contract_functions/organisation_factory_contract.dart/organisation_factory_contract_read_functions.dart. While organisation_factory_contract.dart is an unconventional directory name (typically .dart denotes files), it is a legitimate directory in this codebase, and the import path resolves properly.

Likely an incorrect or invalid review comment.

lib/widgets/nft_display_utils/recent_trees_widget.dart (1)

96-105: Fix inconsistent coordinate conversion offset

The review comment's BigInt concern is incorrect—contract read services already convert BigInt to int (lines 345-346 of tree_nft_contract_read_services.dart). However, the coordinate offset issue is real but incomplete.

The -90 offset applied to both latitude and longitude is inconsistent. The planting_proposals_tab.dart widget (line 124) correctly uses only coordinate / 1000000.0 without offset, which aligns with standard fixed-point coordinate encoding. Remove the - 90.0 offset:

  double _convertCoordinate(int coordinate) {
    // Convert from fixed-point representation to decimal degrees
-   return (coordinate / 1000000.0) - 90.0;
+   return coordinate / 1000000.0;
  }

The _formatDate() function is already safe—the contract read service ensures all numeric fields are converted to int before reaching this widget. No type changes needed.

Likely an incorrect or invalid review comment.

lib/pages/mint_nft/submit_nft_page.dart (2)

134-136: Clarify scope of clearData after success.

clearData() currently runs for both org and individual flows, while the comment mentions only organisation address. Confirm intended behavior; gate by isOrganisationMint if you mean to clear only org-related drafts, or update the comment.

-        // Clear the organisation address after successful submission
-        mintNftProvider.clearData();
+        // Clear minting draft data after success (org and individual)
+        mintNftProvider.clearData();
+        // If only org data should be cleared, gate with: if (isOrganisationMint) { ... }

121-132: Nice: unified success dialog with optional hash.

lib/components/transaction_dialog.dart (1)

211-255: Good API: post-frame, mounted checks, non-dismissible.

lib/pages/mint_nft/mint_nft_coordinates.dart (2)

200-209: Verify downstream acceptance of “fallback_…” geohash.

Contracts or indexers might require a valid geohash charset/length. Confirm this sentinel format won’t break writes/reads. If uncertain, prefer blocking with an error and asking the user to retry.


128-136: Good: strict numeric parsing and explicit range errors.

lib/utils/services/contract_functions/organisation_contract/organisation_read_functions.dart (1)

106-123: Nice: member/owner checks with case-insensitive compare.

lib/widgets/organisation_details_page/tabs/verification_requests_tab.dart (1)

364-419: Solid tabbed UI with clear empty/error states.

lib/widgets/nft_display_utils/tree_nft_view_details_with_map.dart (1)

76-118: Nice header/container refactor; consistent theming.

lib/pages/mint_nft/mint_nft_details.dart (2)

25-33: Strong validation for description/species; null/empty checks look correct.


52-53: Route constant verified and registered. The constant RouteConstants.mintNftOrganisationPath is defined at lib/utils/constants/route_constants.dart:12 and registered as a GoRoute at lib/main.dart:105. The navigation call is correct.

lib/pages/tree_details_page.dart (2)

560-570: BaseScaffold flags and loading indicator integration look good; back/reload wired appropriately.

Also applies to: 590-614


56-63: Confirm pagination intent: using _visibleVerifiersCount stops loading before _totalVerifiersCount.

The pagination condition uses only _visibleVerifiersCount and ignores _totalVerifiersCount. If the contract returns visibleCount < totalCount (indicating hidden/invisible verifiers), pagination will halt before reaching the total count. Verify this is the intended UX behavior—whether invisible verifiers should remain unloaded or whether pagination should instead use totalCount.

lib/widgets/profile_widgets/profile_section_widget.dart (1)

1139-1171: Overall render flow and states are clean and predictable. LGTM.

Comment on lines +128 to +135
child: Text(
'${transactionHash!.substring(0, 10)}...${transactionHash!.substring(transactionHash!.length - 8)}',
style: TextStyle(
fontSize: 12,
fontFamily: 'monospace',
color: colors['textPrimary'],
),
),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard short hashes to prevent RangeError.

Substring indexes assume length ≥ 18. Build the preview defensively.

-                          Expanded(
-                            child: Text(
-                              '${transactionHash!.substring(0, 10)}...${transactionHash!.substring(transactionHash!.length - 8)}',
+                          Expanded(
+                            child: Text(
+                              () {
+                                final h = transactionHash!;
+                                if (h.length <= 10) return h;
+                                final left = h.substring(0, 10);
+                                final right = h.length > 18 ? h.substring(h.length - 8) : '';
+                                return right.isEmpty ? left : '$left...$right';
+                              }(),
                               style: TextStyle(
                                 fontSize: 12,
                                 fontFamily: 'monospace',
                                 color: colors['textPrimary'],
                               ),
                             ),

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

🤖 Prompt for AI Agents
In lib/components/transaction_dialog.dart around lines 128 to 135, the substring
calls assume the transactionHash length is at least 18 and will throw a
RangeError for shorter hashes; update the UI rendering to defensively handle
short or null hashes by computing safe start/end indices (or simply display the
full hash when length is below the truncation threshold) and avoid
force-unwrapping nulls—use length checks (or fallback empty string) and only
perform substring/truncation when indexes are valid so the preview never tries
to access out-of-range positions.

Comment on lines +76 to +94
onTap: () async {
try {
await walletProvider.openWallet(wallet, uri);
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
} catch (e) {
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(e.toString()),
backgroundColor:
// ignore: use_build_context_synchronously
getThemeColors(context)['error'],
),
);
}
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add context.mounted guard after await.

Avoid using a stale BuildContext; remove lint suppressions.

-                        onTap: () async {
+                        onTap: () async {
                           try {
                             await walletProvider.openWallet(wallet, uri);
-                            // ignore: use_build_context_synchronously
-                            Navigator.of(context).pop();
+                            if (!context.mounted) return;
+                            Navigator.of(context).pop();
                           } catch (e) {
-                            // ignore: use_build_context_synchronously
-                            Navigator.of(context).pop();
-                            // ignore: use_build_context_synchronously
-                            ScaffoldMessenger.of(context).showSnackBar(
+                            if (!context.mounted) return;
+                            Navigator.of(context).pop();
+                            ScaffoldMessenger.of(context).showSnackBar(
                               SnackBar(
                                 content: Text(e.toString()),
                                 backgroundColor:
-                                    // ignore: use_build_context_synchronously
                                     getThemeColors(context)['error'],
                               ),
                             );
                           }
                         },
📝 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
onTap: () async {
try {
await walletProvider.openWallet(wallet, uri);
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
} catch (e) {
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(e.toString()),
backgroundColor:
// ignore: use_build_context_synchronously
getThemeColors(context)['error'],
),
);
}
},
onTap: () async {
try {
await walletProvider.openWallet(wallet, uri);
if (!context.mounted) return;
Navigator.of(context).pop();
} catch (e) {
if (!context.mounted) return;
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(e.toString()),
backgroundColor:
getThemeColors(context)['error'],
),
);
}
},
🤖 Prompt for AI Agents
In lib/components/wallet_connect_dialog.dart around lines 76 to 94, the code
uses the BuildContext (Navigator and ScaffoldMessenger) after an await without
checking if the widget is still mounted and currently suppresses the lints;
update the onTap handler to check context.mounted immediately after the await
(e.g., if (!context.mounted) return;) before calling Navigator.of(context).pop()
or showing a SnackBar, and remove the // ignore: use_build_context_synchronously
comments so the code no longer suppresses the lint.

Comment on lines +145 to 157
onTap: () async {
await Clipboard.setData(ClipboardData(text: uri));
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('URI copied to clipboard!'),
// ignore: use_build_context_synchronously
backgroundColor: getThemeColors(context)['primary'],
),
);
},
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Guard Copy URI flow too.

Same mounted check after Clipboard.setData.

-                    onTap: () async {
-                      await Clipboard.setData(ClipboardData(text: uri));
-                      // ignore: use_build_context_synchronously
-                      Navigator.of(context).pop();
-                      // ignore: use_build_context_synchronously
-                      ScaffoldMessenger.of(context).showSnackBar(
+                    onTap: () async {
+                      await Clipboard.setData(ClipboardData(text: uri));
+                      if (!context.mounted) return;
+                      Navigator.of(context).pop();
+                      ScaffoldMessenger.of(context).showSnackBar(
                         SnackBar(
                           content: const Text('URI copied to clipboard!'),
-                          // ignore: use_build_context_synchronously
                           backgroundColor: getThemeColors(context)['primary'],
                         ),
                       );
                     },
🤖 Prompt for AI Agents
In lib/components/wallet_connect_dialog.dart around lines 145 to 157, after
awaiting Clipboard.setData the code uses Navigator and ScaffoldMessenger with
the build context without confirming the widget is still mounted; add a mounted
check immediately after the await (return early if not mounted) before calling
Navigator.of(context).pop() and showing the SnackBar so you don't use a stale
context.

Comment on lines +174 to +191
try {
logger.d("Attempting: encode(lat=$lat, lng=$lng)");
geohash = geoHasher.encode(lat, lng, precision: 12);
logger.d("Success with lat, lng order");
} catch (e1) {
logger.d("Failed with lat, lng order: $e1");
try {
// Alternative order: longitude first, latitude second
logger.d("Attempting: encode(lng=$lng, lat=$lat)");
geohash = geoHasher.encode(lng, lat, precision: 12);
logger.d("Success with lng, lat order");
} catch (e2) {
logger.e("Both parameter orders failed:");
logger.e(" lat, lng order error: $e1");
logger.e(" lng, lat order error: $e2");
throw Exception(
"Geohash generation failed with both parameter orders. lat,lng: $e1 | lng,lat: $e2");
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t swap geohash parameter order.

GeoHash encode is lat, lng. Retrying with lng, lat generates a valid-looking but wrong hash and silently corrupts location.

-        try {
-          logger.d("Attempting: encode(lat=$lat, lng=$lng)");
-          geohash = geoHasher.encode(lat, lng, precision: 12);
-          logger.d("Success with lat, lng order");
-        } catch (e1) {
-          logger.d("Failed with lat, lng order: $e1");
-          try {
-            // Alternative order: longitude first, latitude second
-            logger.d("Attempting: encode(lng=$lng, lat=$lat)");
-            geohash = geoHasher.encode(lng, lat, precision: 12);
-            logger.d("Success with lng, lat order");
-          } catch (e2) {
-            logger.e("Both parameter orders failed:");
-            logger.e("  lat, lng order error: $e1");
-            logger.e("  lng, lat order error: $e2");
-            throw Exception(
-                "Geohash generation failed with both parameter orders. lat,lng: $e1 | lng,lat: $e2");
-          }
-        }
+        try {
+          logger.d("Attempting: encode(lat=$lat, lng=$lng)");
+          geohash = geoHasher.encode(lat, lng, precision: 12);
+          logger.d("Geohash generated");
+        } catch (e1) {
+          logger.e("Geohash generation failed: $e1");
+          throw Exception("Geohash generation failed: $e1");
+        }
🤖 Prompt for AI Agents
In lib/pages/mint_nft/mint_nft_coordinates.dart around lines 174 to 191, remove
the fallback that retries geoHasher.encode with swapped parameters (lng, lat);
always call geoHasher.encode(lat, lng, precision: 12) and if it throws, log the
original error and rethrow (including the error details) instead of attempting
the lng,lat order which produces incorrect geohashes; ensure any catch only
reports the lat,lng error and fails fast, and optionally validate lat/lng ranges
before encoding to catch bad inputs early.

Comment on lines +33 to +70
Future<void> _loadUserOrganisations() async {
setState(() {
_isLoading = true;
_errorMessage = null;
});

try {
final walletProvider =
Provider.of<WalletProvider>(context, listen: false);

final result = await ContractReadFunctions.getOrganisationsByUser(
walletProvider: walletProvider,
);

if (result.success && result.data != null) {
final data = result.data as Map<String, dynamic>;
final organisations = data['organisations'] as List<dynamic>;

setState(() {
_userOrganisations = organisations.map((e) => e.toString()).toList();
_isLoading = false;
});

logger.d("User organisations loaded: $_userOrganisations");
} else {
setState(() {
_errorMessage = result.errorMessage ?? 'Failed to load organisations';
_isLoading = false;
});
}
} catch (e) {
logger.e("Error loading organisations: $e");
setState(() {
_errorMessage = 'Error loading organisations: $e';
_isLoading = false;
});
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add mounted checks around post‑await setState in _loadUserOrganisations.

Prevents setState after dispose on slow networks.

Apply this diff:

   final result = await ContractReadFunctions.getOrganisationsByUser(
     walletProvider: walletProvider,
   );
+  if (!mounted) return;

   if (result.success && result.data != null) {
     final data = result.data as Map<String, dynamic>;
     final organisations = data['organisations'] as List<dynamic>;

-    setState(() {
+    setState(() {
       _userOrganisations = organisations.map((e) => e.toString()).toList();
       _isLoading = false;
     });
   } else {
-    setState(() {
+    if (!mounted) return;
+    setState(() {
       _errorMessage = result.errorMessage ?? 'Failed to load organisations';
       _isLoading = false;
     });
   }
 } catch (e) {
   logger.e("Error loading organisations: $e");
-  setState(() {
+  if (!mounted) return;
+  setState(() {
     _errorMessage = 'Error loading organisations: $e';
     _isLoading = false;
   });
 }
🤖 Prompt for AI Agents
In lib/pages/mint_nft/mint_nft_organisation.dart around lines 33 to 70, the
method _loadUserOrganisations performs awaits then calls setState which can run
after the widget is disposed; add mounted checks before every post-await
setState (i.e., after the await
ContractReadFunctions.getOrganisationsByUser(...) and before the setState in
both the success and error branches, and inside the catch block before setState)
and return early if !mounted to prevent calling setState after dispose.

Comment on lines +69 to +89
if (result.success && result.data != null) {
setState(() {
_requestsByStatus[status] =
List<Map<String, dynamic>>.from(result.data['requests'] ?? []);
});
} else {
setState(() {
_errorMessages[status] =
result.errorMessage ?? 'Failed to load verification requests';
});
}
} catch (e) {
setState(() {
_errorMessages[status] = 'Error loading verification requests: $e';
});
logger.e('Error loading verification requests for status $status: $e');
} finally {
setState(() {
_isLoading[status] = false;
});
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add mounted checks before setState in async flows.

Widget may be disposed before responses arrive.

-      if (result.success && result.data != null) {
-        setState(() {
+      if (!mounted) return;
+      if (result.success && result.data != null) {
+        setState(() {
           _requestsByStatus[status] =
               List<Map<String, dynamic>>.from(result.data['requests'] ?? []);
         });
       } else {
-        setState(() {
+        setState(() {
           _errorMessages[status] =
               result.errorMessage ?? 'Failed to load verification requests';
         });
       }
...
-    } finally {
-      setState(() {
+    } finally {
+      if (!mounted) return;
+      setState(() {
         _isLoading[status] = false;
       });
     }

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

🤖 Prompt for AI Agents
In lib/widgets/organisation_details_page/tabs/verification_requests_tab.dart
around lines 69 to 89, the async result handling calls setState after awaits and
may run after the widget is disposed; add mounted checks before every setState
call (i.e., return early if !mounted) in the success, error, catch, and finally
branches so state is only updated when the State object is still mounted.

Comment on lines +140 to +145
BoxShadow(
color: getThemeColors(context)['shadow']!.withValues(alpha: 0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix: Color.withValues doesn’t exist on Flutter 3.16. Use withOpacity.

This won’t compile on v3.16. Replace with withOpacity(0.1).

-          BoxShadow(
-            color: getThemeColors(context)['shadow']!.withValues(alpha: 0.1),
+          BoxShadow(
+            color: getThemeColors(context)['shadow']!.withOpacity(0.1),
             blurRadius: 4,
             offset: const Offset(0, 2),
           ),
📝 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
BoxShadow(
color: getThemeColors(context)['shadow']!.withValues(alpha: 0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
BoxShadow(
color: getThemeColors(context)['shadow']!.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
🤖 Prompt for AI Agents
In lib/widgets/organisation_details_page/tabs/verification_requests_tab.dart
around lines 140 to 145, the code calls
getThemeColors(context)['shadow']!.withValues(alpha: 0.1) which does not exist
in Flutter 3.16; replace that call with withOpacity(0.1) on the Color instance
(i.e., getThemeColors(context)['shadow']!.withOpacity(0.1)) to fix the
compilation error.

Comment on lines +254 to +265
logger.d("Calling ContractReadFunctions.getProfileDetails...");
final String currentAddress = widget.userAddress;
final result = await ContractReadFunctions.getProfileDetails(
walletProvider: walletProvider,
currentAddress: currentAddress,
);

logger.d("Contract call completed:");
logger.d(" - Success: ${result.success}");
logger.d(" - Data not null: ${result.data != null}");
logger.d(" - Full result data: ${result.data}");

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use currentAddress param pattern matches read service; good. Add mounted checks before setState after awaits to prevent lifecycle issues.

-      final result = await ContractReadFunctions.getProfileDetails(
+      final result = await ContractReadFunctions.getProfileDetails(
         walletProvider: walletProvider,
         currentAddress: currentAddress,
       );
+      if (!mounted) return;

Apply similarly to subsequent setState blocks in this method.

📝 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
logger.d("Calling ContractReadFunctions.getProfileDetails...");
final String currentAddress = widget.userAddress;
final result = await ContractReadFunctions.getProfileDetails(
walletProvider: walletProvider,
currentAddress: currentAddress,
);
logger.d("Contract call completed:");
logger.d(" - Success: ${result.success}");
logger.d(" - Data not null: ${result.data != null}");
logger.d(" - Full result data: ${result.data}");
logger.d("Calling ContractReadFunctions.getProfileDetails...");
final String currentAddress = widget.userAddress;
final result = await ContractReadFunctions.getProfileDetails(
walletProvider: walletProvider,
currentAddress: currentAddress,
);
if (!mounted) return;
logger.d("Contract call completed:");
logger.d(" - Success: ${result.success}");
logger.d(" - Data not null: ${result.data != null}");
logger.d(" - Full result data: ${result.data}");
🤖 Prompt for AI Agents
In lib/widgets/profile_widgets/profile_section_widget.dart around lines 254 to
265, after the await call to ContractReadFunctions.getProfileDetails you must
guard any following setState calls with a mounted check to avoid updating state
after the widget is disposed; capture the result, then add "if (!mounted)
return;" before calling setState and wrap the state mutation inside that
setState, and apply the same mounted-check-before-setState pattern to every
other setState block later in this method that runs after an await.

Comment on lines +55 to +95
try {
final walletProvider =
Provider.of<WalletProvider>(context, listen: false);

final result = await ContractReadFunctions.getProfileDetailsByAddress(
walletProvider: walletProvider,
userAddress: widget.userAddress,
);

if (result.success && result.data != null) {
final List data = result.data['profile'] ?? [];
final List verifierTokensData = result.data['verifierTokens'] ?? [];

setState(() {
_isLoading = false;
if (data.isEmpty) {
_isNotRegistered = true;
} else {
_userProfileData = UserProfileData.fromContractData(data);
_verifierTokens = verifierTokensData
.map((token) => VerificationDetails.fromContractData(token))
.toList();
}
});
} else {
setState(() {
_isLoading = false;
_errorMessage = result.errorMessage ?? 'Failed to load user profile';
if (result.errorMessage?.contains('not registered') ?? false) {
_isNotRegistered = true;
}
});
}
} catch (e) {
logger.e("Error loading user profile", error: e);
setState(() {
_isLoading = false;
_errorMessage = 'Error: ${e.toString()}';
});
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add mounted checks before calling setState after awaits to avoid setState on disposed widget when navigating away mid-load.

-      if (result.success && result.data != null) {
+      if (!mounted) return;
+      if (result.success && result.data != null) {
         setState(() { ... });
-      } else {
+      } else {
+        if (!mounted) return;
         setState(() { ... });
       }
-    } catch (e) {
+    } catch (e) {
       logger.e("Error loading user profile", error: e);
-      setState(() {
+      if (!mounted) return;
+      setState(() {
         _isLoading = false;
         _errorMessage = 'Error: ${e.toString()}';
       });
     }
📝 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
try {
final walletProvider =
Provider.of<WalletProvider>(context, listen: false);
final result = await ContractReadFunctions.getProfileDetailsByAddress(
walletProvider: walletProvider,
userAddress: widget.userAddress,
);
if (result.success && result.data != null) {
final List data = result.data['profile'] ?? [];
final List verifierTokensData = result.data['verifierTokens'] ?? [];
setState(() {
_isLoading = false;
if (data.isEmpty) {
_isNotRegistered = true;
} else {
_userProfileData = UserProfileData.fromContractData(data);
_verifierTokens = verifierTokensData
.map((token) => VerificationDetails.fromContractData(token))
.toList();
}
});
} else {
setState(() {
_isLoading = false;
_errorMessage = result.errorMessage ?? 'Failed to load user profile';
if (result.errorMessage?.contains('not registered') ?? false) {
_isNotRegistered = true;
}
});
}
} catch (e) {
logger.e("Error loading user profile", error: e);
setState(() {
_isLoading = false;
_errorMessage = 'Error: ${e.toString()}';
});
}
}
try {
final walletProvider =
Provider.of<WalletProvider>(context, listen: false);
final result = await ContractReadFunctions.getProfileDetailsByAddress(
walletProvider: walletProvider,
userAddress: widget.userAddress,
);
if (!mounted) return;
if (result.success && result.data != null) {
final List data = result.data['profile'] ?? [];
final List verifierTokensData = result.data['verifierTokens'] ?? [];
setState(() {
_isLoading = false;
if (data.isEmpty) {
_isNotRegistered = true;
} else {
_userProfileData = UserProfileData.fromContractData(data);
_verifierTokens = verifierTokensData
.map((token) => VerificationDetails.fromContractData(token))
.toList();
}
});
} else {
if (!mounted) return;
setState(() {
_isLoading = false;
_errorMessage = result.errorMessage ?? 'Failed to load user profile';
if (result.errorMessage?.contains('not registered') ?? false) {
_isNotRegistered = true;
}
});
}
} catch (e) {
logger.e("Error loading user profile", error: e);
if (!mounted) return;
setState(() {
_isLoading = false;
_errorMessage = 'Error: ${e.toString()}';
});
}
}
🤖 Prompt for AI Agents
lib/widgets/profile_widgets/user_profile_viewer_widget.dart around lines 55 to
95: after the async await call to
ContractReadFunctions.getProfileDetailsByAddress (and any other await), guard
any subsequent setState calls with a mounted check to avoid calling setState on
a disposed widget; specifically, immediately after the await(s) add a check like
if (!mounted) return; then proceed to call setState in both the success and
error branches (and the catch block) so setState is only invoked when the widget
is still mounted.

Comment on lines +612 to +639
Widget _buildErrorWidget() {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: getThemeColors(context)['error']!,
borderRadius: BorderRadius.circular(buttonCircularRadius),
border: Border.all(
color: getThemeColors(context)['error']!,
width: 2,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.error_outline,
size: 64,
color: getThemeColors(context)['error'],
),
const SizedBox(height: 16),
Text(
'Error Loading Profile',
style: TextStyle(
color: getThemeColors(context)['error'],
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Error UI uses the same “error” color for background, icon, and text, making it unreadable. Use contrasting text/icon colors.

-      decoration: BoxDecoration(
-        color: getThemeColors(context)['error']!,
+      decoration: BoxDecoration(
+        color: getThemeColors(context)['background'],
         ...
-      child: Column(
+      child: Column(
         children: [
-          Icon(
-            Icons.error_outline,
-            size: 64,
-            color: getThemeColors(context)['error'],
-          ),
+          Icon(Icons.error_outline, size: 64, color: getThemeColors(context)['error']),
...
-          Text('Error Loading Profile', style: TextStyle(color: getThemeColors(context)['error'], ...)),
+          Text('Error Loading Profile', style: TextStyle(color: getThemeColors(context)['textPrimary'], ...)),
...
-          Text(_errorMessage ?? 'Unknown error', style: TextStyle(color: getThemeColors(context)['textSecondary'], ...)),
+          Text(_errorMessage ?? 'Unknown error', style: TextStyle(color: getThemeColors(context)['textSecondary'], ...)),

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

🤖 Prompt for AI Agents
In lib/widgets/profile_widgets/user_profile_viewer_widget.dart around lines 612
to 639 the error widget is using the same getThemeColors(context)['error'] value
for background, border, icon and text which makes content unreadable; change it
so the container background uses the error color but icon and text use a
contrasting foreground (for example getThemeColors(context)['onError'] or
Colors.white), and keep the border as either a darker error shade or transparent
to preserve contrast; update all color references in this block (container
decoration color, border color, Icon color, and Text style color) to use the
appropriate background vs foreground keys from the theme so the icon and text
are readable against the error background.

@Zahnentferner Zahnentferner merged commit 02cb1fd into main Oct 26, 2025
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants