Skip to content

Conversation

@parthraninga
Copy link

@parthraninga parthraninga commented Dec 13, 2025

Pinata IPFS : ~1 GB free storage , however Web3.storage : ~5 GB free storage . making it better alternative.
Closes #25
Work along #31
This pull request introduces a new "Nearby Trees Map" feature, allowing users to view and interact with trees located near a specific geographic area. The update includes backend logic for geospatial clustering and geohash-based proximity search, UI enhancements for map access, and minor bug fixes. Below are the most important changes grouped by theme:

Nearby Trees Map Feature & UI Integration

  • Added a new page and route for displaying a map of nearby trees (NearbyTreesMapPage), including navigation from the all-trees page and a new "Map" button for quick access. [1] [2] [3] [4]
  • Updated the tech stack in the README.md to reflect the use of Web3.Storage/Storacha for decentralized storage.
  • Changed environment variable from Pinata to Web3.Storage for IPFS token management. (.env.stencil)

Geospatial Search and Clustering Logic

  • Implemented GeohashUtils in lib/utils/geohash_utils.dart for geohash encoding/decoding, neighbor lookups, coverage calculation, and distance computation, enabling efficient spatial queries.
  • Added TreeClusteringService in lib/services/tree_clustering_service.dart to group trees into clusters based on map zoom level and proximity, improving map visualization performance and clarity.
  • Added a new contract read function getNearbyTrees that uses geohash-based filtering and distance checks to efficiently find trees near a given location. [1] [2]

Bug Fixes & UI Improvements

  • Fixed coordinate conversion bug in TreeDetailsPage to ensure correct longitude calculation and display. [1] [2]
  • Improved hint text color in the verification modal for better readability.

Summary by CodeRabbit

  • New Features

    • Added an interactive map displaying nearby planted trees with user location support, tree markers, and detailed tree information
    • Added a "Map" button to the trees interface for easy navigation to nearby trees
    • Enabled the "View on Map" button functionality with proper navigation
    • Migrated storage provider to Web3.Storage for decentralized IPFS support
  • Bug Fixes

    • Fixed coordinate conversion for accurate tree location display

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

@coderabbitai
Copy link

coderabbitai bot commented Dec 13, 2025

Walkthrough

The PR implements a geohash-based nearby trees map feature with tree clustering, migrates IPFS storage from Pinata to Web3.Storage using Bearer token authentication, adds contract-level spatial query functionality, fixes coordinate system calculations (longitude offset 180→90), and updates routing and UI widgets accordingly.

Changes

Cohort / File(s) Summary
Environment & Documentation
\.env.stencil, README.md
Replaced Pinata keys with WEB3_STORAGE_TOKEN; updated tech stack documentation from Pinata IPFS to Web3.Storage/Storacha.
Geospatial Utilities
lib/utils/geohash_utils.dart, lib/services/tree_clustering_service.dart
Introduced GeohashUtils for encoding/decoding geohashes, neighbor retrieval, distance calculation, and coverage geohashes; added TreeClusteringService to cluster trees by zoom level using geohash-based grouping.
Map Feature & Navigation
lib/main.dart, lib/pages/nearby_trees_map_page.dart, lib/pages/trees_page.dart
Added new GoRoute for "/nearby-trees" leading to NearbyTreesMapPage; implemented interactive map page with user location, tree markers, clustering, and tree detail modal; added Map button to trees page header.
Contract Spatial Queries
lib/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_read_services.dart
Added getNearbyTrees function for geohash-based spatial querying with distance filtering and sorting.
IPFS Provider Migration
lib/utils/services/ipfs_services.dart
Migrated from Pinata (apiKey/apiSecret) to Web3.Storage (Bearer token apiToken); updated upload endpoint and response handling to construct URLs via w3s.link gateway.
Coordinate System & Widget Updates
lib/pages/tree_details_page.dart, lib/widgets/nft_display_utils/user_nfts_widget.dart, lib/widgets/nft_display_utils/recent_trees_widget.dart, lib/widgets/profile_widgets/profile_section_widget.dart, lib/widgets/profile_widgets/user_profile_viewer_widget.dart
Fixed longitude offset calculation (180→90) in tree details and NFT card display; updated Map button navigation in recent trees widget; extended IPFS fallback gateway logic to support w3s.link URLs alongside pinata.cloud.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant App as NearbyTreesMapPage
    participant Loc as LocationService
    participant Contract as ContractReadFunctions
    participant Geo as GeohashUtils
    participant Cluster as TreeClusteringService
    participant Map as MapController

    User->>App: Open nearby trees map
    App->>App: Initialize state
    App->>Loc: Get user location
    activate Loc
    Loc-->>App: Return lat/lng or default
    deactivate Loc
    
    App->>Geo: getCoverageGeohashes(lat, lng, radius)
    activate Geo
    Geo-->>App: Return geohash list
    deactivate Geo
    
    App->>Contract: getNearbyTrees(center, radius, limit)
    activate Contract
    Contract->>Contract: Fetch trees via geohashes
    Contract->>Geo: calculateDistance for filtering
    Contract-->>App: Return nearby trees with distances
    deactivate Contract
    
    App->>Cluster: clusterTrees(trees, zoomLevel)
    activate Cluster
    Cluster-->>App: Return clustered/individual markers
    deactivate Cluster
    
    App->>Map: Set markers & center
    activate Map
    Map-->>App: Map rendered
    deactivate Map
    
    User->>App: Tap tree marker
    App->>App: Show tree details modal
    
    User->>App: Tap recenter or refresh
    App->>App: Reload data and redraw
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Areas requiring extra attention:

  • Coordinate system changes (180→90): Verify the longitude offset is correct across tree_details_page.dart, user_nfts_widget.dart, and nearby_trees_map_page.dart—inconsistent conversion could cause map misalignment.
  • Geohash-based spatial queries: Review the getNearbyTrees contract function for correct geohash coverage and distance filtering logic; test edge cases near coordinate boundaries.
  • Web3.Storage integration: Validate Bearer token handling in ipfs_services.dart and confirm w3s.link gateway URLs are accessible; check IPFS fallback logic in profile widgets.
  • Tree clustering algorithm: Ensure zoom-level-based clustering and geohash precision calculations are correct; test with varying tree densities.
  • LocationService usage: Confirm that fallback to default coordinates (Delhi) is intentional and that location permissions are handled gracefully.

