Skip to content

Conversation

@IronJam11
Copy link
Contributor

@IronJam11 IronJam11 commented Aug 29, 2025

✨ New Features

User NFT Portfolio Page - Complete dashboard displaying owned NFTs with status indicators
Verification Management - NFT owners can add/remove verifiers through intuitive modals
Interactive Plantation Map - Static map displaying NFT plantation locations
Enhanced Modals - Streamlined "Add Verification" and "View Verifications" interfaces

🔧 Key Improvements

Responsive card-based layout for NFT display
Modal-driven UI patterns for consistent UX
Owner-controlled verification system
Geographic visualization of agricultural assets

UI:

Screenshot 2025-08-30 at 3 32 13 AM Screenshot 2025-08-30 at 3 32 05 AM Screenshot 2025-08-30 at 3 31 58 AM Screenshot 2025-08-30 at 2 19 31 AM Screenshot 2025-08-30 at 2 21 20 AM

Summary by CodeRabbit

  • New Features
    • Interactive Tree NFT details: view full metadata, map location, and verifier list; submit verifications with descriptions and images; remove verifications where permitted.
    • Static map display for tree locations with loading/error handling.
  • Bug Fixes
    • “Connect Wallet” button now initiates wallet connection.
    • More robust map behavior with coordinate validation and clearer fallbacks.
  • Style
    • UI colors updated to solid tones for improved contrast and consistency.
  • Chores
    • Added Flutter CI pipeline for analyze/format/build.
    • Expanded environment template with app ID and contract addresses.

@coderabbitai
Copy link

coderabbitai bot commented Aug 29, 2025

Walkthrough

Adds env variables and CI workflow. Introduces env-driven contract address usage. Implements tree details loading with contract reads, verification submission/removal via contract writes, and a new static map widget. Refactors UI components for opacity, layout, and constructors. Adds a Tree model, verifiers UI, and connects wallet button action.

Changes

Cohort / File(s) Summary of Changes
Environment & ABI/Address config
.env.stencil
Adds APPLICATION_ID and multiple CONTRACT_ADDRESS variables; reorganizes stencil comments.
Env-driven contract address
lib/utils/constants/contract_abis/tree_nft_contract_abi.dart, lib/utils/constants/contractDetails.dart
Replaces embedded ABI constant with env-driven treeNFtContractAddress; removes commented addresses.
CI pipeline
.github/workflows/flutter.yaml
Adds Flutter CI workflow: checkout, .env copy, setup Flutter, pub get, analyze, format check, build release APK.
Contract services (reads/writes)
lib/utils/services/contract_read_services.dart, lib/utils/services/contract_write_functions.dart
Switches to treeNFtContractAddress/treeNftContractABI; adds getTreeNFTInfo, verifyTree, and removeVerification.
Models
lib/models/tree_details.dart
Adds Tree data model with factory fromContractData and helper _toInt.
Wallet/connect utilities
lib/models/wallet_chain_option.dart, lib/utils/services/switch_chain_utils.dart, lib/components/wallet_connect_dialog.dart, lib/providers/wallet_provider.dart, lib/widgets/wallet_not_connected_widget.dart
Renames Alchemy key variable; minor Iterable change; add lint ignores; add wallet connect action; add ignore for unused method.
Tree details & verifiers UI
lib/pages/tree_details_page.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/tree_nft_view_widget.dart, lib/widgets/nft_display_utils/user_nfts_widget.dart
Refactors TreeDetailsPage to stateful with data load and verify flow; adds verifiers parsing/model/UI and removal; minor shadow/ lint updates; updates UserNftsWidget constructor and header.
Map widgets
lib/widgets/map_widgets/flutter_map_widget.dart, lib/widgets/map_widgets/static_map_display_widget.dart
Updates constructors; improves coordinate sanitization and error handling; adds new StaticCoordinatesMap with loading/error overlays and controls.
Mint NFT flow
lib/pages/mint_nft/mint_nft_coordinates.dart, lib/pages/mint_nft/mint_nft_details.dart, lib/pages/mint_nft/mint_nft_images.dart, lib/providers/mint_nft_provider.dart
Fixes import case; removes opacity modifiers; adjusts provider lookup timing; removes unused _detailsHash.
General UI tweaks
lib/components/universal_navbar.dart, lib/pages/register_user_page.dart, lib/widgets/profile_widgets/profile_section_widget.dart, lib/pages/home_page.dart
Replaces Containers with SizedBox where sizing only; removes opacity modifiers; restructures tokens UI; removes unused imports.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant UI as TreeDetailsPage (Stateful)
  participant Wallet as WalletProvider
  participant Read as ContractReadFunctions
  participant Env as dotenv
  Note over UI,Read: Load Tree Details
  User->>UI: Open Tree NFT details
  UI->>Wallet: getUserAddress()
  UI->>Env: Read TREE_NFT_CONTRACT_ADDRESS
  UI->>Read: getTreeNFTInfo(id, offset, limit)
  Read->>Wallet: readContract(getTreeDetailsbyID)
  Read->>Wallet: readContract(getTreeNftVerifiersPaginated)
  Read->>Wallet: readContract(ownerOf)
  Read-->>UI: {details, verifiers, owner}
  UI->>UI: Parse -> Tree model, canVerify
  UI-->>User: Render map, details, verifiers
Loading
sequenceDiagram
  autonumber
  actor Verifier as User
  participant UI as TreeDetailsPage
  participant Modal as _VerificationModal
  participant IPFS as uploadToIPFS
  participant Write as ContractWriteFunctions
  participant Wallet as WalletProvider
  Note over UI,Modal: Submit Verification
  Verifier->>UI: Tap "Verify"
  UI->>Modal: Open (collect desc + photos)
  Modal->>IPFS: uploadToIPFS(photo[0..n])
  IPFS-->>Modal: [hashes]
  Modal->>Write: verifyTree(treeId, desc, hashes)
  Write->>Wallet: writeContract(verify)
  Wallet-->>Write: tx result / error
  alt success
    Write-->>Modal: success
    Modal-->>UI: close, notify
    UI->>Read: refresh getTreeNFTInfo(...)
    UI-->>Verifier: Updated verifiers list
  else failure
    Write-->>Modal: error
    Modal-->>Verifier: Show SnackBar error
  end
Loading
sequenceDiagram
  autonumber
  actor User
  participant Widget as WalletNotConnectedWidget
  participant Provider as WalletProvider
  User->>Widget: Press "Connect Wallet"
  Widget->>Provider: connectWallet()
  Provider-->>Widget: connection status
  Widget-->>User: Proceed based on status
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • ceilican
  • bhavik-mangla

Poem

I tapped the leaves of code today, 🌿
New roots in .env found their way.
Maps now bloom with steady hue,
Verifiers nod—“this tree is true.”
CI hums a gentle tune,
While wallets link beneath the moon.
Hop hop—ship it soon! 🐇✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch aaryan-NftDetailPage

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

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

Copy link

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

Caution

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

⚠️ Outside diff range comments (24)
lib/providers/wallet_provider.dart (5)

13-15: Missing web3dart imports break build where DeployedContract/Web3Client/EthereumAddress are used.
Add the proper import(s) so read/write paths compile.

Apply:

 import 'dart:convert';
 import 'package:http/http.dart' as http;
+import 'package:web3dart/web3dart.dart';

257-258: Expose userAddress properly (currently always null).
Downstream code likely expects this to reflect the session address.

-  get userAddress => null;
+  String? get userAddress => _currentAddress;