Possibly related PRs

Suggested reviewers

  • ceilican
  • Zahnentferner

🐰 A map of trees now blooms on the screen,
Geohashes whisper where leaves have been,
Web3 storage holds photos so bright,
Coordinates dancing—180 to 90 in flight!
Nearby trees await, clustered just right. 🌳✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes out-of-scope additions: a Nearby Trees Map feature with geospatial clustering, coordinate conversion bug fixes, and UI improvements that exceed the issue #25 requirement of finding an alternative to IPFS. Consider separating the IPFS alternative implementation from the Nearby Trees Map feature and bug fixes into distinct PRs aligned with their respective issues/objectives.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change of switching to Web3.Storage with IPFS fallback, covering the primary objective of the PR.
Linked Issues check ✅ Passed The PR successfully implements Web3.Storage as an alternative to Pinata/IPFS with proper hash handling, content-addressable storage without cost restrictions, meeting all requirements from issue #25.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

🧹 Nitpick comments (9)
lib/pages/tree_details_page.dart (1)

911-911: Consider using theme-derived color instead of hardcoded value.

The hint color changed from getThemeColors(context)['textSecondary'] to Colors.grey.shade600. Hardcoding colors can cause:

  • Inconsistent theming across the app
  • Poor contrast in dark mode
  • Accessibility concerns

Apply this diff to use the theme color:

                        hintStyle: TextStyle(
-                          color: Colors.grey.shade600,
+                          color: getThemeColors(context)['textSecondary'],
                        ),
lib/utils/geohash_utils.dart (1)

195-222: Add error handling for invalid geohash in _getNeighbor.

The _getNeighbor method could throw index out of range exceptions if the geohash contains invalid characters that aren't found in the lookup tables.

Add a try-catch block or validation:

  static String _getNeighbor(String geohash, String direction) {
    if (geohash.isEmpty) return '';
+   
+   try {
      final lastChar = geohash[geohash.length - 1];
+     if (!_base32.contains(lastChar)) {
+       return geohash; // Return original if invalid
+     }
      final parent = geohash.substring(0, geohash.length - 1);
      final type = geohash.length % 2 == 0 ? 'even' : 'odd';
      
      // ... rest of method
+   } catch (e) {
+     return geohash; // Fallback to original on error
+   }
  }
lib/pages/trees_page.dart (1)

56-65: Inconsistent navigation pattern: use go_router for consistency.

The existing "Mint NFT" button uses context.push('/mint-nft') via go_router, but this new Map button uses Navigator.push. According to the AI summary, a route for /nearby-trees is registered in main.dart. Using Navigator.push bypasses go_router, which can break deep linking, browser history (on web), and consistent navigation behavior.

-              onPressed: () {
-                  Navigator.push(
-                    context,
-                    MaterialPageRoute(
-                      builder: (context) => const NearbyTreesMapPage(),
-                    ),
-                  );
-                },
+              onPressed: () {
+                  context.push('/nearby-trees');
+                },

If using go_router, the direct import of NearbyTreesMapPage on line 9 would also become unnecessary.

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

6-6: Unused import.

The mock_trees.dart import is not used in this file.

-import 'package:tree_planting_protocol/utils/mock_data/mock_trees.dart';

409-416: Computed coverageGeohashes are unused for filtering.

The geohash coverage is calculated and logged but not actually used to filter trees. The current implementation fetches all recent trees and filters purely by distance. While the TODO comment acknowledges this limitation, the coverageGeohashes computation is currently dead code.

Consider either:

  1. Removing the unused computation until contract-side filtering is implemented
  2. Using it for client-side pre-filtering before distance calculation (could improve performance for large datasets)
lib/utils/services/ipfs_services.dart (1)

23-30: Add error logging for failed uploads.

When the upload fails (non-200 status), the function returns null without logging the error. This makes debugging difficult in production.

   if (response.statusCode == 200) {
     var jsonResponse = json.decode(await response.stream.bytesToString());
     // Web3.Storage returns a CID in the response
     String cid = jsonResponse['cid'];
     return "https://w3s.link/ipfs/$cid";
   } else {
+    // Log the error for debugging
+    final errorBody = await response.stream.bytesToString();
+    // Consider using your logger: logger.e("IPFS upload failed: ${response.statusCode} - $errorBody");
     return null;
   }
lib/services/tree_clustering_service.dart (2)

139-142: Duplicated _convertCoordinate function.

This helper is duplicated in tree_nft_contract_read_services.dart. Consider extracting it to a shared utility (e.g., in geohash_utils.dart or a coordinate utilities file) to follow DRY principles.

Additionally, the same coordinate conversion concern applies here: using -90.0 offset may be incorrect for longitude values.


119-137: Bounds calculation is safe but could be more defensive.

The force-unwraps on lines 134-135 (south!, west!, north!, east!) are safe in the current usage since _calculateBounds is only called when treesInGroup.length >= minClusterSize (at least 2). However, if this method were called directly with an empty list in the future, it would throw.

Consider adding a guard clause for robustness:

 static LatLngBounds _calculateBounds(List<Map<String, dynamic>> trees) {
+   if (trees.isEmpty) {
+     return LatLngBounds(LatLng(0, 0), LatLng(0, 0));
+   }
    double? north, south, east, west;
lib/pages/nearby_trees_map_page.dart (1)

279-422: Theming/UX: avoid hard-coded white backgrounds; fix error copy; avoid double-building user marker

  • Loading/error overlays use Colors.white (Lines 318, 334) which will look wrong in dark mode; use Theme.of(context).scaffoldBackgroundColor or getThemeColors(context)['background'].
  • Error title says “Failed to load location” even when contract fetch fails (Lines 342-345).
  • _buildUserMarker() is called twice (Line 308) and does work; cache to a local variable in build.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 02cb1fd and 9f0018e.

📒 Files selected for processing (14)
  • .env.stencil (1 hunks)
  • README.md (1 hunks)
  • lib/main.dart (2 hunks)
  • lib/pages/nearby_trees_map_page.dart (1 hunks)
  • lib/pages/tree_details_page.dart (3 hunks)
  • lib/pages/trees_page.dart (2 hunks)
  • lib/services/tree_clustering_service.dart (1 hunks)
  • lib/utils/geohash_utils.dart (1 hunks)
  • lib/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_read_services.dart (2 hunks)
  • lib/utils/services/ipfs_services.dart (2 hunks)
  • lib/widgets/nft_display_utils/recent_trees_widget.dart (1 hunks)
  • lib/widgets/nft_display_utils/user_nfts_widget.dart (1 hunks)
  • lib/widgets/profile_widgets/profile_section_widget.dart (1 hunks)
  • lib/widgets/profile_widgets/user_profile_viewer_widget.dart (1 hunks)
🔇 Additional comments (8)
README.md (1)

142-142: Documentation accurately reflects the storage migration.

The update correctly documents the switch from generic IPFS to Web3.Storage/Storacha, aligning with the PR's objective to use a more cost-effective storage solution.

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

393-395: Good defensive programming for IPFS gateway fallback.

The extension to support both pinata.cloud and w3s.link URLs ensures backward compatibility while accommodating the new Web3.Storage provider. The fallback to ipfs.io gateway is appropriate for both services.

lib/pages/tree_details_page.dart (1)

195-195: Verify coordinate conversion consistency with the smart contract.

Both the map rendering and display text now use - 90.0 offset for longitude (previously 180.0). This matches the change in user_nfts_widget.dart, suggesting a systematic correction across the codebase.

Confirm this aligns with how coordinates are stored on-chain and that the map markers render at the correct locations.

Also applies to: 246-246

lib/main.dart (1)

19-19: Route integration is well-structured.

The new /nearby-trees route follows the existing GoRouter pattern and properly integrates the NearbyTreesMapPage. The route naming and structure are consistent with other routes in the application.

Also applies to: 139-145

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

375-375: Map button now properly navigates to the nearby trees feature.

Good change replacing the placeholder SnackBar with actual navigation to /nearby-trees, making the map functionality accessible to users.

lib/utils/geohash_utils.dart (1)

1-223: Geohash implementation is correct and production-ready.

All components have been verified:

  • Binary subdivision algorithm for encode/decode is mathematically correct
  • Neighbor and border lookup tables match the geohash specification exactly
  • Haversine formula uses the standard Earth radius (6371 km) and correct implementation
  • Precision-to-radius mapping provides appropriate geohash granularity for proximity searches
  • Edge cases are properly handled (empty strings, boundary detection, null-safe operators)

The implementation will effectively support the nearby trees map feature with accurate spatial indexing and neighbor cell discovery.

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

169-174: LGTM!

The expanded fallback logic correctly handles both legacy Pinata URLs and new Web3.Storage (w3s.link) URLs by extracting the IPFS hash and falling back to the ipfs.io gateway. This aligns with the migration to Web3.Storage while maintaining backward compatibility.

lib/services/tree_clustering_service.dart (1)

31-50: LGTM: Clustering logic is well-structured.

The zoom-based clustering approach is sensible:

  • High zoom (≥14) shows individual trees
  • Lower zoom levels group trees by geohash precision
  • The minClusterSize parameter provides flexibility

The implementation correctly handles edge cases like empty input and groups below the minimum cluster threshold.


PINATA_API_KEY=
PINATA_API_SECRET=
WEB3_STORAGE_TOKEN=
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check that .env is properly ignored
echo "=== Checking .gitignore for .env ==="
if grep -q "^\.env$" .gitignore; then
  echo "✓ .env is in .gitignore"
else
  echo "✗ WARNING: .env is not in .gitignore!"
fi

echo -e "\n=== Checking for any committed .env files ==="
git ls-files | grep "\.env$" || echo "✓ No .env files committed"

echo -e "\n=== Searching for hardcoded tokens in code ==="
rg -i "web3.*storage.*token" -C2 | grep -v "dotenv\|getenv\|env\[" || echo "✓ No hardcoded tokens found"

Repository: StabilityNexus/Treee

Length of output: 391


🏁 Script executed:

# Check for .gitignore existence and content
cat .gitignore | head -30

Repository: StabilityNexus/Treee

Length of output: 604


🏁 Script executed:

# Search for any token management or CI/CD configuration
fd -e "\.yml$" -e "\.yaml$" -o -e "\.json$" | grep -E "(ci|workflow|pipeline|github|gitlab|circleci)" | head -20

Repository: StabilityNexus/Treee

Length of output: 180


🏁 Script executed:

# Look for documentation on token/secret management
fd -e "\.md$" | xargs rg -l "token\|secret\|credential\|environment" | head -10

Repository: StabilityNexus/Treee

Length of output: 46


🏁 Script executed:

# Show full .gitignore
cat .gitignore

Repository: StabilityNexus/Treee

Length of output: 3644


🏁 Script executed:

# Search for CI/CD configuration files with corrected syntax
find . -type f \( -name "*.yml" -o -name "*.yaml" -o -name "Dockerfile" \) | grep -v node_modules | head -20

Repository: StabilityNexus/Treee

Length of output: 135


🏁 Script executed:

# Check the ipfs_services.dart file to see token handling
cat lib/utils/services/ipfs_services.dart | head -50

Repository: StabilityNexus/Treee

Length of output: 994


🏁 Script executed:

# Search for documentation files mentioning environment, tokens, or deployment
find . -name "*.md" -type f | xargs grep -l -i "environment\|token\|deployment\|ci\|secret" 2>/dev/null | head -10

Repository: StabilityNexus/Treee

Length of output: 135


🏁 Script executed:

# Check if .env.stencil exists and verify migration context
ls -la .env* 2>/dev/null && echo "---" && cat .env.stencil

Repository: StabilityNexus/Treee

Length of output: 480


🏁 Script executed:

# Check CI/CD workflow for environment variable handling
cat .github/workflows/flutter.yaml

Repository: StabilityNexus/Treee

Length of output: 818


🏁 Script executed:

# Check README for deployment and token management information
cat README.md | head -100

Repository: StabilityNexus/Treee

Length of output: 4225


🏁 Script executed:

# Search for any deployment or operations documentation
find . -type f \( -name "*deploy*" -o -name "*ops*" -o -name "*operation*" -o -name "CONTRIBUTING*" \) 2>/dev/null

Repository: StabilityNexus/Treee

Length of output: 46


🏁 Script executed:

# Check if there's any documentation about Web3.Storage setup
grep -r "Web3.Storage\|WEB3_STORAGE_TOKEN" . --include="*.md" --include="*.txt" 2>/dev/null | head -20

Repository: StabilityNexus/Treee

Length of output: 150


Fix CI/CD workflow to inject Web3.Storage token via GitHub Secrets instead of using empty template.

The .env.stencil template is properly ignored and not committed, and the code correctly loads the token via flutter_dotenv. However, the CI/CD workflow copies .env.stencil directly without injecting the actual token, meaning builds would run with an empty WEB3_STORAGE_TOKEN.

Address the remaining security gaps:

  1. Update .github/workflows/flutter.yaml to inject WEB3_STORAGE_TOKEN from GitHub Secrets before the build step
  2. Document the Web3.Storage token setup and permissions required (upload-only scope)
  3. Add deployment documentation covering environment setup for staging and production environments, including token rotation procedures
🤖 Prompt for AI Agents
In .env.stencil around line 3, the template leaves WEB3_STORAGE_TOKEN empty and
the CI workflow currently copies this file without injecting the real secret;
update .github/workflows/flutter.yaml to create or overwrite the env file during
the workflow using the GitHub Secret (e.g. write WEB3_STORAGE_TOKEN=${{
secrets.WEB3_STORAGE_TOKEN }} into the .env used by the build), ensure the
secret is referenced only at runtime and not printed in logs, and add repository
documentation: a short section describing how to create a Web3.Storage token
with upload-only scope, how to add it to GitHub Secrets for staging/production,
and a deployment subsection describing environment setup and token rotation
procedures.

Comment on lines +42 to +46
@override
void dispose() {
_mapController.dispose();
super.dispose();
}
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

🧩 Analysis chain

🏁 Script executed:

find . -name "pubspec.yaml" -type f | head -5

Repository: StabilityNexus/Treee

Length of output: 78


🏁 Script executed:

rg "flutter_map" -A 2 -B 2

Repository: StabilityNexus/Treee

Length of output: 2734


🏁 Script executed:

rg "MapController" -A 3 -B 1

Repository: StabilityNexus/Treee

Length of output: 2848


🏁 Script executed:

rg "dispose\(" lib/pages/nearby_trees_map_page.dart lib/widgets/map_widgets/ -B 2 -A 1

Repository: StabilityNexus/Treee

Length of output: 1548


🌐 Web query:

flutter_map 8.1.1 MapController dispose lifecycle API

💡 Result:

Short summary (flutter_map v8.1.1)

  • The MapController is a normal object you create and pass into FlutterMap; the plugin attaches that controller to the map and keeps it attached until the FlutterMap widget is disposed. Use a new MapController if you need a fresh controller when rebuilding. [1][2]

  • flutter_map does not provide a separate public "dispose" method on MapController for users to call; controller lifecycle is tied to the FlutterMap widget lifecycle (so it will be effectively detached when the map is disposed). Avoid reusing one external MapController across map widgets that get destroyed/rebuilt — this has produced lifecycle bugs (attachment/desync assertions) and is why the docs recommend creating/initializing the controller adjacent to the FlutterMap or reinitializing when needed. [2][4]

  • Practical guidance:

    • Create the MapController next to the FlutterMap (or recreate it when rebuilding).
    • If your map is in lazy lists (PageView/ListView) use MapOptions.keepAlive to retain state instead of relying on reusing the controller.
    • If you need to manage external references, clear/unset them when the FlutterMap is disposed to avoid reattachment bugs. See the linked GitHub issue for known problems and discussion. [2][3][4]

Sources

  1. flutter_map changelog (v8.1.1). [pub.dev]. [1]
  2. flutter_map controller docs / usage (controller lifecycle notes). [fleaflet.dev]. [2]
  3. GitHub issue: external MapController lifecycle not synced (reports reattachment/desync bug). [github.com]. [3]

Remove _mapController.dispose() — MapController does not expose dispose() in flutter_map 8.1.1

flutter_map does not provide a public dispose() method on MapController. The controller's lifecycle is managed by the FlutterMap widget itself; calling dispose() on it will fail at compile or runtime. Remove the _mapController.dispose() call and rely on the widget lifecycle cleanup instead.

Also note: this same incorrect pattern is present in lib/widgets/map_widgets/flutter_map_widget.dart (3 instances) and lib/widgets/map_widgets/static_map_display_widget.dart.

🤖 Prompt for AI Agents
In lib/pages/nearby_trees_map_page.dart around lines 42–46, remove the call to
_mapController.dispose() because flutter_map 8.1.1's MapController has no public
dispose() and its lifecycle is managed by FlutterMap; simply call
super.dispose() only. Also remove the same erroneous dispose() calls in
lib/widgets/map_widgets/flutter_map_widget.dart (three instances) and
lib/widgets/map_widgets/static_map_display_widget.dart so none of the files call
dispose() on a MapController; rely on the widget tree to clean up instead.

Comment on lines +48 to +107
Future<void> _loadNearbyTrees() async {
setState(() {
_isLoading = true;
_hasError = false;
_errorMessage = null;
});

try {
// Try to get user's current location, but don't fail if it's unavailable
try {
final locationInfo = await _locationService.getCurrentLocation();
_userLat = locationInfo.latitude;
_userLng = locationInfo.longitude;
logger.i("User location: $_userLat, $_userLng");
} catch (locationError) {
// Location failed, use default location
logger.w("Could not get user location, using default: $locationError");
_userLat = _defaultLat;
_userLng = _defaultLng;
}

// Check if widget is still mounted
if (!mounted) return;

// Fetch nearby trees using either actual or default location
final walletProvider =
Provider.of<WalletProvider>(context, listen: false);

final result = await ContractReadFunctions.getNearbyTrees(
walletProvider: walletProvider,
centerLat: _userLat!,
centerLng: _userLng!,
radiusKm: 10.0, // 10km radius
);

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

setState(() {
_nearbyTrees = trees;
_isLoading = false;
});

logger.i("Loaded ${trees.length} nearby trees");
} else {
throw Exception(result.errorMessage ?? 'Failed to load trees');
}
} catch (e) {
logger.e("Error loading nearby trees: $e");

// Use fallback location if not already set
setState(() {
_userLat ??= _defaultLat;
_userLng ??= _defaultLng;
_hasError = true;
_errorMessage = e.toString();
_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

Guard all post-await setState calls with mounted (avoid “setState() called after dispose”)
You check mounted once (Line 69-70), but there’s another await (Line 76-81) after it, and both the success and error paths call setState. Add a mounted check immediately before each setState that happens after an await.

@@
   Future<void> _loadNearbyTrees() async {
     setState(() {
       _isLoading = true;
       _hasError = false;
       _errorMessage = null;
     });
@@
-      final result = await ContractReadFunctions.getNearbyTrees(
+      final result = await ContractReadFunctions.getNearbyTrees(
         walletProvider: walletProvider,
         centerLat: _userLat!,
         centerLng: _userLng!,
         radiusKm: 10.0, // 10km radius
       );
 
+      if (!mounted) return;
+
       if (result.success && result.data != null) {
-        final trees = result.data['trees'] as List<Map<String, dynamic>>;
-        
-        setState(() {
+        final trees = (result.data['trees'] as List).cast<Map<String, dynamic>>();
+
+        setState(() {
           _nearbyTrees = trees;
           _isLoading = false;
         });
@@
     } catch (e) {
       logger.e("Error loading nearby trees: $e");
-      
-      // Use fallback location if not already set
-      setState(() {
+
+      if (!mounted) return;
+
+      // Use fallback location if not already set
+      setState(() {
         _userLat ??= _defaultLat;
         _userLng ??= _defaultLng;
         _hasError = true;
         _errorMessage = e.toString();
         _isLoading = 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
Future<void> _loadNearbyTrees() async {
setState(() {
_isLoading = true;
_hasError = false;
_errorMessage = null;
});
try {
// Try to get user's current location, but don't fail if it's unavailable
try {
final locationInfo = await _locationService.getCurrentLocation();
_userLat = locationInfo.latitude;
_userLng = locationInfo.longitude;
logger.i("User location: $_userLat, $_userLng");
} catch (locationError) {
// Location failed, use default location
logger.w("Could not get user location, using default: $locationError");
_userLat = _defaultLat;
_userLng = _defaultLng;
}
// Check if widget is still mounted
if (!mounted) return;
// Fetch nearby trees using either actual or default location
final walletProvider =
Provider.of<WalletProvider>(context, listen: false);
final result = await ContractReadFunctions.getNearbyTrees(
walletProvider: walletProvider,
centerLat: _userLat!,
centerLng: _userLng!,
radiusKm: 10.0, // 10km radius
);
if (result.success && result.data != null) {
final trees = result.data['trees'] as List<Map<String, dynamic>>;
setState(() {
_nearbyTrees = trees;
_isLoading = false;
});
logger.i("Loaded ${trees.length} nearby trees");
} else {
throw Exception(result.errorMessage ?? 'Failed to load trees');
}
} catch (e) {
logger.e("Error loading nearby trees: $e");
// Use fallback location if not already set
setState(() {
_userLat ??= _defaultLat;
_userLng ??= _defaultLng;
_hasError = true;
_errorMessage = e.toString();
_isLoading = false;
});
}
}
Future<void> _loadNearbyTrees() async {
setState(() {
_isLoading = true;
_hasError = false;
_errorMessage = null;
});
try {
// Try to get user's current location, but don't fail if it's unavailable
try {
final locationInfo = await _locationService.getCurrentLocation();
_userLat = locationInfo.latitude;
_userLng = locationInfo.longitude;
logger.i("User location: $_userLat, $_userLng");
} catch (locationError) {
// Location failed, use default location
logger.w("Could not get user location, using default: $locationError");
_userLat = _defaultLat;
_userLng = _defaultLng;
}
// Check if widget is still mounted
if (!mounted) return;
// Fetch nearby trees using either actual or default location
final walletProvider =
Provider.of<WalletProvider>(context, listen: false);
final result = await ContractReadFunctions.getNearbyTrees(
walletProvider: walletProvider,
centerLat: _userLat!,
centerLng: _userLng!,
radiusKm: 10.0, // 10km radius
);
if (!mounted) return;
if (result.success && result.data != null) {
final trees = (result.data['trees'] as List).cast<Map<String, dynamic>>();
setState(() {
_nearbyTrees = trees;
_isLoading = false;
});
logger.i("Loaded ${trees.length} nearby trees");
} else {
throw Exception(result.errorMessage ?? 'Failed to load trees');
}
} catch (e) {
logger.e("Error loading nearby trees: $e");
if (!mounted) return;
// Use fallback location if not already set
setState(() {
_userLat ??= _defaultLat;
_userLng ??= _defaultLng;
_hasError = true;
_errorMessage = e.toString();
_isLoading = false;
});
}
}
🤖 Prompt for AI Agents
In lib/pages/nearby_trees_map_page.dart around lines 48-107, add mounted guards
before every setState that runs after an await: before calling setState
following the ContractReadFunctions.getNearbyTrees await (the success path where
you assign _nearbyTrees and _isLoading) check if (!mounted) return; and
similarly before the setState in the catch block that sets
_userLat/_userLng/_hasError/_errorMessage/_isLoading, check if (!mounted)
return; (the initial setState at the top is before awaits and can remain
unchanged).

Comment on lines +115 to +124
/// Convert contract coordinates (fixed-point) to decimal degrees
double _convertCoordinate(int coordinate) {
return (coordinate / 1000000.0) - 90.0;
}

List<Marker> _buildTreeMarkers() {
return _nearbyTrees.map((tree) {
final lat = _convertCoordinate(tree['latitude'] as int);
final lng = _convertCoordinate(tree['longitude'] as int);
final isSelected = _selectedTreeId == tree['id'];
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

🧩 Analysis chain

🏁 Script executed:

# Search for coordinate-related code and contract service references
rg -i "coordinate|latitude|longitude" --type dart -A 3 -B 1 | head -100

Repository: StabilityNexus/Treee

Length of output: 7452


🏁 Script executed:

# Look for contract service or helper files
fd -e dart | xargs grep -l "contract\|web3\|ethers" | head -20

Repository: StabilityNexus/Treee

Length of output: 1422


🏁 Script executed:

# Search for constants or configuration related to coordinate encoding
rg -i "offset|scaling|1000000|90\.0|180\.0" --type dart -B 2 -A 2

Repository: StabilityNexus/Treee

Length of output: 50376


🏁 Script executed:

# Look for any Tree NFT or contract ABI files
fd "contract\|abi\|tree.*nft" -i

Repository: StabilityNexus/Treee

Length of output: 46


🏁 Script executed:

# Check if there's a service class handling coordinate conversion
rg "ConvertCoordinate|CoordinateConverter|_convert" --type dart -B 2 -A 5

Repository: StabilityNexus/Treee

Length of output: 10513


🏁 Script executed:

# Get the exact coordinate encoding from the contract write functions
cat -n lib/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_write_functions.dart | grep -A 5 -B 5 "latitude + 90\|longitude + 180"

Repository: StabilityNexus/Treee

Length of output: 644


🏁 Script executed:

# Check all files that use _convertCoordinate to see the scope of the issue
rg "_convertCoordinate" --type dart -B 2 -A 8 | head -150

Repository: StabilityNexus/Treee

Length of output: 12126


Critical: Longitude coordinate conversion uses wrong offset

The contract encodes coordinates as lat = (latitude + 90) * 1e6 and lng = (longitude + 180) * 1e6. Your decode applies -90.0 to both, which is incorrect for longitude. This causes all longitudes to be offset by 90 degrees. Split the conversion methods to apply the correct offsets:

-  double _convertCoordinate(int coordinate) {
-    return (coordinate / 1000000.0) - 90.0;
-  }
+  double _convertLatitude(int coordinate) => (coordinate / 1000000.0) - 90.0;
+  double _convertLongitude(int coordinate) => (coordinate / 1000000.0) - 180.0;

Then update all call sites in this file to use the appropriate method for each coordinate type.

Note: This bug also exists in lib/services/tree_clustering_service.dart, lib/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_read_services.dart, lib/widgets/nft_display_utils/recent_trees_widget.dart, and lib/pages/tree_details_page.dart with the same incorrect -90.0 offset applied to longitudes. Consider extracting a shared helper method to prevent future inconsistencies.

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

🤖 Prompt for AI Agents
In lib/pages/nearby_trees_map_page.dart around lines 115 to 124, the coordinate
decoding uses the same -90.0 offset for both latitude and longitude; change this
by splitting the conversion into two methods (e.g., _convertLatitude and
_convertLongitude) where latitude = (coordinate / 1e6) - 90.0 and longitude =
(coordinate / 1e6) - 180.0, then update the call sites in this file to call the
appropriate method for tree['latitude'] and tree['longitude']; also consider
extracting these helpers into a shared utility and replicate the fix in the
other listed files to avoid future inconsistencies.

Comment on lines +120 to +151
List<Marker> _buildTreeMarkers() {
return _nearbyTrees.map((tree) {
final lat = _convertCoordinate(tree['latitude'] as int);
final lng = _convertCoordinate(tree['longitude'] as int);
final isSelected = _selectedTreeId == tree['id'];

return Marker(
point: LatLng(lat, lng),
width: 40,
height: 50,
child: GestureDetector(
onTap: () {
setState(() {
_selectedTreeId = tree['id'];
});
_showTreeDetails(tree);
},
child: Icon(
Icons.park,
size: isSelected ? 45 : 35,
color: isSelected ? Colors.green[700] : Colors.green,
shadows: [
Shadow(
color: Colors.black.withValues(alpha: 0.3),
blurRadius: 4,
),
],
),
),
);
}).toList();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Marker selection/type robustness: normalize tree['id'] (int vs BigInt/String)
_selectedTreeId == tree['id'] (Line 124) and assignment (Line 133) assume tree['id'] is an int. If the contract layer returns BigInt (common in web3), selection highlighting and navigation can break at runtime. Consider normalizing IDs (final treeId = (tree['id'] as num).toInt() or explicit BigInt handling) at ingestion time (in _loadNearbyTrees).

🤖 Prompt for AI Agents
In lib/pages/nearby_trees_map_page.dart around lines 120 to 151, the code
compares and assigns tree IDs assuming tree['id'] is an int which breaks when
the ID is a BigInt or String; normalize the ID to an int before use (either
during ingestion in _loadNearbyTrees or at the top of _buildTreeMarkers) by
detecting BigInt and calling toInt(), parsing numeric Strings, or casting nums
toInt(), then use that normalized treeId for _selectedTreeId comparisons and
assignments and when passing to any navigation/detail functions.

Comment on lines +22 to +63
static String encode(double lat, double lng, {int precision = 7}) {
final latRange = [-90.0, 90.0];
final lngRange = [-180.0, 180.0];
final geohash = StringBuffer();
var isEven = true;
var bit = 0;
var ch = 0;

while (geohash.length < precision) {
if (isEven) {
// Longitude
final mid = (lngRange[0] + lngRange[1]) / 2;
if (lng > mid) {
ch |= (1 << (4 - bit));
lngRange[0] = mid;
} else {
lngRange[1] = mid;
}
} else {
// Latitude
final mid = (latRange[0] + latRange[1]) / 2;
if (lat > mid) {
ch |= (1 << (4 - bit));
latRange[0] = mid;
} else {
latRange[1] = mid;
}
}

isEven = !isEven;

if (bit < 4) {
bit++;
} else {
geohash.write(_base32[ch]);
bit = 0;
ch = 0;
}
}

return geohash.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 input validation to prevent invalid coordinate encoding.

The encode method doesn't validate that latitude is within [-90, 90] and longitude within [-180, 180]. Invalid coordinates could produce incorrect geohashes or cause unexpected behavior in the binary search logic.

Add validation at the start of the method:

  static String encode(double lat, double lng, {int precision = 7}) {
+   if (lat < -90 || lat > 90) {
+     throw ArgumentError('Latitude must be between -90 and 90, got $lat');
+   }
+   if (lng < -180 || lng > 180) {
+     throw ArgumentError('Longitude must be between -180 and 180, got $lng');
+   }
+   if (precision < 1 || precision > 12) {
+     throw ArgumentError('Precision must be between 1 and 12, got $precision');
+   }
+
    final latRange = [-90.0, 90.0];
🤖 Prompt for AI Agents
In lib/utils/geohash_utils.dart around lines 22 to 63, the encode method lacks
validation for latitude/longitude input; add checks at the start to ensure lat
is within -90..90 and lng within -180..180 and throw a descriptive
RangeError/ArgumentError if out of range so invalid coordinates are rejected
immediately. Also validate precision is a positive integer (and optionally
enforce a sensible max like 12) and throw if invalid. Keep the rest of the
encoding logic unchanged.

Comment on lines +68 to +104
static LatLng decode(String geohash) {
final latRange = [-90.0, 90.0];
final lngRange = [-180.0, 180.0];
var isEven = true;

for (var i = 0; i < geohash.length; i++) {
final char = geohash[i];
final cd = _base32.indexOf(char);

for (var j = 0; j < 5; j++) {
final mask = 1 << (4 - j);

if (isEven) {
// Longitude
if (cd & mask != 0) {
lngRange[0] = (lngRange[0] + lngRange[1]) / 2;
} else {
lngRange[1] = (lngRange[0] + lngRange[1]) / 2;
}
} else {
// Latitude
if (cd & mask != 0) {
latRange[0] = (latRange[0] + latRange[1]) / 2;
} else {
latRange[1] = (latRange[0] + latRange[1]) / 2;
}
}

isEven = !isEven;
}
}

final lat = (latRange[0] + latRange[1]) / 2;
final lng = (lngRange[0] + lngRange[1]) / 2;

return LatLng(lat, lng);
}
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 validation for geohash format in decode.

The decode method doesn't validate the input geohash string. Invalid characters or empty strings could cause index errors when looking up characters in _base32.

Add validation at the start of the method:

  static LatLng decode(String geohash) {
+   if (geohash.isEmpty) {
+     throw ArgumentError('Geohash cannot be empty');
+   }
+   
+   // Validate all characters are valid base32
+   for (var char in geohash.split('')) {
+     if (!_base32.contains(char)) {
+       throw ArgumentError('Invalid geohash character: $char');
+     }
+   }
+
    final latRange = [-90.0, 90.0];
📝 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
static LatLng decode(String geohash) {
final latRange = [-90.0, 90.0];
final lngRange = [-180.0, 180.0];
var isEven = true;
for (var i = 0; i < geohash.length; i++) {
final char = geohash[i];
final cd = _base32.indexOf(char);
for (var j = 0; j < 5; j++) {
final mask = 1 << (4 - j);
if (isEven) {
// Longitude
if (cd & mask != 0) {
lngRange[0] = (lngRange[0] + lngRange[1]) / 2;
} else {
lngRange[1] = (lngRange[0] + lngRange[1]) / 2;
}
} else {
// Latitude
if (cd & mask != 0) {
latRange[0] = (latRange[0] + latRange[1]) / 2;
} else {
latRange[1] = (latRange[0] + latRange[1]) / 2;
}
}
isEven = !isEven;
}
}
final lat = (latRange[0] + latRange[1]) / 2;
final lng = (lngRange[0] + lngRange[1]) / 2;
return LatLng(lat, lng);
}
static LatLng decode(String geohash) {
if (geohash.isEmpty) {
throw ArgumentError('Geohash cannot be empty');
}
// Validate all characters are valid base32
for (var char in geohash.split('')) {
if (!_base32.contains(char)) {
throw ArgumentError('Invalid geohash character: $char');
}
}
final latRange = [-90.0, 90.0];
final lngRange = [-180.0, 180.0];
var isEven = true;
for (var i = 0; i < geohash.length; i++) {
final char = geohash[i];
final cd = _base32.indexOf(char);
for (var j = 0; j < 5; j++) {
final mask = 1 << (4 - j);
if (isEven) {
// Longitude
if (cd & mask != 0) {
lngRange[0] = (lngRange[0] + lngRange[1]) / 2;
} else {
lngRange[1] = (lngRange[0] + lngRange[1]) / 2;
}
} else {
// Latitude
if (cd & mask != 0) {
latRange[0] = (latRange[0] + latRange[1]) / 2;
} else {
latRange[1] = (latRange[0] + latRange[1]) / 2;
}
}
isEven = !isEven;
}
}
final lat = (latRange[0] + latRange[1]) / 2;
final lng = (lngRange[0] + lngRange[1]) / 2;
return LatLng(lat, lng);
}
🤖 Prompt for AI Agents
In lib/utils/geohash_utils.dart around lines 68 to 104, the decode method lacks
input validation so empty strings or characters not in the geohash alphabet can
cause index errors; at the start of the method validate that the input is
non-null/non-empty and that every character exists in the allowed _base32 string
(optionally normalize case to the expected case before checking), and throw an
ArgumentError (or similar) with a clear message if validation fails so the
function exits early on invalid geohash values.

Comment on lines +506 to +509
/// Convert contract fixed-point coordinate to decimal degrees
static double _convertCoordinate(int coordinate) {
return (coordinate / 1000000.0) - 90.0;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for coordinate storage/conversion logic and the 1000000 factor
rg -n "1000000|coordinate" --type dart lib/utils/services/contract_functions/tree_nft_contract/ -B 2 -A 2

Repository: StabilityNexus/Treee

Length of output: 3445


🏁 Script executed:

#!/bin/bash
# Look for usages of _convertCoordinate function
rg -n "_convertCoordinate" --type dart -B 3 -A 3

Repository: StabilityNexus/Treee

Length of output: 10174


🏁 Script executed:

#!/bin/bash
# Search for latitude and longitude handling
rg -n "latitude|longitude" --type dart lib/utils/services/contract_functions/ -B 2 -A 2 | head -80

Repository: StabilityNexus/Treee

Length of output: 8535


Longitude conversion uses wrong offset and produces incorrect results.

The function incorrectly uses - 90.0 for both latitude and longitude. Based on the contract's encoding logic (visible in tree_nft_contract_write_functions.dart lines 65-66):

  • Latitude is encoded as (latitude + 90.0) * 1e6 → decode requires - 90.0
  • Longitude is encoded as (longitude + 180.0) * 1e6 → decode requires - 180.0

The current formula (coordinate / 1e6) - 90.0 produces longitude in the range [-90, 270] instead of the correct [-180, 180]. For example, longitude 0° (encoded as 180,000,000) decodes to 90° instead of 0°.

This affects multiple files using the same flawed conversion: nearby_trees_map_page.dart, tree_clustering_service.dart, recent_trees_widget.dart, and the reviewed file. Change the offset to - 180.0 for longitude values, or create separate conversion functions for latitude vs. longitude.

🤖 Prompt for AI Agents
In
lib/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_read_services.dart
around lines 506 to 509, the coordinate decoder uses a fixed -90.0 offset which
is only correct for latitude; longitude was encoded as (longitude + 180.0) * 1e6
and must be decoded with -180.0. Fix by splitting into two functions (or adding
a boolean/enum) so latitude uses (coordinate / 1e6) - 90.0 and longitude uses
(coordinate / 1e6) - 180.0, update all call sites (nearby_trees_map_page.dart,
tree_clustering_service.dart, recent_trees_widget.dart and this file) to call
the appropriate decoder, and add unit tests verifying decode(180000000) => 0°
longitude and decode(90000000) => 0° latitude.


String apiKey = dotenv.get('PINATA_API_KEY', fallback: "");
String apiSecret = dotenv.get('PINATA_API_SECRET', fallback: "");
String apiToken = dotenv.get('WEB3_STORAGE_TOKEN', fallback: "");
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

Missing validation for empty API token.

The apiToken falls back to an empty string if WEB3_STORAGE_TOKEN is not set, which will cause authentication failures without a clear error message. Consider validating this at upload time.

 String apiToken = dotenv.get('WEB3_STORAGE_TOKEN', fallback: "");

Add validation in uploadToIPFS:

Future<String?> uploadToIPFS(
    File imageFile, Function(bool) setUploadingState) async {
  if (apiToken.isEmpty) {
    // Consider logging or throwing an error
    return null;
  }
  setUploadingState(true);
  // ...
🤖 Prompt for AI Agents
In lib/utils/services/ipfs_services.dart around line 6, the apiToken is set with
a fallback to an empty string which can cause silent auth failures; add a
validation at the start of uploadToIPFS to check if apiToken.isEmpty and if so
immediately return null (or throw) and optionally log a clear error before any
network calls or toggling uploading state, ensure setUploadingState(true) is
only called after the token is validated, and make sure any early-return resets
uploading state or leaves it unchanged as appropriate.

Expanded(
child: Text(
'Location: ${(tree.latitude / 1000000) - 90}, ${(tree.longitude / 1000000) - 180}',
'Location: ${(tree.latitude / 1000000) - 90}, ${(tree.longitude / 1000000) - 90}',
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify coordinate conversion consistency across the codebase

echo "=== Searching for coordinate conversion patterns ==="
rg -n "/ 1[0e]6.*- (90|180)" --type dart -C2

echo -e "\n=== Searching for latitude/longitude field usage ==="
rg -n "\.latitude|\.longitude" --type dart -A1 -B1 | head -100

Repository: StabilityNexus/Treee

Length of output: 6587


🏁 Script executed:

#!/bin/bash
# Search for recent_trees_widget.dart
echo "=== Searching for recent_trees_widget.dart ==="
fd -n "recent_trees_widget" --type f

echo -e "\n=== Search for coordinate-related constants or documentation ==="
rg -n "coordinate|contract.*format|1e6|1000000" --type dart -i | grep -E "(const|final|//|comment)" | head -30

echo -e "\n=== Check for smart contract ABI or constants files ==="
fd -n "(abi|contract|constant)" --type dart --type json | head -20

Repository: StabilityNexus/Treee

Length of output: 4297


🏁 Script executed:

#!/bin/bash
# Look at the _convertCoordinate function implementation
echo "=== _convertCoordinate function in tree_clustering_service.dart ==="
rg -A5 "_convertCoordinate" lib/services/tree_clustering_service.dart

echo -e "\n=== _convertCoordinate function in nearby_trees_map_page.dart ==="
rg -A5 "_convertCoordinate" lib/pages/nearby_trees_map_page.dart

echo -e "\n=== _convertCoordinate function in tree_nft_contract_read_services.dart ==="
rg -A5 "_convertCoordinate" lib/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_read_services.dart

Repository: StabilityNexus/Treee

Length of output: 2789


🏁 Script executed:

#!/bin/bash
# Verify the coordinate encoding in write functions
echo "=== Coordinate encoding in tree_nft_contract_write_functions.dart ==="
sed -n '60,70p' lib/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_write_functions.dart

echo -e "\n=== Coordinate encoding in organisation_write_functions.dart ==="
sed -n '125,135p' lib/utils/services/contract_functions/organisation_contract/organisation_write_functions.dart

echo -e "\n=== Verify all usages of _convertCoordinate ==="
rg "_convertCoordinate" --type dart -c

Repository: StabilityNexus/Treee

Length of output: 1285


Fix longitude coordinate conversion offset across the codebase.

The longitude conversion uses an offset of 90 instead of the correct 180. Smart contract encoding stores coordinates as (latitude + 90.0) * 1e6 and (longitude + 180.0) * 1e6, so decoding must use - 90 for latitude and - 180 for longitude. Currently, all usages apply - 90 to both, causing longitude values to be incorrect by approximately 90 degrees.

This bug affects:

  • user_nfts_widget.dart line 255: (tree.longitude / 1000000) - 90
  • tree_details_page.dart lines 194, 246
  • tree_clustering_service.dart, nearby_trees_map_page.dart, and tree_nft_contract_read_services.dart — all using _convertCoordinate() for both latitude and longitude

Update the conversion logic to apply the correct offset for each coordinate type.

🤖 Prompt for AI Agents
In lib/widgets/nft_display_utils/user_nfts_widget.dart around line 255, the
longitude is decoded with the wrong offset: change the decoding so latitude uses
(value / 1e6) - 90 and longitude uses (value / 1e6) - 180; update the literal at
line 255 to use the correct -180 offset for longitude and audit/adjust any
shared helper (_convertCoordinate) to accept a coordinate type or separate
latitude/longitude converters so longitude applies -180 while latitude remains
-90, then propagate the fix to the other files noted (tree_details_page.dart,
tree_clustering_service.dart, nearby_trees_map_page.dart,
tree_nft_contract_read_services.dart) so all longitude decodings use -180.

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.

Find an alternative to IPFS

2 participants