382-397: Chain switching only mutates local state; wallet remains on old chain. Also uses wrong name lookup.
This will show “switched” UI while transactions still execute on the prior chain. Implement wallet_switchEthereumChain (and fallback to wallet_addEthereumChain) via WalletConnect, and only update state on success. Also fix ${chainInfo['name']} which is indexing the map incorrectly.

   Future<bool> switchChain(String newChainId) async {
     logger.d('[switchChain] Requested chain id: $newChainId');
     if (!_isConnected) {
       logger.e('[switchChain] Wallet not connected.');
       throw Exception('Wallet not connected');
     }

-    if (_currentChainId == newChainId) {
+    if (_currentChainId == newChainId) {
       logger.d('[switchChain] Already on chain $newChainId, skipping switch.');
       return true;
     }
-    _updateStatus('Switching to ${chainInfo['name']}...');
-    _currentChainId = newChainId;
-    notifyListeners();
-    return true;
+    final sessions = _web3App!.sessions.getAll();
+    if (sessions.isEmpty) throw Exception('No active WalletConnect session');
+    final session = sessions.first;
+
+    final hexChain = '0x${int.parse(newChainId).toRadixString(16)}';
+    final targetInfo = _chainInfo[newChainId];
+    _updateStatus(
+        'Switching to ${targetInfo?['name'] ?? newChainId}...');
+    try {
+      await _web3App!.request(
+        topic: session.topic,
+        chainId: 'eip155:${_currentChainId ?? _correctChainId}',
+        request: const SessionRequestParams(
+          // placeholder, replaced below
+          method: '',
+          params: [],
+        ).copyWith(
+          method: 'wallet_switchEthereumChain',
+          params: [
+            {'chainId': hexChain}
+          ],
+        ),
+      );
+    } catch (e) {
+      // Try add-and-switch if the chain is not known to the wallet.
+      if (targetInfo != null) {
+        try {
+          await _web3App!.request(
+            topic: session.topic,
+            chainId: 'eip155:${_currentChainId ?? _correctChainId}',
+            request: SessionRequestParams(
+              method: 'wallet_addEthereumChain',
+              params: [
+                {
+                  'chainId': hexChain,
+                  'chainName': targetInfo['name'],
+                  'rpcUrls': [targetInfo['rpcUrl']],
+                  'nativeCurrency': targetInfo['nativeCurrency'],
+                  'blockExplorerUrls': [targetInfo['blockExplorerUrl']],
+                }
+              ],
+            ),
+          );
+        } catch (e2) {
+          _updateStatus('Failed to switch chain: $e2');
+          rethrow;
+        }
+      } else {
+        _updateStatus('Unsupported chain id: $newChainId');
+        rethrow;
+      }
+    }
+    _currentChainId = newChainId;
+    notifyListeners();
+    return true;
   }

405-442: Ensure HTTP client is closed on all paths in readContract; also accept both String and List ABIs consistently.
Currently, httpClient is only closed on success, leaking on errors.

   Future<dynamic> readContract({
@@
   }) async {
     try {
       if (!_isConnected || _web3App == null || _currentChainId == null) {
         throw Exception('Wallet not connected');
       }
       _updateStatus('Reading from contract...');
-      List<dynamic> abiList;
-      if (abi is String) {
-        abiList = json.decode(abi);
-      } else if (abi is List) {
-        abiList = abi;
-      } else {
-        throw Exception('Invalid ABI format');
-      }
+      final List<dynamic> abiList = abi is String
+          ? (json.decode(abi) as List<dynamic>)
+          : (abi is List ? abi as List<dynamic> : throw Exception('Invalid ABI format'));
@@
-      final httpClient = http.Client();
-      final ethClient = Web3Client(rpcUrl!, httpClient);
-      final result = await ethClient.call(
-        contract: contract,
-        function: function,
-        params: params,
-      );
-
-      httpClient.close();
-      _updateStatus('Contract read successful');
-
-      return result;
+      final httpClient = http.Client();
+      try {
+        final ethClient = Web3Client(rpcUrl!, httpClient);
+        final result = await ethClient.call(
+          contract: contract,
+          function: function,
+          params: params,
+        );
+        _updateStatus('Contract read successful');
+        return result;
+      } finally {
+        httpClient.close();
+      }

460-503: Harden writeContract: handle ABI types consistently and close HTTP client in finally.
Also avoid as String cast on rpcUrl when you can assert non-null.

-      final abiList = json.decode(abi) as List<dynamic>;
+      final List<dynamic> abiList = abi is String
+          ? (json.decode(abi) as List<dynamic>)
+          : (abi is List ? abi as List<dynamic> : throw Exception('Invalid ABI format'));
@@
-      final rpcUrl = getChainDetails(targetChainId).first['rpcUrl'] as String?;
-      final httpClient = http.Client();
-      final ethClient = Web3Client(rpcUrl as String, httpClient);
+      final rpcUrl = getChainDetails(targetChainId).first['rpcUrl'] as String?;
+      final httpClient = http.Client();
+      final ethClient = Web3Client(rpcUrl!, httpClient);
@@
-      final gasPrice = await ethClient.getGasPrice();
-      httpClient.close();
+      final gasPrice = await ethClient.getGasPrice();
+      httpClient.close(); // ok to close early; request below uses WalletConnect
lib/utils/services/ipfs_services.dart (1)

9-31: Always reset uploading state; add timeout; surface error.
If the request throws, setUploadingState(false) never runs. Wrap with try/finally and consider a timeout.

 Future<String?> uploadToIPFS(
     File imageFile, Function(bool) setUploadingState) async {
-  setUploadingState(true);
-
-  var url = Uri.parse("https://api.pinata.cloud/pinning/pinFileToIPFS");
-  var request = http.MultipartRequest("POST", url);
-  request.headers.addAll({
-    "pinata_api_key": apiKey,
-    "pinata_secret_api_key": apiSecret,
-  });
-
-  request.files.add(await http.MultipartFile.fromPath("file", imageFile.path));
-  var response = await request.send();
-
-  setUploadingState(false);
-
-  if (response.statusCode == 200) {
-    var jsonResponse = json.decode(await response.stream.bytesToString());
-    return "https://gateway.pinata.cloud/ipfs/${jsonResponse['IpfsHash']}";
-  } else {
-    return null;
-  }
+  setUploadingState(true);
+  try {
+    if (apiKey.isEmpty || apiSecret.isEmpty) {
+      return null;
+    }
+    final url = Uri.parse("https://api.pinata.cloud/pinning/pinFileToIPFS");
+    final request = http.MultipartRequest("POST", url)
+      ..headers.addAll({
+        "pinata_api_key": apiKey,
+        "pinata_secret_api_key": apiSecret,
+      })
+      ..files.add(await http.MultipartFile.fromPath("file", imageFile.path));
+    final response =
+        await request.send().timeout(const Duration(seconds: 30));
+    if (response.statusCode == 200) {
+      final jsonResponse = json.decode(await response.stream.bytesToString());
+      return "https://gateway.pinata.cloud/ipfs/${jsonResponse['IpfsHash']}";
+    }
+    return null;
+  } finally {
+    setUploadingState(false);
+  }
 }
lib/pages/mint_nft/mint_nft_images.dart (1)

56-85: Guard setState() and provider usage with mounted checks after awaits

Multiple setState() calls are executed after awaited operations; if the widget unmounts mid-upload, this will throw.

Add early returns:

@@
-        setState(() {
+        if (!mounted) return;
+        setState(() {
           _uploadingIndex = i;
         });
@@
-            setState(() {
+            if (!mounted) return;
+            setState(() {
               _uploadedHashes.add(hash);
             });
@@
-      setState(() {
+      if (!mounted) return;
+      setState(() {
         _isUploading = false;
         _uploadingIndex = -1;
         _processingImages.clear();
       });

Also applies to: 91-97

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

200-213: Substring on possibly-empty address can crash

If parsing fails, userAddress may be '', causing RangeError on substring. Use a safe formatter.

-            Text(
-              '${_userProfileData!.userAddress.substring(0, 6)}...${_userProfileData!.userAddress.substring(_userProfileData!.userAddress.length - 4)}',
+            Text(
+              _shortAddress(_userProfileData!.userAddress),

Add helper within the State class:

+  String _shortAddress(String addr) {
+    if (addr.isEmpty || addr.length <= 10) return addr;
+    return '${addr.substring(0, 6)}...${addr.substring(addr.length - 4)}';
+  }

504-521: Surface errors instead of dereferencing null profile data

When fetch fails (but isn’t “not registered”), build still tries to render profile/token widgets using null _userProfileData. Use _buildErrorState and drop the ignore.

-        child: _isLoading
-            ? _buildLoadingState()
-            : _isNotRegistered
-                ? _buildNotRegisteredState()
-                : Row(
+        child: _isLoading
+            ? _buildLoadingState()
+            : _isNotRegistered
+                ? _buildNotRegisteredState()
+                : (_errorMessage != null || _userProfileData == null)
+                    ? _buildErrorState()
+                    : Row(
-  // ignore: unused_element

Also applies to: 358-358

lib/pages/register_user_page.dart (2)

279-289: Icon becomes invisible: foreground matches background

Container and icon both use Color(0xFF1CD381), making the camera icon unreadable.

-              decoration: BoxDecoration(
-                color: const Color(0xFF1CD381),
+              decoration: BoxDecoration(
+                color: const Color(0xFF1CD381),
                 borderRadius: BorderRadius.circular(8),
               ),
               child: const Icon(
                 Icons.camera_alt,
-                color: Color(0xFF1CD381),
+                color: Colors.white,
                 size: 18,
               ),

447-453: Same contrast issue in form field labels

Foreground icon color equals the new solid background.

-            decoration: BoxDecoration(
-              color: const Color(0xFF1CD381),
+            decoration: BoxDecoration(
+              color: const Color(0xFF1CD381),
               borderRadius: BorderRadius.circular(8),
             ),
             child: Icon(
               icon,
-              color: const Color(0xFF1CD381),
+              color: Colors.white,
               size: 18,
             ),
lib/components/wallet_connect_dialog.dart (2)

43-55: Replace lint suppressions with mounted checks

Don’t silence use_build_context_synchronously; guard UI calls after awaits.

-                        // 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) {
+                          Navigator.of(context).pop();
+                          ScaffoldMessenger.of(context).showSnackBar(
                             SnackBar(
                               content: Text(e.toString()),
                               backgroundColor: Colors.red,
                             ),
                           );
+                        }

81-89: Same here for “Copy URI” flow

Guard dialog dismissal and SnackBar with context.mounted.

-                    // 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(
                       const SnackBar(
                         content: Text('URI copied to clipboard!'),
                         backgroundColor: Colors.green,
                       ),
                     );
lib/components/universal_navbar.dart (1)

269-273: Incorrect use of Flexible inside a SizedBox (will throw at runtime)

Flexible must be a direct child of a Flex (Row/Column). This will assert.

-              SizedBox(
-                width: 10,
-                child: Flexible(
-                  child: Text(
+              Flexible(
+                child: Text(
                     formatAddress(walletProvider.currentAddress!),
                     style: TextStyle(
                       color: Colors.green[700],
                       fontWeight: FontWeight.w600,
                       fontSize: 10,
                     ),
                     overflow: TextOverflow.ellipsis,
-                  ),
                 ),
lib/pages/mint_nft/mint_nft_details.dart (3)

140-147: Fix invisible header icon (white-on-white).

The icon color is white inside a white container, making it invisible.

Apply:

-                  child: const Icon(
-                    Icons.edit_note,
-                    color: Colors.white,
-                    size: 28,
-                  ),
+                  child: const Icon(
+                    Icons.edit_note,
+                    color: Color(0xFF1CD381), // contrast against white bg
+                    size: 28,
+                  ),

223-231: Arrow icon is invisible (inherits white on white).

The ElevatedButton sets foregroundColor to white; the arrow icon (no explicit color) sits on a white chip.

Apply:

-                          child: const Icon(
-                            Icons.arrow_forward,
-                            size: 18,
-                          ),
+                          child: const Icon(
+                            Icons.arrow_forward,
+                            color: Color(0xFF1CD381),
+                            size: 18,
+                          ),

259-269: Green icon on green badge (no contrast).

Icon and background both use Color(0xFF1CD381).

Apply one of:

-              decoration: BoxDecoration(
-                color: const Color(0xFF1CD381),
+              decoration: BoxDecoration(
+                color: const Color(0xFF1CD381),
                 borderRadius: BorderRadius.circular(8),
               ),
-              child: Icon(
-                icon,
-                color: const Color(0xFF1CD381),
+              child: Icon(
+                icon,
+                color: Colors.white,
                 size: 18,
               ),
lib/widgets/map_widgets/flutter_map_widget.dart (1)

343-347: Type error: clamp returns num, assigned to double.

This will fail strong analysis; cast to double.

-    double latitude = _sanitizeCoordinate(widget.lat, _defaultLat);
-    double longitude = _sanitizeCoordinate(widget.lng, _defaultLng);
-    latitude = latitude.clamp(-90.0, 90.0);
-    longitude = longitude.clamp(-180.0, 180.0);
+    double latitude = _sanitizeCoordinate(widget.lat, _defaultLat);
+    double longitude = _sanitizeCoordinate(widget.lng, _defaultLng);
+    latitude = latitude.clamp(-90.0, 90.0).toDouble();
+    longitude = longitude.clamp(-180.0, 180.0).toDouble();
lib/widgets/nft_display_utils/user_nfts_widget.dart (5)

134-156: Broken pagination: always fetching the first page.

offset and limit are hardcoded to 0,10, so “load more” re-requests page 0. Use _currentPage/_itemsPerPage and only increment on success.

Apply:

   final result = await ContractReadFunctions.getNFTsByUserPaginated(
     walletProvider: walletProvider,
-    offset: 0,
-    limit: 10,
+    offset: _currentPage * _itemsPerPage,
+    limit: _itemsPerPage,
   );
@@
-          _currentPage++;
+          _currentPage++;
           _hasMore = _nfts.length < _totalCount;

Also applies to: 165-173


150-154: Wrong user scope: ignores userAddress prop.

The read function pulls NFTs for the connected wallet only. This widget accepts userAddress, so viewing another user’s NFTs won’t work.

Either:

  • Update getNFTsByUserPaginated to accept a target address (see proposed change in contract_read_services.dart), and pass widget.userAddress here; or
  • Rename/remove userAddress from this widget to match behavior.

Proposed call after API change:

-  final result = await ContractReadFunctions.getNFTsByUserPaginated(
-    walletProvider: walletProvider,
-    offset: _currentPage * _itemsPerPage,
-    limit: _itemsPerPage,
-  );
+  final result = await ContractReadFunctions.getNFTsByUserPaginated(
+    walletProvider: walletProvider,
+    addressHex: widget.userAddress,
+    offset: _currentPage * _itemsPerPage,
+    limit: _itemsPerPage,
+  );

250-254: Latitude/longitude decoding inconsistent with mint encoding.

Contract stores lat/lng as (lat+90)*1e6 and (lng+180)*1e6. The display should subtract offsets.

-                    'Location: ${tree.latitude / 1000000}, ${tree.longitude / 1000000}',
+                    'Location: ${(tree.latitude / 1e6) - 90.0}, ${(tree.longitude / 1e6) - 180.0}',

283-295: Alive/Deceased label contradicts badge color.

If death == 0, it’s alive; current label shows “Deceased” because 0 < now. Handle the sentinel.

-                  color: tree.death == 0 ? Colors.green : Colors.red,
+                  color: tree.death == 0 ||
+                          tree.death > DateTime.now().millisecondsSinceEpoch ~/ 1000
+                      ? Colors.green
+                      : Colors.red,
@@
-                  tree.death < DateTime.now().millisecondsSinceEpoch ~/ 1000
-                      ? 'Deceased'
-                      : 'Alive',
+                  (tree.death == 0 ||
+                          tree.death > DateTime.now().millisecondsSinceEpoch ~/ 1000)
+                      ? 'Alive'
+                      : 'Deceased',

Also applies to: 287-289


8-101: Duplicate Tree model defined in multiple places.

This file defines Tree, and tree_nft_details_verifiers_widget.dart defines another Tree with different fields. Divergence is likely and parsing bugs will creep in.

Extract a single Tree model (e.g., lib/models/tree_details.dart) and import it across widgets. Remove this local definition.

lib/utils/services/contract_read_services.dart (1)

38-42: Allow reading NFTs for arbitrary addresses.

Current API always reads for the connected wallet. Add an optional addressHex to support viewing another user’s portfolio (used by UserNftsWidget).

-  static Future<ContractReadResult> getNFTsByUserPaginated({
-    required WalletProvider walletProvider,
-    int offset = 0,
-    int limit = 10,
-  }) async {
+  static Future<ContractReadResult> getNFTsByUserPaginated({
+    required WalletProvider walletProvider,
+    String? addressHex,
+    int offset = 0,
+    int limit = 10,
+  }) async {
@@
-      final String address = walletProvider.currentAddress.toString();
+      final String address =
+          (addressHex ?? walletProvider.currentAddress?.toString() ?? '').trim();
       if (!address.startsWith('0x')) {
         return ContractReadResult.error(
           errorMessage: 'Invalid wallet address format',
         );
       }
@@
-      final EthereumAddress userAddress = EthereumAddress.fromHex(address);
+      final EthereumAddress userAddress = EthereumAddress.fromHex(address);

Also mirror the pagination validation used here into other read methods as needed.

Also applies to: 51-59, 66-77

🧹 Nitpick comments (39)
lib/providers/wallet_provider.dart (8)

39-39: Getter type is nullable but source is not.
_correctChainId is non-null; expose as non-nullable.

-  String? get correctChainId => _correctChainId;
+  String get correctChainId => _correctChainId;

43-43: Remove stray debug getter g.
This shadows nothing, leaks internal state with a meaningless name, and invites misuse.

-  bool get g => _isConnecting;

114-114: No-op string expression; replace with logging.
The bare string does nothing. Use logger.

-        ('Session connected: ${event.session.topic}');
+        logger.d('Session connected: ${event.session.topic}');

371-373: Incorrect emptiness check.
if (!sessions.isNotEmpty) is confusing; use isEmpty.

-    if (!sessions.isNotEmpty) {
+    if (sessions.isEmpty) {

583-587: No-op replaceAll; clean up.
replaceAll('://','://') does nothing.

-      if (!deepLinkBase.endsWith('://')) {
-        deepLinkBase = deepLinkBase.replaceAll('://', '://');
-      }
+      if (!deepLinkBase.endsWith('://')) {
+        deepLinkBase = '$deepLinkBase://';
+      }

399-441: Consider EIP-1559 fields for better wallet compatibility.
Including maxFeePerGas and maxPriorityFeePerGas improves UX on chains that prefer them over legacy gasPrice. Optional but recommended.


363-367: Dead method placeholder.
getSupportedChainsWithStatus throws then returns; either implement or remove.

Want me to wire this to compute “current” vs “supported” with reasons?


1-735: Prune unused WalletConnect RPC methods in both requiredNamespaces and optionalNamespaces
Only request the RPC calls you actually invoke—‘eth_sendTransaction’ and ‘personal_sign’—and remove the others (eth_signTransaction, eth_sign, eth_signTypedData, wallet_switchEthereumChain and all read-only methods like eth_call, eth_getBalance, eth_getTransactionCount, eth_getTransactionReceipt, eth_estimateGas) to minimize permission scope.

lib/utils/services/switch_chain_utils.dart (1)

66-73: SnackBar says “switched” even if wallet didn’t switch.
Given WalletProvider.switchChain currently only updates local state, the wallet remains on the old chain. After fixing switchChain to use wallet_switchEthereumChain, this will be accurate; until then, consider deferring the success SnackBar until the request returns without throwing.

lib/widgets/wallet_not_connected_widget.dart (1)

25-32: Optional: disable while connecting to prevent duplicate sessions.
Wire onPressed to null when walletProvider.isConnecting and show a progress indicator.

lib/models/wallet_chain_option.dart (3)

4-5: Handle missing ALCHEMY_API_KEY explicitly.
Empty key yields 401s; fail fast or fall back to public RPCs.

-final String alchemyApiKey = dotenv.env['ALCHEMY_API_KEY'] ?? '';
+final String alchemyApiKey = dotenv.env['ALCHEMY_API_KEY'] ?? '';
+// Optional: assert or warn when empty
+// assert(alchemyApiKey.isNotEmpty, 'ALCHEMY_API_KEY is missing');

22-30: Make static wallet options const.
Saves allocs and clarifies immutability.

-final List<WalletOption> walletOptionsList = [
-  WalletOption(
+final List<WalletOption> walletOptionsList = const [
+  WalletOption(
     name: 'MetaMask',
     deepLink: 'metamask://wc?uri=',
     fallbackUrl: 'https://metamask.app.link/wc?uri=',
     icon: Icons.account_balance_wallet,
     color: Colors.orange,
   ),
 ];

32-58: Avoid duplicating RPC URLs across rpcUrls and chainInfoList.
Source chainInfoList['rpcUrl'] from rpcUrls to keep a single truth.

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

254-262: Restore translucency for “completed” overlay to preserve image legibility

Solid green fully obscures the thumbnail; keep partial opacity.

-                                          color: Colors.green,
+                                          color: Colors.green.withOpacity(0.8),
lib/widgets/profile_widgets/profile_section_widget.dart (2)

49-52: Fix log message copy: references “Tree data” in profile parser

Minor but confusing during debugging.

-      logger.d("Error parsing Tree data: $e");
+      logger.d("Error parsing UserProfile data: $e");

246-355: Reduce duplication in token tiles

Four near-identical blocks; create a small builder to keep copy changes consistent.

Example:

+  Widget _tokenTile(Color bg, String label, int value) => Padding(
+    padding: const EdgeInsets.all(4.0),
+    child: SizedBox(
+      height: 40, width: 150,
+      child: Container(
+        decoration: BoxDecoration(
+          color: bg,
+          border: Border.all(color: Colors.black, width: 2),
+          borderRadius: BorderRadius.circular(12),
+          boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 6, offset: Offset(0,3))],
+        ),
+        child: Center(child: Text('$label : $value')),
+      ),
+    ),
+  );
-    return Column(
+    return Column(
       mainAxisAlignment: MainAxisAlignment.start,
       children: [
-        ... four blocks ...
+        _tokenTile(const Color.fromARGB(255, 251, 251, 99), 'Planter Tokens', _userProfileData!.planterTokens),
+        _tokenTile(const Color.fromARGB(255, 28, 211, 129), 'Care Tokens', _userProfileData!.careTokens),
+        _tokenTile(const Color.fromARGB(255, 251, 251, 99), 'Verifier Tokens', _userProfileData!.verifierTokens),
+        _tokenTile(const Color.fromARGB(255, 28, 211, 129), 'Legacy Tokens', _userProfileData!.legacyTokens),
       ],
     );
lib/pages/register_user_page.dart (1)

316-319: Heavy, fully opaque colored shadows look off

Use a translucent shadow for depth without tinting the surface green.

-                BoxShadow(
-                  color: const Color(0xFF1CD381),
+                BoxShadow(
+                  color: const Color(0xFF1CD381).withOpacity(0.08),
                   blurRadius: 8,
                   offset: const Offset(0, 2),
                 ),

Also applies to: 478-481

lib/components/universal_navbar.dart (1)

250-254: Overly dark shadows on light surfaces

Solid black shadows appear harsh; use a translucent black for a softer elevation.

-          BoxShadow(
-            color: Colors.black,
+          BoxShadow(
+            color: Colors.black26,
             blurRadius: 4,
             offset: const Offset(0, 2),
           ),

Also applies to: 375-379

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

206-211: shadowColor is unused with elevation: 0.

Either increase elevation or drop shadowColor to avoid confusion.

                       elevation: 0,
                       shape: RoundedRectangleBorder(
                         borderRadius: BorderRadius.circular(16),
                       ),
-                      shadowColor: const Color(0xFF1CD381),
lib/widgets/map_widgets/static_map_display_widget.dart (2)

219-225: Improve coordinate banner contrast.

Black text on dark green is low contrast; switch to white for readability.

-                        style: const TextStyle(
-                          fontSize: 10,
-                          color: Colors.black,
-                          fontFamily: 'monospace',
-                        ),
+                        style: const TextStyle(
+                          fontSize: 10,
+                          color: Colors.white,
+                          fontFamily: 'monospace',
+                        ),

139-144: Soften heavy drop shadows.

Solid black shadows look harsh; use translucency for a subtler, consistent material feel.

-                            BoxShadow(
-                              color: Colors.black,
+                            BoxShadow(
+                              color: Colors.black26,
                               blurRadius: 6,
                               offset: const Offset(0, 2),
                             ),
-                BoxShadow(
-                  color: Colors.black,
+                BoxShadow(
+                  color: Colors.black26,
                   blurRadius: 4,
                   offset: const Offset(0, 2),
                 ),

Also applies to: 243-246

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

105-108: Align initialZoom with minZoom.

initialZoom: 1.0 is below minZoom: 3.0; set them consistently to avoid immediate clamping/jank.

-            initialZoom: 1.0,
+            initialZoom: 3.0,
             minZoom: 3.0,
lib/models/tree_details.dart (1)

69-88: Don’t swallow errors silently.

Log the exception to aid debugging.

-    } catch (e) {
+    } catch (e, st) {
+      logger.e('Tree.fromContractData failed: $e', stackTrace: st);
       return Tree(
lib/widgets/nft_display_utils/tree_nft_view_details_with_map.dart (3)

37-41: Reduce overly strong shadows.

Pure black/green shadows read heavy; use translucent variants to match other components.

-                BoxShadow(
-                  color: Colors.black,
+                BoxShadow(
+                  color: Colors.black26,
                   blurRadius: 8,
                   offset: const Offset(0, 2),
                 ),
-                BoxShadow(
-                  color: Colors.green,
+                BoxShadow(
+                  color: Colors.green.withOpacity(0.25),
                   blurRadius: 8,
                   offset: const Offset(0, 2),
                 ),

Also applies to: 67-71


115-121: Add loading/error handling for images.

Network images should provide feedback and avoid layout jumps.

-                            child: Image.network(
-                              provider.getInitialPhotos()[index],
-                              height: 100,
-                              width: double.infinity,
-                              fit: BoxFit.cover,
-                            ),
+                            child: Image.network(
+                              provider.getInitialPhotos()[index],
+                              height: 100,
+                              width: double.infinity,
+                              fit: BoxFit.cover,
+                              loadingBuilder: (ctx, child, progress) =>
+                                  progress == null
+                                      ? child
+                                      : const Center(child: CircularProgressIndicator(strokeWidth: 2)),
+                              errorBuilder: (ctx, _, __) => Container(
+                                color: Colors.grey.shade200,
+                                alignment: Alignment.center,
+                                child: const Icon(Icons.broken_image, color: Colors.grey),
+                              ),
+                            ),

45-51: Redundant .toDouble() calls.

If getLatitude()/getLongitude() already return double, drop .toDouble() to avoid noise.

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

1-1: Drop unnecessary lint suppression.

The file name already follows snake_case. Remove the file_names ignore.

-// ignore: file_names
lib/pages/mint_nft/mint_nft_coordinates.dart (1)

621-657: Remove unused preview (and related import) instead of suppressing lints.

_buildPreviewSection is unused and guarded by // ignore: unused_element. Delete it; if you still want it, gate with kDebugMode and render it. If deleted, the NewNFTWidget import becomes unnecessary too.

- import 'package:tree_planting_protocol/widgets/nft_display_utils/tree_nft_view_widget.dart';
-  // ignore: unused_element
-  Widget _buildPreviewSection() {
-    return Column(
-      crossAxisAlignment: CrossAxisAlignment.start,
-      children: [
-        Padding(
-          padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 16),
-          child: Row(
-            children: [
-              Container(
-                padding: const EdgeInsets.all(8),
-                decoration: BoxDecoration(
-                  color: const Color(0xFFFAEB96),
-                  borderRadius: BorderRadius.circular(10),
-                ),
-                child: Icon(
-                  Icons.preview,
-                  color: const Color(0xFF1CD381),
-                  size: 20,
-                ),
-              ),
-              const SizedBox(width: 12),
-              const Text(
-                'Live Preview',
-                style: TextStyle(
-                  fontSize: 18,
-                  fontWeight: FontWeight.bold,
-                  color: Color(0xFF1CD381),
-                ),
-              ),
-            ],
-          ),
-        ),
-        const NewNFTWidget(),
-      ],
-    );
-  }

Also applies to: 9-9

lib/utils/constants/contract_abis/tree_nft_contract_abi.dart (2)

4-6: Consider moving the large ABI string to an asset JSON.

Keeping a >10KB ABI inline bloats the binary and diffs. Loading from assets (then caching) improves maintainability.


10-16: Remove hard-coded address notes from source; keep in .env.stencil or docs.

These comments tend to drift and risk confusion with env-driven config.

-// CareToken Address:          0xF9C45610FEA0382Ab5d28c7CaEe44F6aC26Fe956
-// PlanterToken Address:       0x18a3BB9E8b6a692b3B29Dcf49Ce58f4bf2CB0E93
-// VerifierToken Address:      0x52db3eEff09D1dBE30007fA06AE14aF9849D29ba
-// LegacyToken Address:        0xD5C0F25B883f018133d1Ce46fdb3365B660EF1db
-// TreeNft Address:            0xeD3D3a4f30ad25d29BD6cB46Bb705a120809DB23
-// OrganisationFactory:        0x75da54F30d347040e977860a9C3495b2C52b4F23
.env.stencil (2)

9-9: Use dotenv-compatible comments and trim extra blank lines.

Switch // to # and remove extra blank lines flagged by the linter.

-
-
-//CONTRACT ADDRESSES 
+# CONTRACT ADDRESSES
-

Also applies to: 8-8, 11-11


5-6: Remove unused CONTRACT_ADDRESS and reconcile factory vars

  • CONTRACT_ADDRESS isn’t referenced in any code (only per-contract keys like TREE_NFT_CONTRACT_ADDRESS are used); remove it from .env.stencil.
  • ORGANISATION_FACTORY_CONTRACT_ADDRESS is declared but never consumed—either drop it or implement its usage, and ensure its spelling exactly matches the key referenced in code.
lib/widgets/nft_display_utils/user_nfts_widget.dart (1)

439-445: Bottom detection can miss due to float equality; add threshold.

Use a small threshold and >= to trigger reliably.

-          if (!_isLoading &&
-              _hasMore &&
-              scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent) {
+          const threshold = 100.0;
+          if (!_isLoading &&
+              _hasMore &&
+              scrollInfo.metrics.pixels >=
+                  scrollInfo.metrics.maxScrollExtent - threshold) {
lib/utils/services/contract_read_services.dart (2)

191-253: Parallelize independent reads and validate pagination.

getTreeNFTInfo runs 3 sequential reads and lacks offset/limit validation. Parallelizing reduces latency; validating inputs guards RPC errors.

   static Future<ContractReadResult> getTreeNFTInfo({
@@
-    try {
+    try {
       if (!walletProvider.isConnected) {
@@
-      final List<dynamic> args = [BigInt.from(id)];
+      if (id < 0 || offset < 0 || limit <= 0 || limit > 100) {
+        return ContractReadResult.error(
+          errorMessage:
+              'Invalid parameters. id>=0, offset>=0, 1<=limit<=100',
+        );
+      }
+      final List<dynamic> args = [BigInt.from(id)];
@@
-      final treeDetailsResult = await walletProvider.readContract(
-        contractAddress: treeNFtContractAddress,
-        functionName: 'getTreeDetailsbyID',
-        params: args,
-        abi: treeNftContractABI,
-      );
+      final detailsF = walletProvider.readContract(
+        contractAddress: treeNFtContractAddress,
+        functionName: 'getTreeDetailsbyID',
+        params: args,
+        abi: treeNftContractABI,
+      );
@@
-      final treeVerifiersResult = await walletProvider.readContract(
-          contractAddress: treeNFtContractAddress,
-          functionName: 'getTreeNftVerifiersPaginated',
-          params: [BigInt.from(id), BigInt.from(offset), BigInt.from(limit)],
-          abi: treeNftContractABI);
+      final verifiersF = walletProvider.readContract(
+        contractAddress: treeNFtContractAddress,
+        functionName: 'getTreeNftVerifiersPaginated',
+        params: [BigInt.from(id), BigInt.from(offset), BigInt.from(limit)],
+        abi: treeNftContractABI,
+      );
@@
-      final ownerResult = await walletProvider.readContract(
-        contractAddress: treeNFtContractAddress,
-        functionName: 'ownerOf',
-        params: [BigInt.from(id)],
-        abi: treeNftContractABI,
-      );
+      final ownerF = walletProvider.readContract(
+        contractAddress: treeNFtContractAddress,
+        functionName: 'ownerOf',
+        params: [BigInt.from(id)],
+        abi: treeNftContractABI,
+      );
+
+      final results = await Future.wait([detailsF, verifiersF, ownerF]);
+      final treeDetailsResult = results[0];
+      final treeVerifiersResult = results[1];
+      final ownerResult = results[2];

85-94: Result shape handling could be stricter.

If the contract ever returns tuples vs arrays, result[0] may itself be a tuple. Consider normalizing to List<dynamic> explicitly.

lib/utils/services/contract_write_functions.dart (3)

64-66: Avoid truncation when encoding coordinates.

Use .round() before BigInt.from to reduce bias from flooring.

-      final lat = BigInt.from((latitude + 90.0) * 1e6);
-      final lng = BigInt.from((longitude + 180.0) * 1e6);
+      final lat = BigInt.from(((latitude + 90.0) * 1e6).round());
+      final lng = BigInt.from(((longitude + 180.0) * 1e6).round());

143-171: Lightweight input validation for verify.

Validate treeId > 0 and non-empty description to fail fast client-side.

   static Future<ContractWriteResult> verifyTree(
@@
-    try {
+    try {
+      if (treeId <= 0) {
+        return ContractWriteResult.error(
+          errorMessage: 'Invalid treeId',
+        );
+      }
+      if (description.trim().isEmpty) {
+        return ContractWriteResult.error(
+          errorMessage: 'Description is required',
+        );
+      }

179-217: Validate address format before EthereumAddress.fromHex.

Provide a clearer error if the address is malformed.

   static Future<ContractWriteResult> removeVerification(
@@
-      final List<dynamic> args = [
+      if (!address.startsWith('0x') || address.length != 42) {
+        return ContractWriteResult.error(
+          errorMessage: 'Invalid verifier address format',
+        );
+      }
+      final List<dynamic> args = [
         BigInt.from(treeId),
         EthereumAddress.fromHex(address)
       ];
lib/widgets/nft_display_utils/tree_nft_details_verifiers_widget.dart (1)

292-336: treeDetails! force unwrap in removal path.

_removeVerifier uses treeDetails!.id. While currently guarded by UI flow, keep it defensive.

-      treeId: treeDetails!.id,
+      treeId: (treeDetails?.id ?? -1),

And early-return with error if treeDetails == null || treeDetails.id < 0.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6c1b0e9 and 8aaab93.

📒 Files selected for processing (28)
  • .env.stencil (1 hunks)
  • .github/workflows/flutter.yaml (1 hunks)
  • lib/components/universal_navbar.dart (7 hunks)
  • lib/components/wallet_connect_dialog.dart (2 hunks)
  • lib/models/tree_details.dart (1 hunks)
  • lib/models/wallet_chain_option.dart (3 hunks)
  • lib/pages/home_page.dart (0 hunks)
  • lib/pages/mint_nft/mint_nft_coordinates.dart (2 hunks)
  • lib/pages/mint_nft/mint_nft_details.dart (8 hunks)
  • lib/pages/mint_nft/mint_nft_images.dart (2 hunks)
  • lib/pages/register_user_page.dart (4 hunks)
  • lib/pages/tree_details_page.dart (1 hunks)
  • lib/providers/mint_nft_provider.dart (0 hunks)
  • lib/providers/wallet_provider.dart (1 hunks)
  • lib/utils/constants/contractDetails.dart (0 hunks)
  • lib/utils/constants/contract_abis/tree_nft_contract_abi.dart (1 hunks)
  • lib/utils/services/contract_read_services.dart (5 hunks)
  • lib/utils/services/contract_write_functions.dart (4 hunks)
  • lib/utils/services/ipfs_services.dart (2 hunks)
  • lib/utils/services/switch_chain_utils.dart (1 hunks)
  • lib/widgets/map_widgets/flutter_map_widget.dart (15 hunks)
  • lib/widgets/map_widgets/static_map_display_widget.dart (1 hunks)
  • lib/widgets/nft_display_utils/tree_nft_details_verifiers_widget.dart (1 hunks)
  • lib/widgets/nft_display_utils/tree_nft_view_details_with_map.dart (2 hunks)
  • lib/widgets/nft_display_utils/tree_nft_view_widget.dart (1 hunks)
  • lib/widgets/nft_display_utils/user_nfts_widget.dart (4 hunks)
  • lib/widgets/profile_widgets/profile_section_widget.dart (3 hunks)
  • lib/widgets/wallet_not_connected_widget.dart (1 hunks)
💤 Files with no reviewable changes (3)
  • lib/pages/home_page.dart
  • lib/utils/constants/contractDetails.dart
  • lib/providers/mint_nft_provider.dart
🧰 Additional context used
🪛 actionlint (1.7.7)
.github/workflows/flutter.yaml

15-15: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

🪛 YAMLlint (1.37.1)
.github/workflows/flutter.yaml

[error] 17-17: trailing spaces

(trailing-spaces)


[error] 18-18: trailing spaces

(trailing-spaces)


[error] 23-23: trailing spaces

(trailing-spaces)


[warning] 35-35: too many spaces after colon

(colons)

🪛 dotenv-linter (3.3.0)
.env.stencil

[warning] 5-5: [UnorderedKey] The CONTRACT_ADDRESS key should go before the WALLETCONNECT_PROJECT_ID key

(UnorderedKey)


[warning] 6-6: [UnorderedKey] The APPLICATION_ID key should go before the CONTRACT_ADDRESS key

(UnorderedKey)


[warning] 8-8: [ExtraBlankLine] Extra blank line detected

(ExtraBlankLine)


[warning] 9-9: [IncorrectDelimiter] The //CONTRACT ADDRESSES key has incorrect delimiter

(IncorrectDelimiter)


[warning] 9-9: [KeyWithoutValue] The //CONTRACT ADDRESSES key should be with a value or have an equal sign

(KeyWithoutValue)


[warning] 9-9: [LeadingCharacter] Invalid leading character detected

(LeadingCharacter)


[warning] 9-9: [TrailingWhitespace] Trailing whitespace detected

(TrailingWhitespace)


[warning] 11-11: [ExtraBlankLine] Extra blank line detected

(ExtraBlankLine)


[warning] 13-13: [UnorderedKey] The ORGANISATION_FACTORY_CONTRACT_ADDRESS key should go before the TREE_NFT_CONTRACT_ADDRESS key

(UnorderedKey)


[warning] 14-14: [UnorderedKey] The CARE_TOKEN_CONTRACT_ADDRESS key should go before the ORGANISATION_FACTORY_CONTRACT_ADDRESS key

(UnorderedKey)


[warning] 16-16: [UnorderedKey] The LEGACY_TOKEN_CONTRACT_ADDRESS key should go before the ORGANISATION_FACTORY_CONTRACT_ADDRESS key

(UnorderedKey)


[warning] 17-17: [UnorderedKey] The PLANTER_TOKEN_CONTRACT_ADDRESS key should go before the TREE_NFT_CONTRACT_ADDRESS key

(UnorderedKey)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (8)
lib/utils/services/switch_chain_utils.dart (1)

25-58: LGTM on removing unnecessary toList().
Spreading an Iterable is fine here and slightly lighter on allocs.

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

37-47: Drop out-of-range index warning; actualData[9] is valid
getUserProfile returns a 10-element tuple (indices 0–9) as defined in the ABI, with careTokens at index 9 (etherscan.io). No change needed.

Likely an incorrect or invalid review comment.

lib/models/tree_details.dart (1)

5-6: Confirm coordinate scaling and types.

Model stores latitude/longitude as int; UI map widgets expect double. If contract returns scaled ints (e.g., E6), either:

  • store as double in the model, or
  • rename to latitudeE6/longitudeE6 and provide helpers to convert.
lib/pages/mint_nft/mint_nft_coordinates.dart (1)

9-9: Import path fix looks good.

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

107-111: Constructor cleanup looks good.

Switching to super.key is consistent and idiomatic.


341-378: Potential unbounded height with Expanded inside a top-level Column.

If this widget is placed in an unconstrained parent (e.g., another Column or SingleChildScrollView), this can throw a RenderFlex error.

Please verify the parent provides a bounded height. If not, wrap this widget in Expanded at the call site, or replace this Expanded with a sized container (e.g., SizedBox(height: ...)).

lib/pages/tree_details_page.dart (2)

64-71: Verifier eligibility logic likely incomplete.

Owners might be allowed/forbidden to verify their own trees depending on business rules. Today only “already verified” is checked.

Confirm the rule. If owners should not verify, add canVerify &= owner.toLowerCase() != loggedInUser?.toLowerCase();


98-103: Map coordinates correctly decoded.

Using (value/1e6) - offset matches mint encoding. Good.


steps:
- name: Checkout repository
uses: actions/checkout@v3
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Modernize action version and fix minor YAML lint issues.

  • Use actions/checkout@v4.
  • Trim trailing spaces (lines 17, 18, 23).
  • Single space after run: (line 35).
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
-      - name: Setup environment file from template 
-        run: cp .env.stencil .env 
+      - name: Setup environment file from template
+        run: cp .env.stencil .env
-          channel: 'stable' 
+          channel: 'stable'
-        run:  flutter build apk --release --no-tree-shake-icons
+        run: flutter build apk --release --no-tree-shake-icons

Also applies to: 17-18, 23-23, 35-35

🧰 Tools
🪛 actionlint (1.7.7)

15-15: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

🤖 Prompt for AI Agents
In .github/workflows/flutter.yaml around lines 15, 17-18, 23 and 35 update the
actions/checkout usage to actions/checkout@v4, remove any trailing spaces on
lines 17, 18 and 23, and ensure there is exactly one space after the run: key on
line 35; keep YAML indentation and quoting unchanged while making these minimal
edits.

run: dart format --output=none --set-exit-if-changed .

- name: Flutter build (apk)
run: flutter build apk --release --no-tree-shake-icons | grep -v "deprecated"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Build failures may be masked by piping to grep.

flutter build ... | grep -v "deprecated" returns grep's exit code; without pipefail, a failing build can pass. Remove the pipe (or set set -o pipefail).

-      - name: Flutter build (apk)
-        run:  flutter build apk --release --no-tree-shake-icons | grep -v "deprecated"
+      - name: Flutter build (apk)
+        run: flutter build apk --release --no-tree-shake-icons
📝 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
run: flutter build apk --release --no-tree-shake-icons | grep -v "deprecated"
- name: Flutter build (apk)
run: flutter build apk --release --no-tree-shake-icons
🧰 Tools
🪛 YAMLlint (1.37.1)

[warning] 35-35: too many spaces after colon

(colons)

🤖 Prompt for AI Agents
In .github/workflows/flutter.yaml around line 35, the build command pipes
flutter build to grep which can mask failures because pipe exit status isn't
checked; either remove the pipe entirely and let the action surface build
errors, or ensure the shell runs with pipefail (use bash and set -o pipefail
before the command) so the workflow fails when flutter build fails while still
filtering out deprecated lines.

Comment on lines +46 to +69
try {
return Tree(
id: _toInt(userData[0]),
latitude: _toInt(userData[1]),
longitude: _toInt(userData[2]),
planting: _toInt(userData[3]),
death: _toInt(userData[4]),
species: userData[5]?.toString() ?? '',
imageUri: userData[6]?.toString() ?? '',
qrIpfsHash: userData[7]?.toString() ?? '',
metadata: userData[8]?.toString() ?? '',
photos: userData[9] is List
? List<String>.from(userData[9].map((p) => p.toString()))
: [],
geoHash: userData[10]?.toString() ?? '',
ancestors: userData[11] is List
? List<String>.from(userData[11].map((a) => a.toString()))
: [],
lastCareTimestamp: _toInt(userData[12]),
careCount: _toInt(userData[13]),
verifiers: List<String>.from(verifiers.map((a) => a.toString())),
owner: owner,
);
} catch (e) {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Validate contract payload shape before indexing.

Direct indexing assumes ≥14 items; add a length guard to avoid RangeError and return a safe fallback with logging.

     try {
+      if (userData.length < 14) {
+        logger.e('Tree.fromContractData: insufficient userData length=${userData.length}');
+        return Tree(
+          id: 0,
+          latitude: 0,
+          longitude: 0,
+          planting: 0,
+          death: 0,
+          species: 'Unknown',
+          imageUri: '',
+          qrIpfsHash: '',
+          metadata: '',
+          photos: const [],
+          geoHash: '',
+          ancestors: const [],
+          lastCareTimestamp: 0,
+          careCount: 0,
+          verifiers: const [],
+          owner: '',
+        );
+      }
       return Tree(
📝 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 {
return Tree(
id: _toInt(userData[0]),
latitude: _toInt(userData[1]),
longitude: _toInt(userData[2]),
planting: _toInt(userData[3]),
death: _toInt(userData[4]),
species: userData[5]?.toString() ?? '',
imageUri: userData[6]?.toString() ?? '',
qrIpfsHash: userData[7]?.toString() ?? '',
metadata: userData[8]?.toString() ?? '',
photos: userData[9] is List
? List<String>.from(userData[9].map((p) => p.toString()))
: [],
geoHash: userData[10]?.toString() ?? '',
ancestors: userData[11] is List
? List<String>.from(userData[11].map((a) => a.toString()))
: [],
lastCareTimestamp: _toInt(userData[12]),
careCount: _toInt(userData[13]),
verifiers: List<String>.from(verifiers.map((a) => a.toString())),
owner: owner,
);
} catch (e) {
try {
if (userData.length < 14) {
logger.e('Tree.fromContractData: insufficient userData length=${userData.length}');
return Tree(
id: 0,
latitude: 0,
longitude: 0,
planting: 0,
death: 0,
species: 'Unknown',
imageUri: '',
qrIpfsHash: '',
metadata: '',
photos: const [],
geoHash: '',
ancestors: const [],
lastCareTimestamp: 0,
careCount: 0,
verifiers: const [],
owner: '',
);
}
return Tree(
id: _toInt(userData[0]),
latitude: _toInt(userData[1]),
longitude: _toInt(userData[2]),
planting: _toInt(userData[3]),
death: _toInt(userData[4]),
species: userData[5]?.toString() ?? '',
imageUri: userData[6]?.toString() ?? '',
qrIpfsHash: userData[7]?.toString() ?? '',
metadata: userData[8]?.toString() ?? '',
photos: userData[9] is List
? List<String>.from(userData[9].map((p) => p.toString()))
: [],
geoHash: userData[10]?.toString() ?? '',
ancestors: userData[11] is List
? List<String>.from(userData[11].map((a) => a.toString()))
: [],
lastCareTimestamp: _toInt(userData[12]),
careCount: _toInt(userData[13]),
verifiers: List<String>.from(verifiers.map((a) => a.toString())),
owner: owner,
);
} catch (e) {

Comment on lines +46 to +48
// ignore: use_build_context_synchronously
final provider = Provider.of<MintNftProvider>(context, listen: 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

Avoid using BuildContext after await; capture provider before the async call

Move the provider lookup above the first await and drop the lint suppression.

Apply:

   Future<void> _pickAndUploadImages() async {
     try {
-      final List<XFile> images = await _picker.pickMultiImage();
+      final provider = Provider.of<MintNftProvider>(context, listen: false);
+      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);
📝 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
// ignore: use_build_context_synchronously
final provider = Provider.of<MintNftProvider>(context, listen: false);
Future<void> _pickAndUploadImages() async {
try {
// Capture the provider before any await to avoid use_build_context_synchronously issues
final provider = Provider.of<MintNftProvider>(context, listen: false);
final List<XFile> images = await _picker.pickMultiImage();
if (images.isEmpty) return;
logger.d('Selected ${images.length} images for upload');
// ...rest of the upload logic, using `provider` as needed...
} catch (e) {
// error handling...
}
}
🤖 Prompt for AI Agents
In lib/pages/mint_nft/mint_nft_images.dart around lines 46 to 48, the code
obtains Provider.of<MintNftProvider>(context, listen: false) after an await and
suppresses the lint; move the provider lookup to before the first await so you
capture the provider synchronously, remove the "// ignore:
use_build_context_synchronously" suppression, and then use the captured provider
variable afterwards (avoid calling Provider.of with the context after any
await).

Comment on lines +47 to +77
Future<void> loadTreeDetails() async {
final walletProvider = Provider.of<WalletProvider>(context, listen: false);
loggedInUser = walletProvider.currentAddress.toString();
setState(() {
_isLoading = true;
});
final result = await ContractReadFunctions.getTreeNFTInfo(
walletProvider: walletProvider,
id: _toInt(widget.treeId),
offset: TREE_VERIFIERS_OFFSET,
limit: TREE_VERIFIERS_LIMIT);
if (result.success && result.data != null) {
final List<dynamic> treesData = result.data['details'] ?? [];
final List<dynamic> verifiersData = result.data['verifiers'] ?? [];
final String owner = result.data['owner'].toString();
treeDetails = Tree.fromContractData(treesData, verifiersData, owner);
logger.d("Verifiers data: $verifiersData");
canVerify = true;
for (var verifier in verifiersData) {
if (verifier[0].toString().toLowerCase() ==
loggedInUser?.toLowerCase()) {
canVerify = false;
break;
}
}
}
logger.d("Tree Details hot: ${treeDetails?.verifiers}");
setState(() {
_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

Null deref risk: treeDetails! used even on read failure.

If the contract read fails, _isLoading becomes false but treeDetails may remain null; _buildMapSection and others force-unwrap it.

Guard in build and/or set an error state. Example change in build:

-        body: _isLoading
-            ? const Center(child: CircularProgressIndicator())
-            : SingleChildScrollView(
+        body: _isLoading
+            ? const Center(child: CircularProgressIndicator())
+            : (treeDetails == null
+                ? Center(
+                    child: Padding(
+                      padding: const EdgeInsets.all(16),
+                      child: Text(
+                        'Failed to load tree details.',
+                        style: TextStyle(color: Colors.red.shade700),
+                      ),
+                    ),
+                  )
+                : SingleChildScrollView(
                   ...
-              ));
+              )));

Also consider surfacing result.errorMessage to the user.

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

🤖 Prompt for AI Agents
In lib/pages/tree_details_page.dart around lines 47 to 77, the contract read
failure path leaves treeDetails null while the UI later force-unwraps it,
risking a null dereference; update loadTreeDetails to handle errors by checking
result.success and, on failure, set an explicit error state (e.g., an
_errorMessage) and ensure treeDetails remains null or gets a safe default, then
call setState to update _isLoading and the error flag; additionally, surface
result.errorMessage to the user (via a Snackbar or showing the error text in
build) and modify build to guard all uses of treeDetails (return a loading/error
placeholder when null) instead of force-unwrapping.

Comment on lines +338 to +456
Widget treeVerifiersSection(String? loggedInUser, Tree? treeDetails,
Function loadTreeDetails, BuildContext context) {
logger.d("=== treeVerifiersSection ===");
logger.d("treeDetails: $treeDetails");
logger.d("treeDetails?.verifiers: ${treeDetails?.verifiers}");
logger.d("verifiers length: ${treeDetails?.verifiers.length}");

final themeColors = _getThemeColors(context);

if (treeDetails?.verifiers == null || treeDetails!.verifiers.isEmpty) {
logger.d("No verifiers found, showing empty state");
return Container(
width: double.infinity,
margin: const EdgeInsets.symmetric(vertical: 16.0),
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(
color: Colors.grey.shade50,
borderRadius: BorderRadius.circular(12.0),
border: Border.all(color: Colors.grey.shade200),
),
child: Column(
children: [
Icon(
Icons.group_off,
color: Colors.grey.shade400,
size: 32,
),
const SizedBox(height: 8),
Text(
"No Verifiers Yet",
style: TextStyle(
color: Colors.grey.shade600,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 4),
Text(
"This tree hasn't been verified by anyone",
style: TextStyle(
color: Colors.grey.shade500,
fontSize: 14,
),
),
],
),
);
}

logger.d(
"Rendering verifiers section with ${treeDetails.verifiers.length} verifiers");
final isOwner = treeDetails.owner == loggedInUser;

return Container(
width: double.infinity,
margin: const EdgeInsets.symmetric(vertical: 16.0),
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(
color: themeColors['primaryLight'],
borderRadius: BorderRadius.circular(12.0),
border: Border.all(color: themeColors['primaryBorder']!),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.verified_user,
color: themeColors['primary'],
size: 24,
),
const SizedBox(width: 8),
Text(
"Tree Verifiers",
style: TextStyle(
color: themeColors['primary'],
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const Spacer(),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: themeColors['primary']!,
borderRadius: BorderRadius.circular(12),
),
child: Text(
"${treeDetails.verifiers.length}",
style: TextStyle(
color: themeColors['primary'],
fontWeight: FontWeight.bold,
),
),
),
],
),
const SizedBox(height: 12),
Text(
isOwner
? "Tap any verifier to view details • Tap ✕ to remove (owner only)"
: "Tap any verifier to view verification details",
style: TextStyle(
color: themeColors['primary']!,
fontSize: 12,
),
),
const SizedBox(height: 16),
...treeDetails.verifiers.asMap().entries.map((entry) {
final index = entry.key;
final verifier = entry.value;
return _buildVerifierCard(
verifier, index, isOwner, loadTreeDetails, treeDetails, context);
}),
],
),
);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Model duplication; centralize Tree and Verifier.

This file defines full models; another Tree exists in user_nfts_widget.dart, and PR mentions lib/models/tree_details.dart. Divergence risk is high.

Move both models to lib/models/ and import here and in other widgets. Keep a single fromContractData path.

Comment on lines +421 to +433
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: themeColors['primary']!,
borderRadius: BorderRadius.circular(12),
),
child: Text(
"${treeDetails.verifiers.length}",
style: TextStyle(
color: themeColors['primary'],
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

Unreadable count chip: text color matches background.

Chip background and text both use themeColors['primary'], making the text invisible.

-              child: Text(
-                "${treeDetails.verifiers.length}",
-                style: TextStyle(
-                  color: themeColors['primary'],
-                  fontWeight: FontWeight.bold,
-                ),
-              ),
+              child: Text(
+                "${treeDetails.verifiers.length}",
+                style: const TextStyle(
+                  color: Colors.white,
+                  fontWeight: FontWeight.bold,
+                ),
+              ),
📝 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
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: themeColors['primary']!,
borderRadius: BorderRadius.circular(12),
),
child: Text(
"${treeDetails.verifiers.length}",
style: TextStyle(
color: themeColors['primary'],
fontWeight: FontWeight.bold,
),
),
),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: themeColors['primary']!,
borderRadius: BorderRadius.circular(12),
),
child: Text(
"${treeDetails.verifiers.length}",
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
🤖 Prompt for AI Agents
In lib/widgets/nft_display_utils/tree_nft_details_verifiers_widget.dart around
lines 421 to 433, the count chip sets both background and text color to
themeColors['primary'], making the text unreadable; change the Text style color
to a contrasting value (for example themeColors['onPrimary'] or Colors.white) or
compute a readable foreground from the background, and ensure the chosen key
exists (fall back to a safe default) so the count remains visible against the
primary-colored background.

Comment on lines +549 to +575
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: themeColors['primary']!,
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.photo_library,
size: 10,
color: themeColors['primary'],
),
const SizedBox(width: 2),
Text(
"${verifier.proofHashes.length}",
style: TextStyle(
fontSize: 10,
color: themeColors['primary'],
fontWeight: FontWeight.w600,
),
),
],
),
),
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Proof badge text/icon also match background.

Same issue: foreground equals background.

-                                decoration: BoxDecoration(
-                                  color: themeColors['primary']!,
+                                decoration: BoxDecoration(
+                                  color: themeColors['primary']!,
                                   borderRadius: BorderRadius.circular(8),
                                 ),
@@
-                                    Icon(
+                                    Icon(
                                       Icons.photo_library,
-                                      size: 10,
-                                      color: themeColors['primary']!,
+                                      size: 10,
+                                      color: Colors.white,
                                     ),
@@
-                                    Text(
+                                    Text(
                                       "${verifier.proofHashes.length}",
-                                      style: TextStyle(
-                                        fontSize: 10,
-                                        color: themeColors['primary'],
-                                        fontWeight: FontWeight.w600,
-                                      ),
+                                      style: const TextStyle(
+                                        fontSize: 10,
+                                        color: Colors.white,
+                                        fontWeight: FontWeight.w600,
+                                      ),

Also applies to: 560-572

🤖 Prompt for AI Agents
In lib/widgets/nft_display_utils/tree_nft_details_verifiers_widget.dart around
lines 549-575 (and similarly 560-572), the badge's icon and text are using the
same color as the badge background so they aren't visible; update the Icon and
Text style to use a contrasting foreground color (for example
themeColors['onPrimary'] or Colors.white) instead of themeColors['primary'], and
apply the same fix to the other occurrence so both icon and number are readable
against the badge background.

Comment on lines +121 to 125
// ignore: unused_field
int _currentPage = 0;
// ignore: unused_field
final int _itemsPerPage = 10;
int _totalCount = 0;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Unused pagination fields; wire them in or remove.

_itemsPerPage is never used (suppressed via ignore). Either use it for the contract call or drop both the field and ignores.

Apply this diff to prepare for real pagination:

-  // ignore: unused_field
-  int _currentPage = 0;
-  // ignore: unused_field
-  final int _itemsPerPage = 10;
+  int _currentPage = 0;
+  final int _itemsPerPage = 10;
📝 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
// ignore: unused_field
int _currentPage = 0;
// ignore: unused_field
final int _itemsPerPage = 10;
int _totalCount = 0;
int _currentPage = 0;
final int _itemsPerPage = 10;
int _totalCount = 0;
🤖 Prompt for AI Agents
In lib/widgets/nft_display_utils/user_nfts_widget.dart around lines 121-125, the
pagination fields are declared but _itemsPerPage is ignored and unused; remove
the ignore comments and wire _itemsPerPage and _currentPage into the NFT fetch
logic so the contract call/request uses limit=_itemsPerPage and
offset=_currentPage * _itemsPerPage (or equivalent), update _totalCount based on
the returned total from the call, and remove any unused-field suppressions;
alternatively if pagination is not yet supported, delete both _currentPage and
_itemsPerPage and their ignore comments and adjust code to not reference them.

Comment on lines 26 to 31
onPressed: () {
final walletProvider =
Provider.of<WalletProvider>(context, listen: false);
walletProvider.connectWallet();
},
child: const Text('Connect Wallet'),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Button doesn’t await or open a wallet; users won’t see any prompt.
connectWallet() returns a WC URI that must be deep-linked into a chosen wallet. Hook it up and handle errors.

-        ElevatedButton(
-          onPressed: () {
-            final walletProvider =
-                Provider.of<WalletProvider>(context, listen: false);
-            walletProvider.connectWallet();
-          },
+        ElevatedButton(
+          onPressed: () async {
+            final walletProvider =
+                Provider.of<WalletProvider>(context, listen: false);
+            final uri = await walletProvider.connectWallet();
+            if (uri != null && walletProvider.walletOptions.isNotEmpty) {
+              // Simple default: open first known wallet; or present a chooser UI.
+              await walletProvider.openWallet(walletProvider.walletOptions.first, uri);
+            } else {
+              if (context.mounted) {
+                ScaffoldMessenger.of(context).showSnackBar(
+                  const SnackBar(content: Text('Unable to start wallet connection')),
+                );
+              }
+            }
+          },
📝 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
onPressed: () {
final walletProvider =
Provider.of<WalletProvider>(context, listen: false);
walletProvider.connectWallet();
},
child: const Text('Connect Wallet'),
ElevatedButton(
onPressed: () async {
final walletProvider =
Provider.of<WalletProvider>(context, listen: false);
final uri = await walletProvider.connectWallet();
if (uri != null && walletProvider.walletOptions.isNotEmpty) {
// Simple default: open first known wallet; or present a chooser UI.
await walletProvider.openWallet(
walletProvider.walletOptions.first,
uri,
);
} else {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Unable to start wallet connection'),
),
);
}
}
},
child: const Text('Connect Wallet'),
),
🤖 Prompt for AI Agents
In lib/widgets/wallet_not_connected_widget.dart around lines 26 to 31, the
button callback calls walletProvider.connectWallet() but doesn't await the
returned WalletConnect (WC) URI or deep-link it into a wallet app, so no wallet
prompt appears; update the onPressed to await connectWallet(), capture the
returned WC URI, then open it with the platform URL launcher (or pass it to the
platform-specific deep-link handler) to start the wallet app, and wrap this in
try/catch to handle and log errors and show user feedback (SnackBar/dialog) and
provide a fallback like copying the URI to clipboard if launching fails.

@Zahnentferner Zahnentferner merged commit 8cbb0a5 into main Sep 4, 2025
2 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Oct 26, 2025
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.

4 participants