Skip to content

Conversation

@IronJam11
Copy link
Contributor

@IronJam11 IronJam11 commented Aug 23, 2025

Solves #10

Summary by CodeRabbit

  • New Features

    • User NFTs header now shows a concise item count.
  • Bug Fixes

    • Connect Wallet button now initiates wallet connection.
    • Improved map stability with coordinate validation and safer fallbacks.
  • Style

    • Unified to solid colors and crisper shadows/borders across navigation, wallet UI, Mint NFT, Register, maps, and NFT views.
    • Simplified layouts for cleaner headers and containers.
  • Chores

    • Added Flutter CI workflow for analysis, formatting, and builds.
    • Sample environment template extended with APPLICATION_ID; contract address now sourced from environment.

@coderabbitai
Copy link

coderabbitai bot commented Aug 23, 2025

Walkthrough

Adds a Flutter CI workflow and an APPLICATION_ID env template key, converts several constants to env-driven names, applies UI styling changes to opaque colors, introduces small API/constructor tweaks and lint suppressions, and makes the wallet connect button initiate a connection.

Changes

Cohort / File(s) Summary
CI & Env
.github/workflows/flutter.yaml, .env.stencil
Adds a "Flutter CI" workflow (checkout, copy .env.stencil.env, setup Flutter, analyze/format/build) and adds APPLICATION_ID to the .env.stencil template.
Contract ABI & address → env-driven
lib/utils/constants/contract_abis/tree_nft_contract_abi.dart, lib/utils/services/contract_read_services.dart, lib/utils/services/contract_write_functions.dart
Renames TreeNftContractABItreeNftContractABI, TreeNFtContractAddresstreeNFtContractAddress; imports flutter_dotenv and source contract address from dotenv.env[...]; updates call sites to use new identifiers.
API key identifier renames
lib/models/wallet_chain_option.dart, lib/utils/services/ipfs_services.dart
Renames ALCHEMY_API_KEYalchemyApiKey, API_KEY/API_SECRETapiKey/apiSecret; retrieval from dotenv unchanged, updated usages.
Navbar & visual style adjustments
lib/components/universal_navbar.dart, lib/pages/mint_nft/mint_nft_details.dart, lib/pages/register_user_page.dart, lib/widgets/nft_display_utils/tree_nft_view_details_with_map.dart, lib/widgets/nft_display_utils/user_nfts_widget.dart
Replaced translucent colors with fully opaque values, tightened shadow/border colors, swapped some ContainerSizedBox, simplified headers and card layout; no logic changes.
Lint suppressions & minor static edits
lib/components/wallet_connect_dialog.dart, lib/providers/wallet_provider.dart, lib/pages/mint_nft/mint_nft_coordinates.dart, lib/widgets/nft_display_utils/tree_nft_view_widget.dart
Adds // ignore: comments for analyzer rules (use_build_context_synchronously, unused_element, file_names) without runtime changes.
Map widgets: APIs, validation, UI tweaks
lib/widgets/map_widgets/flutter_map_widget.dart
Constructors now use super.key; removed internal default coord/error-message fields; added coordinate sanitization/clamping; solid color overlays and layout cleanup.
Mint NFT flow & provider cleanup
lib/pages/mint_nft/mint_nft_images.dart, lib/providers/mint_nft_provider.dart, lib/pages/mint_nft/mint_nft_coordinates.dart
Retrieves provider before setState (with lint ignore), removes unused _detailsHash from provider, fixes import case and adds duplicate private preview builders annotated as unused.
Wallet connect trigger
lib/widgets/wallet_not_connected_widget.dart
Connect Wallet button now calls walletProvider.connectWallet() instead of doing nothing.
Misc cleanups
lib/pages/home_page.dart, lib/utils/constants/contractDetails.dart
Removes unused imports and deletes commented contract address notes.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant UI as WalletNotConnectedWidget
  participant WP as WalletProvider
  User->>UI: Tap "Connect Wallet"
  UI->>WP: walletProvider.connectWallet()
  WP-->>UI: Initiate connection flow (dialogs / callbacks)
  UI-->>User: Show progress / result
Loading
sequenceDiagram
  participant UI as App UI
  participant Svc as Contract Service
  participant Env as dotenv
  participant ABI as treeNftContractABI
  UI->>Svc: call read/write(...)
  Svc->>Env: dotenv.env['TREE_NFT_CONTRACT_ADDRESS']
  Env-->>Svc: contract address
  Svc->>ABI: reference ABI constant
  Svc-->>UI: future/result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • bhavik-mangla
  • ceilican

Poem

A rabbit taps the CI drum, hup-two-hop!
Env seed planted: APPLICATION_ID on top.
Contracts read from dotenv, neat and spry,
Wallets connect—we bind, then watch them fly. 🐇✨

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-githubWorkflow

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

🧹 Nitpick comments (7)
.env.stencil (1)

5-6: Fix dotenv-linter warnings: sort keys and add trailing newline

Alphabetize keys and ensure a blank line at EOF to satisfy lint hints. No functional impact, but it keeps templates clean and consistent.

Apply:

-WALLETCONNECT_PROJECT_ID=<WALLETCONNECT_PROJECT_ID>
-API_KEY=<PINATA_API_KEY>
-API_SECRET=<PINATA_API_SECRET>
-ALCHEMY_API_KEY=<ALCHEMY_API_KEY>
-CONTRACT_ADDRESS=<CONTRACT_ADDRESS>
-APPLICATION_ID=<APPLICATION_ID>
+ALCHEMY_API_KEY=<ALCHEMY_API_KEY>
+API_KEY=<PINATA_API_KEY>
+API_SECRET=<PINATA_API_SECRET>
+APPLICATION_ID=<APPLICATION_ID>
+CONTRACT_ADDRESS=<CONTRACT_ADDRESS>
+WALLETCONNECT_PROJECT_ID=<WALLETCONNECT_PROJECT_ID>
+
.github/workflows/flutter.yml (6)

20-20: Remove trailing whitespace

Trims the single trailing space that YAMLlint flagged.

-          flutter-version: '>=3.16.0' 
+          flutter-version: '>=3.16.0'

17-21: Enable pub cache to speed up CI

subosito/flutter-action supports caching; enabling it materially reduces build times.

       - name: Setup Flutter
         uses: subosito/flutter-action@v2
         with:
-          flutter-version: '>=3.16.0'
+          flutter-version: '>=3.16.0'
+          cache: true

25-26: Avoid redundant pub resolution during analyze

Since you already ran flutter pub get, pass --no-pub to keep analysis faster and deterministic.

-      - name: Dart analyze
-        run: flutter analyze
+      - name: Dart analyze
+        run: flutter analyze --no-pub

3-8: Add concurrency and minimal permissions

Prevent duplicate runs on the same ref spamming CI and set least-privilege permissions.

 name: Flutter CI

 on:
   push:
     branches: [ main ]
   pull_request:
     branches: [ main ]
+
+permissions:
+  contents: read
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true

13-21: Security hardening: pin actions to commit SHAs

To reduce supply-chain risk, pin third-party actions to specific SHAs and keep a note of the version.

-      - name: Checkout repository
-        uses: actions/checkout@v4
+      - name: Checkout repository
+        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
@@
-      - name: Setup Flutter
-        uses: subosito/flutter-action@v2
+      - name: Setup Flutter
+        uses: subosito/flutter-action@f45c8c6e01bd3a746c4d4d5a9c8e9c3b6a2a2c29 # v2

Note: Replace SHAs with the latest verified for your org’s policy.


28-32: Add a Flutter test step to CI

We detected an existing Dart test file at test/widget_test.dart. To catch regressions early, it’s recommended to run your tests in the CI pipeline before building the web artifact.

• Location: .github/workflows/flutter.yml
– Insert immediately after the Check formatting step and before the Flutter build (web example) step.

Suggested update:

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

+      - name: Run tests
+        run: flutter test --coverage
+
       - name: Flutter build (web example)
         run: flutter build web
📜 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 c7fba57.

📒 Files selected for processing (2)
  • .env.stencil (1 hunks)
  • .github/workflows/flutter.yml (1 hunks)
🧰 Additional context used
🪛 actionlint (1.7.7)
.github/workflows/flutter.yml

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.yml

[error] 20-20: trailing spaces

(trailing-spaces)

🪛 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: [EndingBlankLine] No blank line at the end of the file

(EndingBlankLine)


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

(UnorderedKey)

ALCHEMY_API_KEY=<ALCHEMY_API_KEY>
CONTRACT_ADDRESS=<CONTRACT_ADDRESS>
CONTRACT_ADDRESS=<CONTRACT_ADDRESS>
APPLICATION_ID=<APPLICATION_ID>
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify APPLICATION_ID usage and docs

Confirm that APPLICATION_ID is actually read by the app and mentioned in README/Getting Started so newcomers don’t miss it.

Run:


🏁 Script executed:

#!/bin/bash
# Find references to APPLICATION_ID and env loading
rg -nCI3 --glob '!**/build/**' 'APPLICATION_ID|dotenv|flutter_dotenv|--dart-define'

Length of output: 160


🏁 Script executed:

#!/bin/bash
# Find references to APPLICATION_ID and env loading with correct flags
rg -n -C 3 -i --glob '!**/build/**' \
  -e 'APPLICATION_ID' \
  -e 'dotenv' \
  -e 'flutter_dotenv' \
  -e '--dart-define'

Length of output: 6796


🏁 Script executed:

#!/bin/bash
# Check README for mention of APPLICATION_ID and env setup
rg -n -C 3 'APPLICATION_ID' -g 'README.md'
rg -n -C 3 'env' -g 'README.md'

Length of output: 1177


Add and document APPLICATION_ID environment support

It looks like APPLICATION_ID is declared in .env.stencil but never actually read by the Flutter code, and the README has no mention of it. To avoid confusion for newcomers, please:

  • Load the variable in Dart
    In lib/main.dart, after await dotenv.load(...), add something like:

    const String applicationId = dotenv.env['APPLICATION_ID'] ?? '';

    so that your app can access the value at runtime.

  • Pass it through to your Linux runner
    Modify linux/CMakeLists.txt to use the environment value (e.g. via $ENV{APPLICATION_ID}) or otherwise propagate applicationId into the desktop build.

  • Remove or update .env.stencil
    If you’re not actually using APPLICATION_ID at runtime, remove it from .env.stencil. Otherwise, keep it and document it fully.

  • Document in README.md
    Under “Getting Started,” add steps to:

    1. Copy .env.stencil to .env
    2. Set APPLICATION_ID=<your.app.id>
    3. Run the app (and rebuild desktop targets as needed)

This ensures the environment variable is both used and clearly documented for all platforms.

🧰 Tools
🪛 dotenv-linter (3.3.0)

[warning] 6-6: [EndingBlankLine] No blank line at the end of the file

(EndingBlankLine)


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

(UnorderedKey)

🤖 Prompt for AI Agents
In .env.stencil around line 6 the APPLICATION_ID variable is declared but not
used; update lib/main.dart to read the variable after dotenv.load (e.g., assign
dotenv.env['APPLICATION_ID'] to a const or final applicationId) so the app can
access it at runtime, modify linux/CMakeLists.txt to propagate the environment
value into the Linux build (for example read $ENV{APPLICATION_ID} or pass it as
a compile-time definition to the binary), and then either remove APPLICATION_ID
from .env.stencil if unused or keep and document it in README.md under Getting
Started with steps to copy .env.stencil to .env, set
APPLICATION_ID=<your.app.id>, and rebuild/run desktop targets.


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.

⚠️ Potential issue

Update checkout action to v4 (actionlint failure)

actionlint flags actions/checkout@v3 as too old on current runners. Upgrade to v4 to avoid workflow failures.

-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
📝 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
uses: actions/checkout@v3
uses: actions/checkout@v4
🧰 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
.github/workflows/flutter.yml around line 15: the workflow uses
actions/checkout@v3 which actionlint flags as outdated; update the checkout step
to actions/checkout@v4. Edit the workflow to replace the version string from v3
to v4 (or use the stable major tag actions/checkout@v4) and ensure any existing
inputs remain unchanged; commit the updated workflow file so the CI runs with
the newer action version.

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

🧹 Nitpick comments (6)
.github/workflows/flutter.yaml (6)

22-24: Reproducible installs and faster CI with Pub cache.

  • Enforce the lockfile for deterministic dependency resolution.
  • Cache the Pub cache to speed up subsequent runs.

Apply this diff:

+      - name: Cache Pub dependencies
+        uses: actions/cache@v4
+        with:
+          path: ~/.pub-cache
+          key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.lock') }}
+          restore-keys: |
+            ${{ runner.os }}-pub-
+
       - name: Install dependencies
-        run: flutter pub get
+        run: flutter pub get --enforce-lockfile

31-32: Harden the web build step (optional).

  • Pre-cache web artifacts to reduce build time and flakiness on clean runners.
  • Explicitly pass --release (default) for clarity.

Apply this diff:

+      - name: Precache web artifacts
+        run: flutter precache --web
+
       - name: Flutter build (web example)
-        run: flutter build web
+        run: flutter build web --release

3-8: Add minimal permissions and cancel-in-progress to save CI minutes.

  • Principle of least privilege for GHA tokens.
  • Cancel superseded runs on the same ref to avoid wasted builds.

Apply this diff:

 on:
   push:
     branches: [ main ]
   pull_request:
     branches: [ main ]
 
+permissions:
+  contents: read
+  pull-requests: read
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true

Optionally skip draft PRs:

 jobs:
   build:
+    if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
     runs-on: ubuntu-latest

11-11: Set a job timeout to prevent hung builds.

Protects against indefinite runner usage when a step stalls.

Apply this diff:

   build:
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-latest
+    timeout-minutes: 20

20-20: Trailing space flagged by yamllint.

There’s a trailing space after 'stable' on Line 20. The earlier “Setup Flutter” fix removes this line entirely; otherwise, remove the trailing space.

If you keep flutter-version, apply this minimal diff:

-          flutter-version: 'stable' 
+          flutter-version: 'stable'

1-33: Ensure correct Flutter action input and enhance CI robustness

The subosito/flutter-action@v2 step is correctly using the flutter-version input—no channel field is needed. It accepts both channel names (stable, beta, dev, master) and explicit SDK versions (e.g. 3.10.2), as well as aliases like latest or stable . For consistency and reproducibility, consider pinning to an explicit version:

- name: Setup Flutter
  uses: subosito/flutter-action@v2
  with:
    flutter-version: '3.10.2'  # or 'stable'

Additional recommendations to improve speed and reliability:

• Checkout step

  • Pin the checkout action to v3 and fetch full history if you rely on tags or versioning:
    - name: Checkout repository
      uses: actions/checkout@v3
      with:
        fetch-depth: 0

• Cache pub dependencies

- name: Cache Pub packages
  uses: actions/cache@v3
  with:
    path: ~/.pub-cache
    key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.yaml') }}
    restore-keys: |
      ${{ runner.os }}-pub-

• Run unit tests

- name: Run Flutter tests
  run: flutter test --coverage

• Add workflow concurrency to avoid redundant runs

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

• Review permissions

  • Limit token scopes where possible (e.g., permissions: { contents: read } if write access isn’t required).

These changes will prevent unexpected breakage, speed up CI by reusing dependencies, and ensure only the latest relevant runs are active.

📜 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 c7fba57 and 17ff414.

📒 Files selected for processing (1)
  • .github/workflows/flutter.yaml (1 hunks)
🧰 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] 20-20: trailing spaces

(trailing-spaces)

🔇 Additional comments (1)
.github/workflows/flutter.yaml (1)

28-29: Formatting check is solid.

Using dart format --set-exit-if-changed is the right approach for consistent formatting enforcement.

Comment on lines +15 to +16
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.

⚠️ Potential issue

Upgrade checkout to v4 (v3 is deprecated and flagged by actionlint).

actionlint warns that actions/checkout@v3 is too old for current runners. Move to v4 to avoid workflow failures and to get Node 20 compatibility.

Apply this diff:

-      - name: Checkout repository
-        uses: actions/checkout@v3
+      - name: Checkout repository
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 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
uses: actions/checkout@v3
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
🧰 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
.github/workflows/flutter.yaml around lines 15-16: the workflow uses
actions/checkout@v3 which is deprecated and flagged by actionlint; update the
step to use actions/checkout@v4 (replace v3 with v4), commit the updated
workflow file, and re-run the workflow to verify actionlint and CI succeed.

Comment on lines 18 to 24
uses: subosito/flutter-action@v2
with:
flutter-version: 'stable'

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix Flutter setup: use channel or pin a real version; also enable caching.

Using flutter-version: 'stable' is likely invalid; the action expects a concrete version (e.g., 3.24.x) or you should set channel: stable. Add caching to speed up runs. This change also removes the trailing space lint on Line 20.

Apply this diff (channel-based, recommended for CI unless you need a specific patch version):

       - name: Setup Flutter
         uses: subosito/flutter-action@v2
         with:
-          flutter-version: 'stable' 
+          channel: 'stable'
+          cache: true

Alternatively, pin an explicit version for reproducibility:

-          flutter-version: 'stable' 
+          flutter-version: '3.24.x'
+          cache: true
🧰 Tools
🪛 YAMLlint (1.37.1)

[error] 20-20: trailing spaces

(trailing-spaces)

🤖 Prompt for AI Agents
.github/workflows/flutter.yaml around lines 18 to 21: the workflow uses
subosito/flutter-action@v2 with flutter-version: 'stable' (invalid) and has a
trailing space; change it to either set channel: stable (preferred) or pin a
concrete patch series like flutter-version: '3.24.x' for reproducibility, remove
the trailing space, and enable caching for Flutter artifacts (use the action's
cache option or add actions/cache for the Flutter SDK and pub cache) so CI runs
are faster and deterministic.

Comment on lines +25 to +30
- name: Dart analyze
run: flutter analyze

Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Add unit tests to CI (if present).

Running tests is a core CI signal and catches regressions beyond static analysis.

Proposed step:

       - name: Dart analyze
         run: flutter analyze
+
+      - name: Flutter tests
+        run: flutter test -r expanded

To verify whether this repo has tests before adding the step, run:


🏁 Script executed:

#!/bin/bash
# List *_test.dart files with context
rg -nP --type=dart -C2 '_test\.dart$' || true

Length of output: 46


🏁 Script executed:

#!/bin/bash
# Find directories named test or tests
find . -type d \( -name test -o -name tests \) || true

# List Dart test files by suffix
rg -n --type=dart '_test\.dart$' || true

# Search for test import statements
rg -n --type=dart 'import .+test\.dart' || true

# Search for any test function calls
rg -n --type=dart 'test\(' || true

# Search specifically for Flutter widget tests
rg -n --type=dart 'testWidgets' || true

Length of output: 384


Add Flutter test step to CI workflow

Tests already exist in test/widget_test.dart (using testWidgets), so let’s surface them in CI. Update .github/workflows/flutter.yaml as follows:

• File: .github/workflows/flutter.yaml
After the Dart analyze step, add a Flutter tests job.

       - name: Dart analyze
         run: flutter analyze
+      - name: Run Flutter tests
+        run: flutter test -r expanded
📝 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
- name: Dart analyze
run: flutter analyze
- name: Dart analyze
run: flutter analyze
- name: Run Flutter tests
run: flutter test -r expanded
🤖 Prompt for AI Agents
.github/workflows/flutter.yaml around lines 25-27: after the "Dart analyze"
step, add a new CI step/job to run Flutter tests; create a job (or a step in the
existing job) that ensures Flutter is set up (same runner or setup-flutter
action), runs flutter pub get, and then runs flutter test (so
test/widget_test.dart will execute), and ensure the step fails the build on test
failures and has appropriate name and placement immediately after the analyze
step.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (3)
.github/workflows/flutter.yaml (3)

17-21: Enable Flutter pub caching and remove trailing whitespace.

Use the action’s built-in cache to speed up CI. Also, Line 20 has a trailing space flagged by yamllint.

       - name: Setup Flutter
         uses: subosito/flutter-action@v2
         with:
-          channel: 'stable' 
+          channel: 'stable'
+          cache: true

14-16: Upgrade checkout to v4 (and fetch full history).

actions/checkout@v3 is deprecated and flagged by actionlint on current runners. Move to v4 and fetch full history (useful for versioning or tooling that inspects git metadata).

       - name: Checkout repository
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 0

25-27: Run your Flutter tests in CI.

Tests exist in the repo; add a step so regressions fail the build.

       - name: Dart analyze
         run: flutter analyze
+
+      - name: Run Flutter tests
+        run: flutter test -r expanded
🧹 Nitpick comments (2)
.github/workflows/flutter.yaml (2)

31-32: Publish build artifacts from CI.

Uploading the web build helps with PR previews and debugging.

       - name: Flutter build (web example)
         run: flutter build web
+
+      - name: Upload web build artifact
+        uses: actions/upload-artifact@v4
+        with:
+          name: web-build
+          path: build/web

3-12: Add workflow hardening: least-privilege permissions, concurrency, and a timeout.

  • permissions: contents: read reduces token scope.
  • concurrency cancels superseded runs on the same branch.
  • timeout-minutes prevents hung jobs.
 on:
   push:
     branches: [ main ]
   pull_request:
     branches: [ main ]
 
+permissions:
+  contents: read
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
 jobs:
   build:
     runs-on: ubuntu-latest
+    timeout-minutes: 30
📜 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 17ff414 and 874e6fb.

📒 Files selected for processing (1)
  • .github/workflows/flutter.yaml (1 hunks)
🧰 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] 20-20: trailing spaces

(trailing-spaces)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 16

Caution

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

⚠️ Outside diff range comments (6)
lib/widgets/nft_display_utils/user_nfts_widget.dart (2)

283-295: “Alive/Deceased” status logic is inconsistent (color vs label)

Color uses death == 0 while the label uses death < now — a living tree (death=0) gets a green pill but the label “Deceased”. Use a single predicate (death > 0 => Deceased) for both.

Apply this diff:

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

150-155: Fix pagination logic and support arbitrary user addresses

The current implementation always uses offset: 0 and the connected wallet’s address, causing duplicate pages and preventing fetching NFTs for widget.userAddress when viewing another user’s profile.

Key changes required:

• In lib/utils/services/contract_read_services.dart
– Add an optional EthereumAddress? userAddress parameter to getNFTsByUserPaginated’s signature.
– In the method body, build args using userAddress ?? walletProvider.currentAddress.

• In lib/widgets/nft_display_utils/user_nfts_widget.dart (lines 150–155 & 164–173)
– Compute offset dynamically based on _currentPage and _itemsPerPage instead of hard-coding 0.
– Pass userAddress: EthereumAddress.fromHex(widget.userAddress) to target the correct address.
– Reset _currentPage when not loading more, dedupe by token ID, then increment page and update _hasMore.

Proposed diffs:

--- a/lib/utils/services/contract_read_services.dart
+++ b/lib/utils/services/contract_read_services.dart
@@ class ContractReadFunctions {
-  static Future<ContractReadResult> getNFTsByUserPaginated({
-    required WalletProvider walletProvider,
-    int offset = 0,
-    int limit = 10,
+  static Future<ContractReadResult> getNFTsByUserPaginated({
+    required WalletProvider walletProvider,
+    EthereumAddress? userAddress,     // <— new optional target address
+    int offset = 0,
+    int limit = 10,
   }) async {
     try {
       if (!walletProvider.isConnected) {
@@
-      final String address = walletProvider.currentAddress.toString();
-      if (!address.startsWith('0x')) {
-        return ContractReadResult.error(
-          errorMessage: 'Invalid wallet address format',
-        );
-      }
-
-      final EthereumAddress userAddress = EthereumAddress.fromHex(address);
+      // Use provided address or fall back to connected wallet
+      final EthereumAddress user = userAddress ??
+          EthereumAddress.fromHex(walletProvider.currentAddress.toString());
+
+      if (!user.hex.startsWith('0x')) {
+        return ContractReadResult.error(
+          errorMessage: 'Invalid wallet address format',
+        );
+      }
@@
-      final List<dynamic> args = [
-        userAddress,
+      final List<dynamic> args = [
+        user,
         BigInt.from(offset),
         BigInt.from(limit),
       ];
--- a/lib/widgets/nft_display_utils/user_nfts_widget.dart
+++ b/lib/widgets/nft_display_utils/user_nfts_widget.dart
@@ 150,8c150,13
-      final result = await ContractReadFunctions.getNFTsByUserPaginated(
-        walletProvider: walletProvider,
-        offset: 0,
-        limit: 10,
+      final result = await ContractReadFunctions.getNFTsByUserPaginated(
+        walletProvider: walletProvider,
+        userAddress: EthereumAddress.fromHex(widget.userAddress),
+        offset: (loadMore ? _currentPage : 0) * _itemsPerPage,
+        limit: _itemsPerPage,
       );
@@ 164,10c169,21
-        setState(() {
-          if (loadMore) {
-            _nfts.addAll(newTrees);
-          } else {
-            _nfts = newTrees;
-          }
-          _totalCount = totalCount;
-          _currentPage++;
-          _hasMore = _nfts.length < _totalCount;
-        });
+        setState(() {
+          if (loadMore) {
+            _nfts.addAll(newTrees);
+          } else {
+            _nfts = newTrees;
+            _currentPage = 0; // reset page when refreshing
+          }
+          // De-duplicate by token ID to guard against overlap
+          final seen = <int>{};
+          _nfts = _nfts.where((t) => seen.add(t.id)).toList();
+          _totalCount = totalCount;
+          _currentPage += 1;
+          _hasMore = _nfts.length < _totalCount;
+        });

These updates ensure correct paging, avoid duplicate fetches, and allow viewing another user’s NFTs.

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

495-501: Buttons use same foreground and background color — icons/text become invisible

Both IconButton and TextButton set foregroundColor and backgroundColor to 0xFF1CD381, resulting in no contrast.

Apply this diff:

-              IconButton(
+              IconButton(
                 onPressed: _refreshLocation,
                 icon: const Icon(Icons.refresh, size: 20),
                 style: IconButton.styleFrom(
-                  backgroundColor: const Color(0xFF1CD381),
-                  foregroundColor: const Color(0xFF1CD381),
+                  backgroundColor: const Color(0xFF1CD381),
+                  foregroundColor: Colors.white,
                 ),
               ),
@@
-                TextButton.icon(
+                TextButton.icon(
                   onPressed: _useCurrentLocation,
                   icon: const Icon(Icons.my_location, size: 16),
                   label: const Text("Auto"),
                   style: TextButton.styleFrom(
-                    foregroundColor: const Color(0xFF1CD381),
-                    backgroundColor: const Color(0xFF1CD381),
+                    backgroundColor: const Color(0xFF1CD381),
+                    foregroundColor: Colors.white,
                   ),
                 ),

Also applies to: 508-511


561-567: Field label chip: icon and background share the same color — icon invisible

Use a contrasting icon color (e.g., white or black) against the green background.

Apply this diff:

-            Container(
+            Container(
               padding: const EdgeInsets.all(4),
               decoration: BoxDecoration(
                 color: const Color(0xFF1CD381),
                 borderRadius: BorderRadius.circular(6),
               ),
               child: Icon(
                 icon,
-                color: const Color(0xFF1CD381),
+                color: Colors.white,
                 size: 14,
               ),
             ),
lib/pages/mint_nft/mint_nft_details.dart (2)

140-147: Header icon invisible: white icon on white background

The container is Colors.white and the icon is also Colors.white. Use a contrasting icon color.

Apply this diff:

               Container(
                 padding: const EdgeInsets.all(12),
                 decoration: BoxDecoration(
-                  color: Colors.white,
+                  color: Colors.white,
                   borderRadius: BorderRadius.circular(14),
                 ),
                 child: const Icon(
                   Icons.edit_note,
-                  color: Colors.white,
+                  color: Color(0xFF1CD381),
                   size: 28,
                 ),
               ),

262-268: Form label chip: icon and background use the same green — icon invisible

Set the icon to white (or use a white container with green icon). This matches the visual language used elsewhere.

Apply this diff:

               decoration: BoxDecoration(
-                color: const Color(0xFF1CD381),
+                color: const Color(0xFF1CD381),
                 borderRadius: BorderRadius.circular(8),
               ),
               child: Icon(
                 icon,
-                color: const Color(0xFF1CD381),
+                color: Colors.white,
                 size: 18,
               ),
♻️ Duplicate comments (2)
.github/workflows/flutter.yaml (2)

15-16: Upgrade checkout to v4 (v3 flagged by actionlint).

Apply this diff:

-      - name: Checkout repository
-        uses: actions/checkout@v3
+      - name: Checkout repository
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 0

28-29: Run Flutter tests in CI.

Tests catch regressions beyond static analysis; they exist in the repo.

Apply this diff right after analyze:

       - name: Dart analyze
         run: flutter analyze
+
+      - name: Run Flutter tests
+        run: flutter test -r expanded
🧹 Nitpick comments (30)
lib/pages/mint_nft/mint_nft_images.dart (2)

142-146: Make _showSnackBar no-op if widget is unmounted

Defensive guard avoids using a stale BuildContext if uploads complete after navigation.

   void _showSnackBar(String message) {
-    ScaffoldMessenger.of(context).showSnackBar(
+    if (!mounted) return;
+    ScaffoldMessenger.of(context).showSnackBar(
       SnackBar(content: Text(message)),
     );
   }

258-261: Solid green overlay fully obscures the image; prefer themed, semi-transparent success overlay

The earlier semi-transparent overlay preserved image context. Consider using theme colors with slight opacity for better legibility across light/dark themes.

-                                          color: Colors.green,
+                                          color: Theme.of(context)
+                                              .colorScheme
+                                              .primary
+                                              .withOpacity(0.85),

Alternative: use a lighter success tone if you have a dedicated success color in your theme.

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

41-46: Avoid null-assertion on MaterialColor indexing.

Replace Colors.green[50] and Colors.grey[300]! with .shade50/.shade300 to remove the ! and be explicit about shades.

Apply this diff:

-                  tileColor: isCurrentChain ? Colors.green[50] : null,
+                  tileColor: isCurrentChain ? Colors.green.shade50 : null,
@@
-                      color: isCurrentChain ? Colors.green : Colors.grey[300]!,
+                      color: isCurrentChain ? Colors.green : Colors.grey.shade300,

66-88: Handle unsuccessful chain switch (success == false) with feedback.

Currently only exceptions surface an error snackbar. If switchChain returns false, the user gets no feedback.

Apply this diff:

   final success = await walletProvider.switchChain(chainId);
-  if (success && context.mounted) {
+  if (success && context.mounted) {
     ScaffoldMessenger.of(context).showSnackBar(
       const SnackBar(
         content: Text('Chain switched successfully!'),
         backgroundColor: Colors.green,
       ),
     );
+  } else if (context.mounted) {
+    ScaffoldMessenger.of(context).showSnackBar(
+      const SnackBar(
+        content: Text('Could not switch chain. Please try again.'),
+        backgroundColor: Colors.red,
+      ),
+    );
   }
lib/widgets/nft_display_utils/tree_nft_view_details_with_map.dart (2)

36-41: Shadow color now fully opaque; consider reducing opacity for a softer elevation.

Colors.black for shadows can look harsh (especially on web). Prior withOpacity(0.1) was closer to Material defaults.

Apply this diff:

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

66-71: Same here: fully opaque green shadow may appear heavy.

Use a slight opacity for subtler depth and better theming.

Apply this diff:

-                BoxShadow(
-                  color: Colors.green,
+                BoxShadow(
+                  color: Colors.green.withOpacity(0.1),
                   blurRadius: 8,
                   offset: const Offset(0, 2),
                 ),
lib/models/wallet_chain_option.dart (1)

33-35: Guard against missing key; produce actionable diagnostics.

Even with lazy access, an empty key yields broken URLs. Consider asserting in debug or logging a clear message to help setup.

Example:

assert(
  _alchemyApiKey().isNotEmpty,
  'ALCHEMY_API_KEY is missing. Provide it via .env or CI secrets.',
);

Also applies to: 40-40, 50-50

.github/workflows/flutter.yaml (2)

17-19: Trim trailing spaces and enable Flutter caching for faster CI.

There are trailing spaces (YAMLlint errors), and enabling the action’s cache speeds up runs.

Apply this diff:

-      - name: Setup environment file from template 
-        run: cp .env.stencil .env 
+      - name: Setup environment file from template
+        run: cp .env.stencil .env
@@
-      - name: Setup Flutter
+      - name: Setup Flutter
         uses: subosito/flutter-action@v2
         with:
-          channel: 'stable' 
+          channel: 'stable'
+          cache: true

Also applies to: 20-24


34-35: Optional: add basic workflow hardening.

Consider explicit least-privilege permissions and PR concurrency to cancel superseded builds.

Example:

 name: Flutter CI

 on:
   push:
@@
 jobs:
   build:
     runs-on: ubuntu-latest
+    permissions:
+      contents: read
+    concurrency:
+      group: ${{ github.workflow }}-${{ github.ref }}
+      cancel-in-progress: true
lib/utils/services/ipfs_services.dart (1)

9-31: Make upload lifecycle robust: ensure state reset on error and add a timeout.

Set uploading=false in a finally block and avoid indefinite hangs with a timeout.

Apply this diff:

-Future<String?> uploadToIPFS(
-    File imageFile, Function(bool) setUploadingState) async {
-  setUploadingState(true);
+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;
-  }
+  try {
+    final response =
+        await request.send().timeout(const Duration(seconds: 60));
+    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/utils/constants/contract_abis/tree_nft_contract_abi.dart (2)

4-4: ABI constant rename to lowerCamel looks good; remove stale lint suppression

Renaming to treeNftContractABI aligns with Effective Dart. The // ignore: constant_identifier_names at Line 1 is now obsolete—please remove it.


2-2: dotenv.load call verified; recommend optional lazy getter for env variables

Confirmed that dotenv.load(fileName: ".env") is invoked in lib/main.dart at line 29, so your .env is loaded during app initialization.

• lib/main.dart:29 – await dotenv.load(fileName: ".env"); ensures env vars are available before runApp.
• lib/utils/constants/contract_abis/tree_nft_contract_abi.dart – you still read from dotenv.env at the top level; switching to a lazy getter will defer access until runtime (after load) and allow you to validate the value.

Example refactor:

--- a/lib/utils/constants/contract_abis/tree_nft_contract_abi.dart
+++ b/lib/utils/constants/contract_abis/tree_nft_contract_abi.dart
-// top-level constant (evaluated at import)
- final treeNftContractAbi = dotenv.env['TREE_NFT_CONTRACT_ABI']!;
+// lazy getter defers access until after dotenv.load and validates presence
+ String get treeNftContractAbi {
+   final abi = dotenv.env['TREE_NFT_CONTRACT_ABI'];
+   if (abi == null || abi.isEmpty) {
+     throw StateError('TREE_NFT_CONTRACT_ABI is missing or empty in .env');
+   }
+   return abi;
+ }
lib/widgets/profile_widgets/profile_section_widget.dart (2)

49-51: Correct log context and severity for parse failures

It’s profile data, not tree data; and parse failures should be logged as errors.

-      logger.d("Error parsing Tree data: $e");
-      logger.d("Data received: $data");
-      logger.d("Data type: ${data.runtimeType}");
+      logger.e("Error parsing User profile data", error: e);
+      logger.d("Profile raw data: $data");
+      logger.d("Profile data runtimeType: ${data.runtimeType}");

247-355: UI simplification is fine; consider a small helper to remove duplication and add consts

The four nearly identical tiles can be rendered via a small builder (less repetition, easier theming). Also, most BoxShadow/Offset/Text can be const.

Example helper (outside selected range):

Widget _tokenTile({
  required String label,
  required int value,
  required Color color,
}) {
  return Padding(
    padding: const EdgeInsets.all(4.0),
    child: SizedBox(
      height: 40,
      width: 150,
      child: DecoratedBox(
        decoration: BoxDecoration(
          color: color,
          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')),
      ),
    ),
  );
}

Then call it for each token.

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

341-378: Expanded inside Column can assert in unbounded-height contexts

Returning a Column with an inner Expanded can throw “RenderFlex children have non-zero flex but incoming height constraints are unbounded” when this widget is placed in scrollables or other unconstrained parents.

Options:

  • Let the parent control flex: expose the list as a widget without Expanded.
  • Or detect constraints and fall back to a fixed-height SizedBox when unbounded.
  • Or convert to a CustomScrollView with a SliverAppBar-like header and a SliverList body.

If helpful, I can prepare a patch to convert the body into a SliverList.


439-446: Infinite scroll trigger uses equality; prefer a threshold

Exact equality on pixels == maxScrollExtent can miss triggers due to floating-point rounding and momentum scrolling.

Apply this diff:

-          if (!_isLoading &&
-              _hasMore &&
-              scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent) {
+          if (_hasMore &&
+              !_isLoading &&
+              scrollInfo.metrics.pixels >=
+                  scrollInfo.metrics.maxScrollExtent - 96) {
             _loadNFTs(loadMore: true);
           }
lib/widgets/wallet_not_connected_widget.dart (1)

29-30: Wire-up looks good; consider awaiting and surfacing errors to users

If connectWallet() is async/fallible, make the handler async, await it, and show feedback on failure.

Apply this diff:

-        ElevatedButton(
-          onPressed: () {
+        ElevatedButton(
+          onPressed: () async {
             final walletProvider =
                 Provider.of<WalletProvider>(context, listen: false);
-            walletProvider.connectWallet();
+            try {
+              await walletProvider.connectWallet();
+            } catch (e) {
+              ScaffoldMessenger.of(context).showSnackBar(
+                SnackBar(
+                  content: Text('Failed to connect wallet: $e'),
+                  backgroundColor: Colors.red,
+                ),
+              );
+            }
           },
lib/pages/mint_nft/mint_nft_coordinates.dart (1)

621-657: Dead code: _buildPreviewSection() is defined but unused

Keeping unused UI code drifts with future changes and increases maintenance.

Apply this diff to remove the block (or wire it into the page under the main content):

-  // 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(),
-      ],
-    );
-  }
lib/pages/mint_nft/mint_nft_details.dart (1)

209-226: Minor: button shadow color equals the fill

Using the same green for shadowColor as the button’s backgroundColor reduces perceived elevation. Consider a darker variant or semi-transparent black for depth.

Example:

-  shadowColor: const Color(0xFF1CD381),
+  shadowColor: Colors.black.withOpacity(0.15),
lib/widgets/map_widgets/flutter_map_widget.dart (11)

35-37: Harden against invalid provider coordinates (NaN/∞) and out-of-range values

Provider outputs are used directly with no sanitation, unlike StaticDisplayMap. This can crash LatLng or render off-world positions.

Apply minimal in-place sanitation:

-        final double latitude = provider.getLatitude();
-        final double longitude = provider.getLongitude();
+        final latRaw = provider.getLatitude();
+        final lngRaw = provider.getLongitude();
+        final double latitude = (latRaw.isFinite ? latRaw : 0.0).clamp(-90.0, 90.0).toDouble();
+        final double longitude = (lngRaw.isFinite ? lngRaw : 0.0).clamp(-180.0, 180.0).toDouble();

Optional: mirror the _sanitizeCoordinate helper from StaticDisplayMap (or extract to a shared util) for consistency.


104-109: initialZoom is below minZoom; may be clamped or cause jank

You set initialZoom: 1.0 with minZoom: 3.0. Some flutter_map versions clamp silently; others may jump on first frame.

Recommend aligning them:

-            initialZoom: 1.0,
+            initialZoom: 3.0,
             minZoom: 3.0,

131-137: Avoid flipping the entire widget to an error state on a single tile failure

Setting _hasError = true inside errorTileCallback hides the map if any one tile fails. Transient tile failures are common; flutter_map already displays an error tile.

Consider logging and allowing the map to continue:

-              errorTileCallback: (tile, error, stackTrace) {
-                if (mounted) {
-                  setState(() {
-                    _hasError = true;
-                  });
-                }
-              },
+              errorTileCallback: (tile, error, stackTrace) {
+                logger.w('Tile load failed: $error');
+              },

If you want a hard fail, gate it behind a retry threshold or a timeout rather than immediate flip.


157-168: Loading overlay color: consider theme surface for dark mode

Solid Colors.white can be jarring in dark theme.

-          Container(
-            color: Colors.white,
+          Container(
+            color: Theme.of(context).colorScheme.surface,

175-177: Coordinate badge background: prefer toned black or themed surface with opacity

Solid black can dominate; Colors.black87 or a themed surface with slight alpha improves readability over tiles.

-              color: Colors.black,
+              color: Colors.black87,

198-201: Shadow is fully black; soften for a more natural elevation

Pure black shadows can look harsh; tone it down for material consistency.

-                      color: Colors.black,
+                      color: Colors.black.withOpacity(0.25),

277-289: Info banner color: consider Theme color for consistency and accessibility

Solid Colors.blue may clash with app theming. Using colorScheme.primary keeps contrast predictable across themes.

-              color: Colors.blue,
+              color: Theme.of(context).colorScheme.primary,

471-482: Static map loading overlay: same dark-mode note as dynamic map

Mirror the themed surface for consistency across both widgets.

-          Container(
-            color: Colors.white,
+          Container(
+            color: Theme.of(context).colorScheme.surface,

489-491: Static badge background: same readability/theming nit

Match the earlier suggestion for toned black or themed surface.

-              color: Colors.black,
+              color: Colors.black87,

512-515: Static map shadow: soften for consistency

Same rationale as the other panel.

-                      color: Colors.black,
+                      color: Colors.black.withOpacity(0.25),

591-603: Static info banner: prefer themed primary

Same theming nit as dynamic widget; improves cohesion across the app.

-              color: Colors.blue,
+              color: Theme.of(context).colorScheme.primary,
📜 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 874e6fb and ec5e3e4.

📒 Files selected for processing (22)
  • .github/workflows/flutter.yaml (1 hunks)
  • lib/components/universal_navbar.dart (7 hunks)
  • lib/components/wallet_connect_dialog.dart (2 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/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 (2 hunks)
  • lib/utils/services/contract_read_services.dart (4 hunks)
  • lib/utils/services/contract_write_functions.dart (2 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/nft_display_utils/tree_nft_view_details_with_map.dart (2 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/utils/constants/contractDetails.dart
  • lib/pages/home_page.dart
  • lib/providers/mint_nft_provider.dart
✅ Files skipped from review due to trivial changes (2)
  • lib/providers/wallet_provider.dart
  • lib/pages/register_user_page.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)

⏰ 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 (15)
lib/utils/services/switch_chain_utils.dart (1)

25-57: Nice micro-optimization: spreading the mapped Iterable avoids an extra allocation.

Using ...walletProvider.getSupportedChains().map(...) directly is cleaner and saves a toList() call. Looks good.

lib/components/universal_navbar.dart (5)

36-39: LGTM: Using SizedBox for the bottom plant strip is appropriate

The swap to SizedBox tightens layout intent and avoids a decoration wrapper where none is needed.


154-161: LGTM: Solid colors and clear border improve contrast

The move to opaque beige and solid black border makes the strip crisper; decoration remains simple.


185-201: LGTM: Per-plant wrappers simplified to SizedBox

Keeps constraints explicit and reduces widget depth. Asset path handling looks consistent.


243-252: LGTM: Wallet menu surface colors updated

Solid white background, green border, and solid black shadow are fine; consistent with the rest of the visual refresh.


368-377: LGTM: Connect button surface consistency

White background + green border/shadow matches the wallet menu styling; good cohesion.

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

108-111: Constructor modernization to const UserNftsWidget({super.key, ...}) — LGTM

Matches Flutter best practices and keeps API stable.

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

9-10: Import path correction — LGTM

Rename aligns with the new widget location.

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

104-112: Gradient/shadow solid-color update — LGTM

Consistent with the broader PR’s move away from opacity.


339-360: Preview chip solid color — LGTM

Good contrast and consistent with the rest of the styling.

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

222-231: Switch to SizedBox for icon hit target is a nice cleanup

Using SizedBox(40x40) keeps the touch target clear without extra decoration. LGTM.


253-262: Likewise for the minus button: LGTM

Consistent touch target sizing and no unnecessary Container. Good.


307-310: Constructor modernization with super.key and explicit fields looks good

Public API stays the same while using idiomatic super.key. No issues.


536-544: SizedBox touch target: LGTM

Good consistency and accessibility for the zoom-in control.


567-575: SizedBox touch target: LGTM

Matches the zoom-in pattern; clear and consistent.

Comment on lines 34 to 35
- name: Flutter build (web example)
run: flutter build web
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Heads-up: Web build may fail if any imported code uses dart:io. Verify import graph.

lib/utils/services/ipfs_services.dart imports dart:io. If it’s in the web import graph, flutter build web will fail.

Run this to confirm whether dart:io-importing files are in the web graph:


🏁 Script executed:

#!/bin/bash
# Find files importing dart:io
rg -nP -C2 "^\s*import\s+'dart:io';" lib || true
# Show who imports them (first level)
fd . lib -t f -e dart -x rg -nP -C1 "import .+ipfs_services\.dart" {}

Length of output: 1262


🏁 Script executed:

#!/bin/bash
# Find who imports mint_nft_images.dart
rg -nP -C1 "import .+mint_nft_images\.dart" lib || true
# Find who imports register_user_page.dart
rg -nP -C1 "import .+register_user_page\.dart" lib || true

Length of output: 645


🏁 Script executed:

#!/bin/bash
# List all files importing ipfs_services.dart (show filenames and matched lines)
rg -nP --with-filename "import .*ipfs_services\.dart" lib

Length of output: 287


Action Required: Remove or Guard dart:io Imports to Enable Web Build

The import graph for your web example includes dart:io (which isn’t supported on the web), causing flutter build web to fail. Specifically:

  • lib/utils/services/ipfs_services.dart imports dart:io.
  • That service is imported by:
    • lib/pages/register_user_page.dart
    • lib/pages/mint_nft/mint_nft_images.dart
  • Both pages are pulled in by the app’s entry point:
    • lib/main.dart imports register_user_page.dart and mint_nft_images.dart

Next steps:

• Refactor ipfs_services.dart to use conditional imports, splitting out dart:io–dependent code from a pure-Dart (web) implementation.
Example:

// lib/utils/services/ipfs_services.dart
export 'ipfs_services_mobile.dart' if (dart.library.html) 'ipfs_services_web.dart';

• Create:

  • ipfs_services_mobile.dart – retains the dart:io imports for mobile/desktop
  • ipfs_services_web.dart – uses http or js interop without dart:io
    • Verify that neither register_user_page.dart nor mint_nft_images.dart directly references dart:io; they should only depend on the abstracted service API.

Implementing these changes will allow flutter build web to succeed without removing your existing mobile/desktop functionality.

🤖 Prompt for AI Agents
.github/workflows/flutter.yaml around lines 34-35: the web build fails because
lib/utils/services/ipfs_services.dart imports dart:io (unsupported on web);
refactor ipfs_services.dart into a conditional-export facade and two
platform-specific implementations (one for mobile/desktop that keeps dart:io
usage, e.g., ipfs_services_mobile.dart, and one for web without dart:io, e.g.,
ipfs_services_web.dart using http/js interop), update the facade to use the
conditional export (export 'ipfs_services_mobile.dart' if (dart.library.html)
'ipfs_services_web.dart';), ensure register_user_page.dart and
mint_nft_images.dart import only the facade and do not directly import dart:io,
and run flutter build web to verify the issue is resolved.

Comment on lines +269 to 272
SizedBox(
width: 10,
child: Flexible(
child: Text(
Copy link

@coderabbitai coderabbitai bot Aug 23, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

Critical: Flexible cannot be a child of SizedBox (will throw “Incorrect use of ParentDataWidget”)

Flexible must be a direct descendant of a Flex (Row/Column). Nesting it inside SizedBox will cause a runtime error. Also, width: 10 is too narrow to display the formatted address.

Fix by removing SizedBox and letting Flexible handle truncation, or keep only SizedBox:

-              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,
-                  ),
-                ),
-              ),
+                ),
+              ),

If you want a fixed cap, you can alternatively use:

// Outside selected range (illustrative)
SizedBox(
  width: 100,
  child: Text(formatAddress(...), overflow: TextOverflow.ellipsis, style: ...),
)
🤖 Prompt for AI Agents
In lib/components/universal_navbar.dart around lines 269 to 272, a Flexible is
nested inside a SizedBox (width: 10) which will throw "Incorrect use of
ParentDataWidget" because Flexible must be a direct child of a Flex and the
fixed width is far too small; fix by removing the SizedBox and using Flexible
(or Expanded) directly as a child of the surrounding Row/Column, ensure the Text
uses overflow: TextOverflow.ellipsis and maxLines: 1 so it truncates properly,
or if you need a fixed cap replace Flexible with a SizedBox with a sensible
width (e.g., ~100) and put the Text inside that SizedBox with overflow handling.

Copy link
Contributor

Choose a reason for hiding this comment

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

@IronJam11 Please look into this, check if there are any errors in running of this widget

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @CodeRabbit in a new comment, and I'll come hopping out!

Comment on lines +43 to 49
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
} catch (e) {
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context).showSnackBar(
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

❓ Verification inconclusive

Don’t suppress use_build_context_synchronously; guard with context.mounted

Ignoring the lint risks using a disposed context after await. Check context.mounted (Flutter ≥3.7) before Navigator.pop and showing SnackBars.

Apply this diff:

                     onPressed: () async {
                       try {
                         await walletProvider.openWallet(wallet, uri);
-                        // ignore: use_build_context_synchronously
-                        Navigator.of(context).pop();
+                        if (!context.mounted) return;
+                        Navigator.of(context).pop();
                       } catch (e) {
-                        // ignore: use_build_context_synchronously
-                        Navigator.of(context).pop();
-                        // ignore: use_build_context_synchronously
-                        ScaffoldMessenger.of(context).showSnackBar(
+                        if (!context.mounted) return;
+                        Navigator.of(context).pop();
+                        ScaffoldMessenger.of(context).showSnackBar(
                           SnackBar(
                             content: Text(e.toString()),
                             backgroundColor: Colors.red,
                           ),
                         );
                       }
                     },
@@
                   onPressed: () async {
                     await Clipboard.setData(ClipboardData(text: uri));
-                    // ignore: use_build_context_synchronously
-                    Navigator.of(context).pop();
-                    // ignore: use_build_context_synchronously
-                    ScaffoldMessenger.of(context).showSnackBar(
+                    if (!context.mounted) return;
+                    Navigator.of(context).pop();
+                    ScaffoldMessenger.of(context).showSnackBar(
                       const SnackBar(
                         content: Text('URI copied to clipboard!'),
                         backgroundColor: Colors.green,
                       ),
                     );
                   },

If you support older Flutter without context.mounted, capture final nav = Navigator.of(context); final messenger = ScaffoldMessenger.of(context); before await and use those locals afterward.

Also applies to: 82-89


Guard against disposed BuildContext instead of suppressing the lint

Ignoring use_build_context_synchronously can lead to exceptions if the widget’s BuildContext is unmounted by the time an awaited call completes. Replace the // ignore: use_build_context_synchronously comments with proper guards:

• Affected locations:
– lib/components/wallet_connect_dialog.dart, onPressed handler (~lines 43–49)
– lib/components/wallet_connect_dialog.dart, URI-copy handler (~lines 82–89)

For Flutter ≥ 3.7 (with context.mounted):

onPressed: () async {
  try {
    await walletProvider.openWallet(wallet, uri);
-   // ignore: use_build_context_synchronously
-   Navigator.of(context).pop();
+   // avoid using a disposed context
+   if (!context.mounted) return;
+   Navigator.of(context).pop();
  } catch (e) {
-   // ignore: use_build_context_synchronously
-   Navigator.of(context).pop();
-   // ignore: use_build_context_synchronously
-   ScaffoldMessenger.of(context).showSnackBar(
+   if (!context.mounted) return;
+   Navigator.of(context).pop();
+   ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(e.toString()),
        backgroundColor: Colors.red,
      ),
    );
  }
},
…
onPressed: () async {
  await Clipboard.setData(ClipboardData(text: uri));
- // ignore: use_build_context_synchronously
- Navigator.of(context).pop();
- // ignore: use_build_context_synchronously
- ScaffoldMessenger.of(context).showSnackBar(
+ if (!context.mounted) return;
+ Navigator.of(context).pop();
+ ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(
      content: Text('URI copied to clipboard!'),
      backgroundColor: Colors.green,
    ),
  );
},

Fallback for pre-3.7 Flutter (no context.mounted):
Capture NavigatorState and ScaffoldMessengerState before the await, then use those locals afterward:

onPressed: () async {
  final nav = Navigator.of(context);
  final messenger = ScaffoldMessenger.of(context);
  try {
    await walletProvider.openWallet(wallet, uri);
    nav.pop();
  } catch (e) {
    nav.pop();
    messenger.showSnackBar(
      SnackBar(
        content: Text(e.toString()),
        backgroundColor: Colors.red,
      ),
    );
  }
},
…
onPressed: () async {
  final nav = Navigator.of(context);
  final messenger = ScaffoldMessenger.of(context);
  await Clipboard.setData(ClipboardData(text: uri));
  nav.pop();
  messenger.showSnackBar(
    const SnackBar(
      content: Text('URI copied to clipboard!'),
      backgroundColor: Colors.green,
    ),
  );
},
🤖 Prompt for AI Agents
In lib/components/wallet_connect_dialog.dart around lines 43–49 and 82–89, the
code suppresses the use_build_context_synchronously lint instead of guarding
against a possibly disposed BuildContext; update both async handlers to avoid
using context after await by either (1) if using Flutter ≥3.7, check
context.mounted before calling Navigator.of(context).pop() and
ScaffoldMessenger.of(context).showSnackBar(...), or (2) for older Flutter,
capture NavigatorState and ScaffoldMessengerState into local variables before
any await (e.g., final nav = Navigator.of(context); final messenger =
ScaffoldMessenger.of(context);) and then use those locals after the await to
call nav.pop() and messenger.showSnackBar(...); remove the ignore comments and
ensure all code paths (success and catch) use the guarded approach so no context
is accessed when unmounted.

import 'package:flutter_dotenv/flutter_dotenv.dart';

final String ALCHEMY_API_KEY = dotenv.env['ALCHEMY_API_KEY'] ?? '';
final String alchemyApiKey = dotenv.env['ALCHEMY_API_KEY'] ?? '';
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Top-level dotenv read at import time can initialize to empty before dotenv.load() runs.

Dart evaluates top-level initializers before main() (where dotenv.load() typically happens). This risks blank RPC URLs in production.

Apply this diff to make the API key lookup lazy (safe even if dotenv loads later):

-import 'package:flutter/material.dart';
-import 'package:flutter_dotenv/flutter_dotenv.dart';
-
-final String alchemyApiKey = dotenv.env['ALCHEMY_API_KEY'] ?? '';
+import 'package:flutter/material.dart';
+import 'package:flutter_dotenv/flutter_dotenv.dart';
+
+String _alchemyApiKey() => dotenv.maybeGet('ALCHEMY_API_KEY') ?? '';

And convert rpcUrls/chainInfoList to getters so the key is resolved at access time:

-final Map<String, String> rpcUrls = {
-  '11155111': 'https://eth-sepolia.g.alchemy.com/v2/$alchemyApiKey',
-  '1': 'https://eth-mainnet.g.alchemy.com/v2/$alchemyApiKey',
-};
+Map<String, String> get rpcUrls => {
+  '11155111': 'https://eth-sepolia.g.alchemy.com/v2/${_alchemyApiKey()}',
+  '1': 'https://eth-mainnet.g.alchemy.com/v2/${_alchemyApiKey()}',
+};
@@
-final Map<String, Map<String, dynamic>> chainInfoList = {
+Map<String, Map<String, dynamic>> get chainInfoList => {
   '1': {
     'name': 'Ethereum Mainnet',
-    'rpcUrl': 'https://eth-mainnet.g.alchemy.com/v2/$alchemyApiKey',
+    'rpcUrl': 'https://eth-mainnet.g.alchemy.com/v2/${_alchemyApiKey()}',
@@
   '11155111': {
     'name': 'Sepolia Testnet',
-    'rpcUrl': 'https://eth-sepolia.g.alchemy.com/v2/$alchemyApiKey',
+    'rpcUrl': 'https://eth-sepolia.g.alchemy.com/v2/${_alchemyApiKey()}',

Optionally, fail fast in debug if the key is missing where you first consume these maps.

🤖 Prompt for AI Agents
In lib/models/wallet_chain_option.dart around line 4, the file reads
dotenv.env['ALCHEMY_API_KEY'] at import time which can evaluate to empty before
dotenv.load() runs; change the top-level alchemyApiKey to a getter that reads
dotenv.env['ALCHEMY_API_KEY'] on access, and convert rpcUrls and chainInfoList
to getters so they compute their maps using the runtime value of the API key;
update any code that depended on the old top-level fields to access the getters,
and optionally add a debug-only assertion or throw where these getters are first
consumed to fail fast if the key is missing.

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.

🛠️ Refactor suggestion

⚠️ Potential issue

Stop ignoring use_build_context_synchronously; capture provider before awaiting and add mounted guards

Using context after an await can crash if the widget unmounts meanwhile. Don’t suppress the lint; fix the flow by capturing the provider before the first await and guarding UI updates with mounted.

Apply this small in-range cleanup first:

-      // ignore: use_build_context_synchronously
-      final provider = Provider.of<MintNftProvider>(context, listen: false);
+      // Provider captured earlier (see revised function below).

Then revise the function to safely capture the provider and avoid post-await context usage:

Future<void> _pickAndUploadImages() async {
  // capture before any await
  final provider = context.read<MintNftProvider>();

  try {
    final List<XFile> images = await _picker.pickMultiImage();
    if (images.isEmpty) return;

    if (!mounted) return;
    setState(() {
      _processingImages = images.map((image) => File(image.path)).toList();
      _isUploading = true;
    });

    final List<String> newHashes = [];
    for (int i = 0; i < images.length; i++) {
      if (!mounted) return;
      setState(() => _uploadingIndex = i);

      try {
        final File imageFile = File(images[i].path);
        final String? hash = await uploadToIPFS(imageFile, (isUploading) {});
        if (!mounted) return;

        if (hash != null) {
          newHashes.add(hash);
          setState(() => _uploadedHashes.add(hash));
          logger.d('Successfully uploaded image ${i + 1}: $hash');
        } else {
          _showSnackBar('Failed to upload image ${i + 1}');
        }
      } catch (e) {
        logger.e('Error uploading image ${i + 1}: $e');
        if (mounted) _showSnackBar('Error uploading image ${i + 1}: $e');
      }
    }

    if (!mounted) return;
    setState(() {
      _isUploading = false;
      _uploadingIndex = -1;
      _processingImages.clear();
    });
    provider.setInitialPhotos(_uploadedHashes);

    if (newHashes.isNotEmpty && mounted) {
      _showSnackBar('Successfully uploaded ${newHashes.length} images');
    }
  } catch (e) {
    if (!mounted) return;
    setState(() {
      _isUploading = false;
      _uploadingIndex = -1;
      _processingImages.clear();
    });
    if (mounted) _showSnackBar('Error selecting images: $e');
  }
}

Note: context.read() is equivalent to Provider.of(context, listen: false) but clearer.

🤖 Prompt for AI Agents
In lib/pages/mint_nft/mint_nft_images.dart around lines 46–48, the code
currently ignores the use_build_context_synchronously lint by calling
Provider.of<MintNftProvider>(context, listen: false) after an await; capture the
provider synchronously before any awaits and replace with
context.read<MintNftProvider>() (or store Provider.of(...) immediately), then
add mounted checks after each await before calling setState, updating widgets,
or using context (return early if !mounted); ensure all setState calls and UI
notifications (_showSnackBar, provider.setInitialPhotos, etc.) occur only when
mounted and reset _isUploading/_uploadingIndex/_processingImages in both success
and error paths.

Comment on lines +6 to +7
String apiKey = dotenv.get('API_KEY', fallback: "");
String apiSecret = dotenv.get('API_SECRET', 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

Top-level dotenv reads will be empty if dotenv.load() runs after imports.

These variables initialize before main() and will stay "", breaking Pinata auth.

Apply this diff to compute keys at call-time (safe for both app and CI):

-import 'package:flutter_dotenv/flutter_dotenv.dart';
-
-String apiKey = dotenv.get('API_KEY', fallback: "");
-String apiSecret = dotenv.get('API_SECRET', fallback: "");
+import 'package:flutter_dotenv/flutter_dotenv.dart';
+
+String get apiKey => dotenv.maybeGet('API_KEY') ?? '';
+String get apiSecret => dotenv.maybeGet('API_SECRET') ?? '';
📝 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
String apiKey = dotenv.get('API_KEY', fallback: "");
String apiSecret = dotenv.get('API_SECRET', fallback: "");
import 'package:flutter_dotenv/flutter_dotenv.dart';
String get apiKey => dotenv.maybeGet('API_KEY') ?? '';
String get apiSecret => dotenv.maybeGet('API_SECRET') ?? '';
🤖 Prompt for AI Agents
In lib/utils/services/ipfs_services.dart around lines 6 to 7, the top-level
reads dotenv.get('API_KEY') and dotenv.get('API_SECRET') run at import time and
will be empty if dotenv.load() runs later; change to read the env vars at
call-time (e.g., inside the functions that build Pinata auth headers or via a
small getter that calls dotenv.get on each invocation) so keys are retrieved
after dotenv.load(); also consider validating/throwing if values are still empty
to fail fast in CI.

Comment on lines 11 to 16
const CoordinatesMap(
{Key? key,
{super.key,
this.onLocationSelected,
required double lat,
required double lng})
: super(key: key);
required double 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

Remove or use the unused required lat/lng in CoordinatesMap constructor

lat and lng are required positional params but not stored or used anywhere in CoordinatesMap. This creates a confusing public API and forces callers to pass values that are ignored.

Two safe options:

  • Option A (prefer): Remove these params entirely if the widget is purely provider-driven.
-  const CoordinatesMap(
-      {super.key,
-      this.onLocationSelected,
-      required double lat,
-      required double lng});
+  const CoordinatesMap({
+    super.key,
+    this.onLocationSelected,
+  });
  • Option B (fallback-friendly, non-breaking): Keep them optional and store as fields to use as a fallback if provider yields invalid coordinates.
 class CoordinatesMap extends StatefulWidget {
   final Function(double lat, double lng)? onLocationSelected;
+  final double? lat;
+  final double? lng;

-  const CoordinatesMap(
-      {super.key,
-      this.onLocationSelected,
-      required double lat,
-      required double lng});
+  const CoordinatesMap({
+    super.key,
+    this.onLocationSelected,
+    this.lat,
+    this.lng,
+  });

If you choose Option B, I can propose a small update in build() to fall back to widget.lat/lng when provider returns NaN/∞. Want me to wire that up?

📝 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
const CoordinatesMap(
{Key? key,
{super.key,
this.onLocationSelected,
required double lat,
required double lng})
: super(key: key);
required double lng});
const CoordinatesMap({
super.key,
this.onLocationSelected,
});
🤖 Prompt for AI Agents
In lib/widgets/map_widgets/flutter_map_widget.dart around lines 11–16, the
constructor declares required double lat and required double lng but never
stores or uses them; remove these unused parameters from the public API
(preferred) by deleting them from the constructor and updating any callers, or
if you need non-breaking fallback behavior, convert them to nullable final
fields (final double? lat, final double? lng), make the constructor accept
optional named params and assign them, then update build() to use
widget.lat/widget.lng as a fallback when the provider returns invalid
coordinates (NaN/∞); also update any documentation/comments and unit tests/call
sites accordingly.

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

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove incorrect unused_field ignores and actually use pagination state

_currentPage is used below; _itemsPerPage should be used to drive the paginated fetch. Drop the ignores and wire these fields into the API call.

Apply this diff:

-  // ignore: unused_field
   int _currentPage = 0;
-  // ignore: unused_field
-  final int _itemsPerPage = 10;
+  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 _currentPage = 0;
final int _itemsPerPage = 10;
🤖 Prompt for AI Agents
In lib/widgets/nft_display_utils/user_nfts_widget.dart around lines 121 to 124,
remove the incorrect // ignore: unused_field comments and wire the pagination
state into the API call: stop ignoring _currentPage and _itemsPerPage, use
_itemsPerPage as the page size when requesting data (pass it as limit/size or
per_page parameter) and compute the offset or page index from _currentPage
(e.g., offset = _currentPage * _itemsPerPage or pass _currentPage directly if
API expects page number). Also ensure any UI pagination controls
increment/decrement _currentPage inside setState and trigger a refetch using the
updated page and _itemsPerPage values so pagination actually drives the
requests.

Comment on lines +37 to +39
name: actualData[2].toString(),
userAddress: actualData[0].toString(),
profilePhotoIpfs: actualData[1].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

Avoid "null" showing up in UI and malformed image URLs — restore null coalescing

toString() on null yields the literal "null", which will (a) display as a name "null" and (b) try to load an image from "null". Use null-aware defaults.

-        name: actualData[2].toString(),
-        userAddress: actualData[0].toString(),
-        profilePhotoIpfs: actualData[1].toString(),
+        name: (actualData[2] == null) ? '' : actualData[2].toString(),
+        userAddress: (actualData[0] == null) ? '' : actualData[0].toString(),
+        profilePhotoIpfs: (actualData[1] == null) ? '' : actualData[1].toString(),
📝 Committable suggestion

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

Suggested change
name: actualData[2].toString(),
userAddress: actualData[0].toString(),
profilePhotoIpfs: actualData[1].toString(),
name: (actualData[2] == null) ? '' : actualData[2].toString(),
userAddress: (actualData[0] == null) ? '' : actualData[0].toString(),
profilePhotoIpfs: (actualData[1] == null) ? '' : actualData[1].toString(),
🤖 Prompt for AI Agents
In lib/widgets/profile_widgets/profile_section_widget.dart around lines 37 to
39, the code calls toString() directly on possibly-null actualData elements
which produces the literal "null" (showing in UI and creating malformed image
URLs); replace those with null-aware expressions such as
actualData[2]?.toString() ?? '' for name, actualData[0]?.toString() ?? '' for
userAddress, and actualData[1]?.toString() ?? '' (or null/empty as appropriate
for profilePhotoIpfs) so that nulls are coalesced to safe defaults and no "null"
string is rendered or used as an image URL.

);
}

// ignore: unused_element
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Don’t suppress unused error-view — actually show it to avoid a crash when profile load fails

Right now, on any non-“not registered” error, build still renders the profile/tiles, dereferencing _userProfileData! and risking a runtime crash. Use _buildErrorState() instead of suppressing it.

Minimal integration (outside selected range):

// In build(), replace the ternary with an error-aware branch
child: _isLoading
    ? _buildLoadingState()
    : _isNotRegistered
        ? _buildNotRegisteredState()
        : (_errorMessage != null && _errorMessage!.isNotEmpty)
            ? _buildErrorState()
            : Row(
                // current overview + tokens
              ),

Once used, remove the // ignore: unused_element.

🤖 Prompt for AI Agents
In lib/widgets/profile_widgets/profile_section_widget.dart around line 358, the
code currently suppresses an unused-element warning and still renders the
profile UI even when a non-"not registered" load error occurred, which
dereferences _userProfileData! and can crash; update the build logic to check
_errorMessage (or equivalent error flag) and call _buildErrorState() when an
error exists instead of rendering the profile overview, ensure the profile
widgets only render when _userProfileData is non-null, and remove the
now-unnecessary “// ignore: unused_element” comment for the unused error-view
helper.

@IronJam11 IronJam11 force-pushed the aaryan-githubWorkflow branch from 9f81283 to 1cd1feb Compare August 23, 2025 23:34
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: 3

Caution

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

⚠️ Outside diff range comments (1)
lib/widgets/map_widgets/flutter_map_widget.dart (1)

105-109: Fix invalid initialZoom/minZoom combination

initialZoom: 1.0 while minZoom: 3.0 is contradictory; the map will clamp or behave unexpectedly on first paint. Make initialZoom >= minZoom (or lower minZoom).

-            initialZoom: 1.0,
-            minZoom: 3.0,
+            initialZoom: 3.0,
+            minZoom: 3.0,
♻️ Duplicate comments (13)
.env.stencil (1)

6-6: Ensure APPLICATION_ID is actually used and documented (duplicate from earlier review).

APPLICATION_ID is templated but I can’t confirm it’s read at runtime or mentioned in README/Getting Started.

Run this to verify usage and docs:

#!/bin/bash
# Search app code for APPLICATION_ID access and env loading
rg -nCI3 --glob '!**/build/**' \
  -e '\bAPPLICATION_ID\b' \
  -e 'flutter_dotenv|dotenv|--dart-define'

# Check README for onboarding instructions
rg -n -C2 'APPLICATION_ID|\.env' README.md || true

If unused, remove it from .env.stencil; if used, document: copy .env.stencil -> .env and set APPLICATION_ID.

.github/workflows/flutter.yaml (2)

15-16: Upgrade checkout to v4 for current runners and Node 20 compatibility.

Apply:

-      - name: Checkout repository
-        uses: actions/checkout@v3
+      - name: Checkout repository
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 0

28-33: Add a Flutter test step to CI.

You already analyze and format; run tests to catch regressions.

Apply:

       - name: Dart analyze
         run: flutter analyze

+      - name: Run Flutter tests
+        run: flutter test -r expanded
+
       - name: Check formatting
         run: dart format --output=none --set-exit-if-changed .
lib/utils/services/contract_read_services.dart (4)

1-5: Add missing web3dart import for EthereumAddress (compile error).

EthereumAddress is defined in web3dart, not in walletconnect_flutter_v2. This file won’t compile without the import.

 import 'package:tree_planting_protocol/utils/constants/contract_abis/tree_nft_contract_abi.dart';
-import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
+import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
+import 'package:web3dart/credentials.dart';

70-76: Guard against unset/malformed contract address and use consistent casing.

Add a pre-call check for the contract address and standardize the identifier to camelCase treeNftContractAddress. This prevents confusing runtime failures when the env value is missing/invalid. Also aligns with Dart naming style.

       final List<dynamic> args = [
         userAddress,
         BigInt.from(offset),
         BigInt.from(limit),
       ];
 
-      final result = await walletProvider.readContract(
-        contractAddress: treeNFtContractAddress,
+      // Validate configured contract address
+      if (treeNftContractAddress.isEmpty ||
+          !RegExp(r'^0x[a-fA-F0-9]{40}$').hasMatch(treeNftContractAddress)) {
+        return ContractReadResult.error(
+          errorMessage:
+              'Invalid contract address configured. Set TREE_NFT_CONTRACT_ADDRESS.',
+        );
+      }
+      final result = await walletProvider.readContract(
+        contractAddress: treeNftContractAddress,
         functionName: 'getNFTsByUserPaginated',
         params: args,
         abi: treeNftContractABI,
       );

If the source constant is currently named treeNFtContractAddress, please rename it at the declaration site too for consistency and update all callsites.


118-123: Repeat: validate contract address and fix identifier casing in ping().

-      final result = await walletProvider.readContract(
-        contractAddress: treeNFtContractAddress,
+      if (treeNftContractAddress.isEmpty ||
+          !RegExp(r'^0x[a-fA-F0-9]{40}$').hasMatch(treeNftContractAddress)) {
+        return ContractReadResult.error(
+          errorMessage:
+              'Invalid contract address configured. Set TREE_NFT_CONTRACT_ADDRESS.',
+        );
+      }
+      final result = await walletProvider.readContract(
+        contractAddress: treeNftContractAddress,
         functionName: 'ping',
-        abi: treeNftContractABI,
+        abi: treeNftContractABI,
         params: [],
       );

174-178: Validate contract address and standardize casing in getProfileDetails().

-      final result = await walletProvider.readContract(
-        contractAddress: treeNFtContractAddress,
+      if (treeNftContractAddress.isEmpty ||
+          !RegExp(r'^0x[a-fA-F0-9]{40}$').hasMatch(treeNftContractAddress)) {
+        return ContractReadResult.error(
+          errorMessage:
+              'Invalid contract address configured. Set TREE_NFT_CONTRACT_ADDRESS.',
+        );
+      }
+      final result = await walletProvider.readContract(
+        contractAddress: treeNftContractAddress,
         functionName: 'getUserProfile',
         abi: treeNftContractABI,
         params: args,
       );
lib/models/wallet_chain_option.dart (3)

37-58: Same issue for chainInfoList: compute lazily to avoid blank RPC URLs.

-final Map<String, Map<String, dynamic>> chainInfoList = {
+Map<String, Map<String, dynamic>> get chainInfoList => {
   '1': {
     'name': 'Ethereum Mainnet',
-    'rpcUrl': 'https://eth-mainnet.g.alchemy.com/v2/$alchemyApiKey',
+    'rpcUrl': 'https://eth-mainnet.g.alchemy.com/v2/${_alchemyApiKey()}',
     'nativeCurrency': {
       'name': 'Ether',
       'symbol': 'ETH',
       'decimals': 18,
     },
     'blockExplorerUrl': 'https://etherscan.io',
   },
   '11155111': {
     'name': 'Sepolia Testnet',
-    'rpcUrl': 'https://eth-sepolia.g.alchemy.com/v2/$alchemyApiKey',
+    'rpcUrl': 'https://eth-sepolia.g.alchemy.com/v2/${_alchemyApiKey()}',
     'nativeCurrency': {
       'name': 'Sepolia Ether',
       'symbol': 'SEP',
       'decimals': 18,
     },
     'blockExplorerUrl': 'https://sepolia.etherscan.io',
   },
 };

Optionally, assert/log when the key is missing at first use to fail fast in debug:

  • assert(_alchemyApiKey().isNotEmpty, 'Missing ALCHEMY_API_KEY');

4-4: Don’t read dotenv at import time; make API key lookup lazy.

Top-level final initializes before dotenv.load() runs, leading to permanently blank RPC URLs. Compute the key at access time.

-final String alchemyApiKey = dotenv.env['ALCHEMY_API_KEY'] ?? '';
+String _alchemyApiKey() => dotenv.maybeGet('ALCHEMY_API_KEY') ?? '';

32-35: Convert rpcUrls to a getter so it uses the runtime API key.

-final Map<String, String> rpcUrls = {
-  '11155111': 'https://eth-sepolia.g.alchemy.com/v2/$alchemyApiKey',
-  '1': 'https://eth-mainnet.g.alchemy.com/v2/$alchemyApiKey',
-};
+Map<String, String> get rpcUrls => {
+  '11155111': 'https://eth-sepolia.g.alchemy.com/v2/${_alchemyApiKey()}',
+  '1': 'https://eth-mainnet.g.alchemy.com/v2/${_alchemyApiKey()}',
+};
lib/widgets/profile_widgets/profile_section_widget.dart (2)

37-39: Prevent "null" leaking into UI — restore null-aware defaults

Calling toString() on null yields the literal "null", which will render in the name and attempt to fetch an image from "null". Use null-aware coalescing.

-        name: actualData[2].toString(),
-        userAddress: actualData[0].toString(),
-        profilePhotoIpfs: actualData[1].toString(),
+        name: actualData[2]?.toString() ?? '',
+        userAddress: actualData[0]?.toString() ?? '',
+        profilePhotoIpfs: actualData[1]?.toString() ?? '',

Optional: if profilePhotoIpfs can be "ipfs://...", normalize to a gateway before passing to Image.network.


358-358: Don’t suppress the error view; render it when a load error occurs

Currently, build() ignores _errorMessage and continues to render widgets that dereference _userProfileData!, risking a crash on non-“not registered” failures. Use _buildErrorState() and remove the suppression.

-  // ignore: unused_element

Apply these supporting changes outside the selected range:

  • Update build() to branch on error and guard null profile:
@override
Widget build(BuildContext context) {
  final hasError = _errorMessage != null && _errorMessage!.isNotEmpty;
  final profile = _userProfileData;

  return Container(
    padding: const EdgeInsets.all(16),
    child: _isLoading
        ? _buildLoadingState()
        : _isNotRegistered
            ? _buildNotRegisteredState()
            : hasError
                ? _buildErrorState()
                : (profile == null)
                    ? _buildLoadingState()
                    : Row(
                        mainAxisAlignment: MainAxisAlignment.start,
                        children: [
                          _profileOverview(),
                          const SizedBox(width: 15),
                          _tokenWidget(),
                        ],
                      ),
  );
}
  • Normalize error state variable usage and fix typo:
// Prefer: null means "no error", any non-null string is an error message
String? _errorMessage;

// ...
setState(() {
  _errorMessage = 'Error loading User profile details: $e';
});
lib/widgets/map_widgets/flutter_map_widget.dart (1)

12-15: Remove or use the unused required lat/lng in CoordinatesMap constructor

lat and lng are still required but never stored or used. This forces callers to pass meaningless values and makes the API misleading. This was flagged earlier and remains unresolved.

Option A (preferred): remove the params.

-  const CoordinatesMap(
-      {super.key,
-      this.onLocationSelected,
-      required double lat,
-      required double lng});
+  const CoordinatesMap({
+    super.key,
+    this.onLocationSelected,
+  });

Option B (fallback, non-breaking): add nullable fields and use as fallback when provider emits invalid coordinates.

 class CoordinatesMap extends StatefulWidget {
   final Function(double lat, double lng)? onLocationSelected;
+  final double? lat;
+  final double? lng;

-  const CoordinatesMap(
-      {super.key,
-      this.onLocationSelected,
-      required double lat,
-      required double lng});
+  const CoordinatesMap({
+    super.key,
+    this.onLocationSelected,
+    this.lat,
+    this.lng,
+  });

If you choose Option B, I’ll wire up the fallback in build() to use widget.lat/widget.lng when provider returns invalid values.

Run this to find and update all call sites:

#!/bin/bash
# Find all usages of CoordinatesMap(...) and show surrounding context.
rg -nP --type=dart -C2 '\bCoordinatesMap\s*\('
🧹 Nitpick comments (19)
.env.stencil (1)

1-6: Reorder keys and add trailing newline to satisfy dotenv-linter; keep template tidy.

Current ordering triggers UnorderedKey warnings and there is no ending blank line. Sort keys alphabetically (common practice) and add a newline.

Apply:

-WALLETCONNECT_PROJECT_ID=<WALLETCONNECT_PROJECT_ID>
-API_KEY=<PINATA_API_KEY>
-API_SECRET=<PINATA_API_SECRET>
-ALCHEMY_API_KEY=<ALCHEMY_API_KEY>
-CONTRACT_ADDRESS=<CONTRACT_ADDRESS>
-APPLICATION_ID=<APPLICATION_ID>
+ALCHEMY_API_KEY=<ALCHEMY_API_KEY>
+API_KEY=<PINATA_API_KEY>
+API_SECRET=<PINATA_API_SECRET>
+APPLICATION_ID=<APPLICATION_ID>
+CONTRACT_ADDRESS=<CONTRACT_ADDRESS>
+WALLETCONNECT_PROJECT_ID=<WALLETCONNECT_PROJECT_ID>
+
.github/workflows/flutter.yaml (2)

17-23: Trim trailing spaces and enable Flutter SDK/pub caching.

There are trailing spaces on Lines 17, 18, and 23. Also, enabling caching speeds up CI.

Apply:

-      - name: Setup environment file from template 
-        run: cp .env.stencil .env 
+      - name: Setup environment file from template
+        run: cp .env.stencil .env

       - name: Setup Flutter
         uses: subosito/flutter-action@v2
         with:
-          channel: 'stable' 
+          channel: 'stable'
+          cache: true

20-26: Verify Android toolchain availability for APK builds; pin JDK if needed.

ubuntu-latest has a JDK, but the Android SDK/platforms may be missing. Confirm build reliability; if flaky, set up Java 17 and Android SDK explicitly.

Option to pin Java and install SDK:

       - name: Setup Flutter
         uses: subosito/flutter-action@v2
         with:
           channel: 'stable'
           cache: true
+
+      - name: Set up Java
+        uses: actions/setup-java@v4
+        with:
+          distribution: temurin
+          java-version: '17'
+
+      - name: Set up Android SDK
+        uses: android-actions/setup-android@v3
+
+      - name: Accept Android licenses
+        run: yes | sdkmanager --licenses

Then ensure required platforms/build-tools are present (adjust versions to your minSdk/targetSdk):

- name: Install Android platforms/build-tools
  run: |
    sdkmanager "platforms;android-34" "build-tools;34.0.0" "platform-tools"

Also applies to: 34-35

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

50-56: Strengthen wallet address validation.

startsWith('0x') is too weak. Validate full EIP-55 shape to avoid passing malformed addresses downstream.

-      if (!address.startsWith('0x')) {
+      if (!RegExp(r'^0x[a-fA-F0-9]{40}$').hasMatch(address)) {
         return ContractReadResult.error(
           errorMessage: 'Invalid wallet address format',
         );
       }

164-168: Strengthen wallet address validation here as well.

-      if (!address.startsWith('0x')) {
+      if (!RegExp(r'^0x[a-fA-F0-9]{40}$').hasMatch(address)) {
         return ContractReadResult.error(
           errorMessage: 'Invalid wallet address format',
         );
       }

170-173: Avoid null-assert on currentAddress; fail fast with a clear error.

walletProvider.currentAddress! may be null even if isConnected is true (depends on provider state). Guard explicitly.

-      final String currentAddress = walletProvider.currentAddress!.toString();
+      final current = walletProvider.currentAddress;
+      if (current == null) {
+        return ContractReadResult.error(
+          errorMessage: 'Wallet address unavailable. Please reconnect.',
+        );
+      }
+      final String currentAddress = current.toString();
lib/widgets/profile_widgets/profile_section_widget.dart (1)

49-51: Log parse failures at error level and keep stack traces

This is an error path; emit logger.e with stack trace so issues don’t get lost in debug-only logs.

-      logger.d("Error parsing Tree data: $e");
-      logger.d("Data received: $data");
-      logger.d("Data type: ${data.runtimeType}");
+      // Prefer capturing full context at error level
+      // ignore: avoid_catches_without_on_clauses
+      logger.e("Error parsing Tree data", error: e, stackTrace: StackTrace.current);
+      logger.d("Data received: $data");
+      logger.d("Data type: ${data.runtimeType}");
lib/widgets/map_widgets/flutter_map_widget.dart (12)

129-138: Log tile errors to aid troubleshooting; keep error UI as-is

Right now, failures silently flip _hasError with no diagnostics. Add a log for tile failures; it helps distinguish network vs. template/config issues.

               errorTileCallback: (tile, error, stackTrace) {
                 if (mounted) {
+                  logger.e('Tile load error: $error', stackTrace);
                   setState(() {
                     _hasError = true;
                   });
                 }
               },

157-167: Prefer theme-based loading overlay colors for Dark Mode/accessibility

Hardcoded white can clash in dark themes. Use colorScheme.surface and onSurface instead.

-          Container(
-            color: Colors.white,
-            child: const Center(
+          Container(
+            color: Theme.of(context).colorScheme.surface,
+            child: Center(
               child: Column(
                 mainAxisAlignment: MainAxisAlignment.center,
                 children: [
-                  CircularProgressIndicator(),
+                  const CircularProgressIndicator(),
                   SizedBox(height: 16),
-                  Text("Loading map..."),
+                  Text(
+                    "Loading map...",
+                    style: TextStyle(
+                      color: Theme.of(context).colorScheme.onSurface,
+                    ),
+                  ),
                 ],
               ),
             ),
           ),

173-181: Use theme tokens for the coordinate badge background/foreground

Solid black/white ignores theme; invertible surfaces read better in both schemes.

-            decoration: BoxDecoration(
-              color: Colors.black,
+            decoration: BoxDecoration(
+              color: Theme.of(context).colorScheme.inverseSurface,
               borderRadius: BorderRadius.circular(4),
             ),
             child: Text(
               "${latitude.toStringAsFixed(6)}, ${longitude.toStringAsFixed(6)}",
-              style: const TextStyle(
-                color: Colors.white,
+              style: TextStyle(
+                color: Theme.of(context).colorScheme.onInverseSurface,
                 fontSize: 10,
               ),
             ),

192-201: Tweak shadow color for better elevation semantics

A pure black shadow is visually heavy. Prefer a translucent black or theme.shadowColor for Material-like elevation.

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

277-289: Theme the bottom info bar instead of hardcoding blue

Hardcoding can clash with brand/dark mode. Use colorScheme.primary and onPrimary.

-            decoration: BoxDecoration(
-              color: Colors.blue,
+            decoration: BoxDecoration(
+              color: Theme.of(context).colorScheme.primary,
               borderRadius: BorderRadius.circular(4),
             ),
-            child: const Text(
+            child: Text(
               "Tap to set location • Use zoom buttons or pinch to zoom",
-              style: TextStyle(
-                color: Colors.white,
+              style: TextStyle(
+                color: Theme.of(context).colorScheme.onPrimary,
                 fontSize: 11,
               ),
               textAlign: TextAlign.center,
             ),

329-339: Nice defensive sanitize helper; consider sharing it with CoordinatesMap

This helper is solid and mirrors what CoordinatesMap needs. To avoid duplication, extract a shared private top-level function or a small utility.

For example, at top-level in this file:

double sanitizeCoordinate(double value, double defaultValue) {
  if (value.isNaN || !value.isFinite) return defaultValue;
  return value;
}

Then call it from both widgets.


407-416: Avoid duplicate coordinate checks; reuse the sanitizer

This re-check duplicates the earlier sanitize/clamp in build(). Prefer calling the sanitizer here (or only in one place) to keep logic single-sourced.

-    if (latitude.isNaN ||
-        latitude.isInfinite ||
-        longitude.isNaN ||
-        longitude.isInfinite) {
-      logger.e(
-          'ERROR: Invalid coordinates in _buildMapWidget - lat: $latitude, lng: $longitude');
-      latitude = _defaultLat;
-      longitude = _defaultLng;
-    }
+    latitude = _sanitizeCoordinate(latitude, _defaultLat);
+    longitude = _sanitizeCoordinate(longitude, _defaultLng);

436-439: Confirm “static” behavior: should StaticDisplayMap fire onTap and allow edits?

StaticDisplayMap currently invokes onLocationSelected on tap and allows all interactions (panning/zoom). If the intent is truly static, disable tap or reduce interaction flags; otherwise, consider renaming to avoid confusion.

Option (static):

-            interactionOptions: const InteractionOptions(
-              flags: InteractiveFlag.all,
-            ),
+            interactionOptions: const InteractionOptions(
+              flags: InteractiveFlag.doubleTapZoom | InteractiveFlag.pinchZoom,
+            ),
-            onTap: (tapPosition, point) {
-              if (widget.onLocationSelected != null) {
-                widget.onLocationSelected!(point.latitude, point.longitude);
-              }
-            },
+            // No onTap for purely static display

471-482: Theme-based loading overlay for StaticDisplayMap

Same note as CoordinatesMap: use surface/onSurface to respect theme and improve readability.

-          Container(
-            color: Colors.white,
-            child: const Center(
+          Container(
+            color: Theme.of(context).colorScheme.surface,
+            child: Center(
               child: Column(
                 mainAxisAlignment: MainAxisAlignment.center,
                 children: [
-                  CircularProgressIndicator(),
+                  const CircularProgressIndicator(),
                   SizedBox(height: 16),
-                  Text("Loading map..."),
+                  Text(
+                    "Loading map...",
+                    style: TextStyle(
+                      color: Theme.of(context).colorScheme.onSurface,
+                    ),
+                  ),
                 ],
               ),
             ),
           ),

489-498: Theme the coordinate badge in StaticDisplayMap

Mirror the theming suggestion made for CoordinatesMap for consistency across widgets.

-            decoration: BoxDecoration(
-              color: Colors.black,
+            decoration: BoxDecoration(
+              color: Theme.of(context).colorScheme.inverseSurface,
               borderRadius: BorderRadius.circular(4),
             ),
             child: Text(
               "${latitude.toStringAsFixed(6)}, ${longitude.toStringAsFixed(6)}",
-              style: const TextStyle(
-                color: Colors.white,
+              style: TextStyle(
+                color: Theme.of(context).colorScheme.onInverseSurface,
                 fontSize: 10,
               ),
             ),

512-515: Lighter shadow color for consistency

Same rationale as earlier shadow: use translucent black or theme.shadowColor.

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

591-603: Theme the bottom info bar (StaticDisplayMap)

Mirror the suggestion from CoordinatesMap for primary/onPrimary.

-            decoration: BoxDecoration(
-              color: Colors.blue,
+            decoration: BoxDecoration(
+              color: Theme.of(context).colorScheme.primary,
               borderRadius: BorderRadius.circular(4),
             ),
-            child: const Text(
+            child: Text(
               "Static display • Use zoom buttons or pinch to zoom",
-              style: TextStyle(
-                color: Colors.white,
+              style: TextStyle(
+                color: Theme.of(context).colorScheme.onPrimary,
                 fontSize: 11,
               ),
               textAlign: TextAlign.center,
             ),
📜 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 12f6fea and 1cd1feb.

📒 Files selected for processing (24)
  • .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/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/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 (2 hunks)
  • lib/utils/services/contract_read_services.dart (4 hunks)
  • lib/utils/services/contract_write_functions.dart (2 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/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/utils/constants/contractDetails.dart
  • lib/providers/mint_nft_provider.dart
  • lib/pages/home_page.dart
✅ Files skipped from review due to trivial changes (2)
  • lib/widgets/nft_display_utils/tree_nft_view_widget.dart
  • lib/providers/wallet_provider.dart
🚧 Files skipped from review as they are similar to previous changes (13)
  • lib/widgets/nft_display_utils/tree_nft_view_details_with_map.dart
  • lib/utils/services/ipfs_services.dart
  • lib/pages/mint_nft/mint_nft_images.dart
  • lib/utils/constants/contract_abis/tree_nft_contract_abi.dart
  • lib/utils/services/switch_chain_utils.dart
  • lib/widgets/nft_display_utils/user_nfts_widget.dart
  • lib/widgets/wallet_not_connected_widget.dart
  • lib/pages/register_user_page.dart
  • lib/utils/services/contract_write_functions.dart
  • lib/pages/mint_nft/mint_nft_coordinates.dart
  • lib/components/universal_navbar.dart
  • lib/components/wallet_connect_dialog.dart
  • lib/pages/mint_nft/mint_nft_details.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: [EndingBlankLine] No blank line at the end of the file

(EndingBlankLine)


[warning] 6-6: [UnorderedKey] The APPLICATION_ID key should go before the 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 (6)
lib/utils/services/contract_read_services.dart (1)

1-189: All instances of treeNFtContractAddress are consistent

The ripgrep scan returned exactly six matches across the codebase, all of which refer to the same constant definition and its uses in contract read/write services and the ABI file. There are no stray or mixed-case identifiers elsewhere.

If you decide to rename treeNFtContractAddress to treeNftContractAddress, remember to update the following locations:

• lib/utils/constants/contract_abis/tree_nft_contract_abi.dart (definition)
• lib/utils/services/contract_read_services.dart (3 usages)
• lib/utils/services/contract_write_functions.dart (2 usages)

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

222-231: LGTM: Increased tap target for zoom-in

Switching to a 40x40 SizedBox improves accessibility and consistency with Material minimums.


253-262: LGTM: Increased tap target for zoom-out

Same improvement as zoom-in; good call for usability.


307-310: Good: Constructor modernization with super.key and explicit fields

The StaticDisplayMap API is clear: explicit lat/lng and optional callback. This aligns with Flutter conventions.


536-545: LGTM: 40x40 tap target for zoom-in (StaticDisplayMap)

Consistent accessibility improvement.


567-575: LGTM: 40x40 tap target for zoom-out (StaticDisplayMap)

Consistent accessibility improvement.

Comment on lines 34 to 35
- name: Flutter build (apk)
run: flutter build apk --release --no-tree-shake-icons 2>/dev/null | 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

Do not pipe build output to grep; this can hide failures or cause false negatives.

Piping to grep makes the step’s exit code depend on grep, not flutter, and can fail when there’s no stdout even if the build succeeded. It can also mask real errors by discarding stderr.

Apply:

-      - name: Flutter build (apk)
-        run:  flutter build apk --release --no-tree-shake-icons 2>/dev/null | grep -v "deprecated"
+      - name: Flutter build (apk)
+        run: flutter build apk --release --no-tree-shake-icons

If you truly need to filter noisy lines without losing the build’s exit code, use pipefail and tee:

- name: Flutter build (apk)
  run: |
    set -o pipefail
    flutter build apk --release --no-tree-shake-icons 2>&1 | tee build.log
🧰 Tools
🪛 YAMLlint (1.37.1)

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

(colons)

🤖 Prompt for AI Agents
.github/workflows/flutter.yaml lines 34-35: the workflow step pipes flutter
build output to grep which can mask build failures and makes the step exit code
reflect grep instead of flutter; replace the single-line piped command with a
multi-line run that enables pipefail, captures both stdout and stderr, and
writes output to a log (so noisy lines can be inspected) while preserving the
flutter command's exit status — specifically set pipefail before running
flutter, redirect stderr to stdout, and tee the output to a file instead of
piping to grep.

Comment on lines +35 to 37
final double latitude = provider.getLatitude();
final double longitude = provider.getLongitude();

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add coordinate sanitization/clamping before rendering

Unlike StaticDisplayMap, CoordinatesMap passes provider values straight to the map. NaN/∞/out-of-range values can crash or mis-center the map. Mirror the StaticDisplayMap guards.

Proposed minimal patch:

-        final double latitude = provider.getLatitude();
-        final double longitude = provider.getLongitude();
+        final double rawLat = provider.getLatitude();
+        final double rawLng = provider.getLongitude();
+        final double latitude = _sanitizeCoordinate(rawLat, 0.0).clamp(-90.0, 90.0).toDouble();
+        final double longitude = _sanitizeCoordinate(rawLng, 0.0).clamp(-180.0, 180.0).toDouble();

Add this helper inside _CoordinatesMapState (same as in StaticDisplayMap), optionally logging when coercing:

 class _CoordinatesMapState extends State<CoordinatesMap> {
   late MapController _mapController;
   bool _mapLoaded = false;
   bool _hasError = false;
+  double _sanitizeCoordinate(double value, double defaultValue) {
+    if (value.isNaN || !value.isFinite) {
+      logger.e('Invalid coordinate detected: $value, using default: $defaultValue');
+      return defaultValue;
+    }
+    return value;
+  }
📝 Committable suggestion

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

Suggested change
final double latitude = provider.getLatitude();
final double longitude = provider.getLongitude();
++ b/lib/widgets/map_widgets/flutter_map_widget.dart
@@ build(BuildContext context) {
- final double latitude = provider.getLatitude();
final double rawLat = provider.getLatitude();
final double rawLng = provider.getLongitude();
final double latitude = _sanitizeCoordinate(rawLat, 0.0)
.clamp(-90.0, 90.0)
.toDouble();
final double longitude = _sanitizeCoordinate(rawLng, 0.0)
.clamp(-180.0, 180.0)
.toDouble();
Suggested change
final double latitude = provider.getLatitude();
final double longitude = provider.getLongitude();
++ b/lib/widgets/map_widgets/flutter_map_widget.dart
@@ class _CoordinatesMapState extends State<CoordinatesMap> {
late MapController _mapController;
bool _mapLoaded = false;
bool _hasError = false;
double _sanitizeCoordinate(double value, double defaultValue) {
if (value.isNaN || !value.isFinite) {
logger.e('Invalid coordinate detected: $value, using default: $defaultValue');
return defaultValue;
}
return value;
}

Comment on lines 246 to 355
Widget _tokenWidget() {
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(4.0),
child: SizedBox(
height: 40,
width: 150,
child: Container(
decoration: BoxDecoration(
color: const Color.fromARGB(255, 251, 251, 99),
border: Border.all(
color: Colors.black,
width: 2,
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black12, // shadow color
blurRadius: 6, // shadow softness
offset: Offset(0, 3), // shadow position
),
],
return Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(4.0),
child: SizedBox(
height: 40,
width: 150,
child: Container(
decoration: BoxDecoration(
color: const Color.fromARGB(255, 251, 251, 99),
border: Border.all(
color: Colors.black,
width: 2,
),
child: Center(
child: Text(
'Planter Tokens : ${_userProfileData!.planterTokens}'))),
),
),
Padding(
padding: const EdgeInsets.all(4.0),
child: SizedBox(
height: 40,
width: 150,
child: Container(
decoration: BoxDecoration(
color: const Color.fromARGB(255, 28, 211, 129),
border: Border.all(
color: Colors.black,
width: 2,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black12, // shadow color
blurRadius: 6, // shadow softness
offset: Offset(0, 3), // shadow position
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 6,
offset: Offset(0, 3),
),
],
),
child: Center(
child: Text(
'Care Tokens : ${_userProfileData!.careTokens}'))),
),
],
),
child: Center(
child: Text(
'Planter Tokens : ${_userProfileData!.planterTokens}'))),
),
Padding(
padding: const EdgeInsets.all(4.0),
child: SizedBox(
height: 40,
width: 150,
child: Container(
decoration: BoxDecoration(
color: const Color.fromARGB(255, 251, 251, 99),
border: Border.all(
color: Colors.black,
width: 2,
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 6,
offset: Offset(0, 3),
),
],
),
Padding(
padding: const EdgeInsets.all(4.0),
child: SizedBox(
height: 40,
width: 150,
child: Container(
decoration: BoxDecoration(
color: const Color.fromARGB(255, 28, 211, 129),
border: Border.all(
color: Colors.black,
width: 2,
),
child: Center(
child: Text(
'Verifier Tokens : ${_userProfileData!.verifierTokens}'))),
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 6,
offset: Offset(0, 3),
),
],
),
child: Center(
child:
Text('Care Tokens : ${_userProfileData!.careTokens}'))),
),
Padding(
padding: const EdgeInsets.all(4.0),
child: SizedBox(
height: 40,
width: 150,
child: Container(
decoration: BoxDecoration(
color: const Color.fromARGB(255, 28, 211, 129),
border: Border.all(
color: Colors.black,
width: 2,
),
Padding(
padding: const EdgeInsets.all(4.0),
child: SizedBox(
height: 40,
width: 150,
child: Container(
decoration: BoxDecoration(
color: const Color.fromARGB(255, 251, 251, 99),
border: Border.all(
color: Colors.black,
width: 2,
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 6,
offset: Offset(0, 3),
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 6,
offset: Offset(0, 3),
),
],
],
),
child: Center(
child: Text(
'Verifier Tokens : ${_userProfileData!.verifierTokens}'))),
),
),
Padding(
padding: const EdgeInsets.all(4.0),
child: SizedBox(
height: 40,
width: 150,
child: Container(
decoration: BoxDecoration(
color: const Color.fromARGB(255, 28, 211, 129),
border: Border.all(
color: Colors.black,
width: 2,
),
child: Center(
child: Text(
'Legacy Tokens : ${_userProfileData!.legacyTokens}'))),
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 6,
offset: Offset(0, 3),
),
],
),
child: Center(
child: Text(
'Legacy Tokens : ${_userProfileData!.legacyTokens}'))),
),
],
),
),
],
);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Guard against null profile and de-duplicate token tiles

This widget dereferences _userProfileData! four times. If build() accidentally renders this on an error path, it will throw. Also, the four nearly-identical blocks can be collapsed into a single helper for maintainability.

-  Widget _tokenWidget() {
-    return Column(
-      mainAxisAlignment: MainAxisAlignment.start,
-      children: [
-        Padding(
-          padding: const EdgeInsets.all(4.0),
-          child: SizedBox(
-            height: 40,
-            width: 150,
-            child: Container(
-                decoration: BoxDecoration(
-                  color: const Color.fromARGB(255, 251, 251, 99),
-                  border: Border.all(
-                    color: Colors.black,
-                    width: 2,
-                  ),
-                  borderRadius: BorderRadius.circular(12),
-                  boxShadow: [
-                    BoxShadow(
-                      color: Colors.black12, // shadow color
-                      blurRadius: 6, // shadow softness
-                      offset: Offset(0, 3), // shadow position
-                    ),
-                  ],
-                ),
-                child: Center(
-                    child: Text(
-                        'Planter Tokens : ${_userProfileData!.planterTokens}'))),
-          ),
-        ),
-        Padding(
-          padding: const EdgeInsets.all(4.0),
-          child: SizedBox(
-            height: 40,
-            width: 150,
-            child: Container(
-                decoration: BoxDecoration(
-                  color: const Color.fromARGB(255, 28, 211, 129),
-                  border: Border.all(
-                    color: Colors.black,
-                    width: 2,
-                  ),
-                  borderRadius: BorderRadius.circular(12),
-                  boxShadow: [
-                    BoxShadow(
-                      color: Colors.black12,
-                      blurRadius: 6,
-                      offset: Offset(0, 3),
-                    ),
-                  ],
-                ),
-                child: Center(
-                    child:
-                        Text('Care Tokens : ${_userProfileData!.careTokens}'))),
-          ),
-        ),
-        Padding(
-          padding: const EdgeInsets.all(4.0),
-          child: SizedBox(
-            height: 40,
-            width: 150,
-            child: Container(
-                decoration: BoxDecoration(
-                  color: const Color.fromARGB(255, 251, 251, 99),
-                  border: Border.all(
-                    color: Colors.black,
-                    width: 2,
-                  ),
-                  borderRadius: BorderRadius.circular(12),
-                  boxShadow: [
-                    BoxShadow(
-                      color: Colors.black12,
-                      blurRadius: 6,
-                      offset: Offset(0, 3),
-                    ),
-                  ],
-                ),
-                child: Center(
-                    child: Text(
-                        'Verifier Tokens : ${_userProfileData!.verifierTokens}'))),
-          ),
-        ),
-        Padding(
-          padding: const EdgeInsets.all(4.0),
-          child: SizedBox(
-            height: 40,
-            width: 150,
-            child: Container(
-                decoration: BoxDecoration(
-                  color: const Color.fromARGB(255, 28, 211, 129),
-                  border: Border.all(
-                    color: Colors.black,
-                    width: 2,
-                  ),
-                  borderRadius: BorderRadius.circular(12),
-                  boxShadow: [
-                    BoxShadow(
-                      color: Colors.black12,
-                      blurRadius: 6,
-                      offset: Offset(0, 3),
-                    ),
-                  ],
-                ),
-                child: Center(
-                    child: Text(
-                        'Legacy Tokens : ${_userProfileData!.legacyTokens}'))),
-          ),
-        ),
-      ],
-    );
-  }
+  Widget _tokenWidget() {
+    final profile = _userProfileData;
+    if (profile == null) return const SizedBox.shrink();
+
+    Widget tile(String label, int value, Color color) {
+      return SizedBox(
+        height: 40,
+        width: 150,
+        child: Container(
+          decoration: BoxDecoration(
+            color: color,
+            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(
+      mainAxisAlignment: MainAxisAlignment.start,
+      children: [
+        Padding(
+          padding: const EdgeInsets.all(4.0),
+          child: tile('Planter Tokens', profile.planterTokens,
+              const Color.fromARGB(255, 251, 251, 99)),
+        ),
+        Padding(
+          padding: const EdgeInsets.all(4.0),
+          child: tile('Care Tokens', profile.careTokens,
+              const Color.fromARGB(255, 28, 211, 129)),
+        ),
+        Padding(
+          padding: const EdgeInsets.all(4.0),
+          child: tile('Verifier Tokens', profile.verifierTokens,
+              const Color.fromARGB(255, 251, 251, 99)),
+        ),
+        Padding(
+          padding: const EdgeInsets.all(4.0),
+          child: tile('Legacy Tokens', profile.legacyTokens,
+              const Color.fromARGB(255, 28, 211, 129)),
+        ),
+      ],
+    );
+  }
📝 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
Widget _tokenWidget() {
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(4.0),
child: SizedBox(
height: 40,
width: 150,
child: Container(
decoration: BoxDecoration(
color: const Color.fromARGB(255, 251, 251, 99),
border: Border.all(
color: Colors.black,
width: 2,
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black12, // shadow color
blurRadius: 6, // shadow softness
offset: Offset(0, 3), // shadow position
),
],
return Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(4.0),
child: SizedBox(
height: 40,
width: 150,
child: Container(
decoration: BoxDecoration(
color: const Color.fromARGB(255, 251, 251, 99),
border: Border.all(
color: Colors.black,
width: 2,
),
child: Center(
child: Text(
'Planter Tokens : ${_userProfileData!.planterTokens}'))),
),
),
Padding(
padding: const EdgeInsets.all(4.0),
child: SizedBox(
height: 40,
width: 150,
child: Container(
decoration: BoxDecoration(
color: const Color.fromARGB(255, 28, 211, 129),
border: Border.all(
color: Colors.black,
width: 2,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black12, // shadow color
blurRadius: 6, // shadow softness
offset: Offset(0, 3), // shadow position
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 6,
offset: Offset(0, 3),
),
],
),
child: Center(
child: Text(
'Care Tokens : ${_userProfileData!.careTokens}'))),
),
],
),
child: Center(
child: Text(
'Planter Tokens : ${_userProfileData!.planterTokens}'))),
),
Padding(
padding: const EdgeInsets.all(4.0),
child: SizedBox(
height: 40,
width: 150,
child: Container(
decoration: BoxDecoration(
color: const Color.fromARGB(255, 251, 251, 99),
border: Border.all(
color: Colors.black,
width: 2,
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 6,
offset: Offset(0, 3),
),
],
),
Padding(
padding: const EdgeInsets.all(4.0),
child: SizedBox(
height: 40,
width: 150,
child: Container(
decoration: BoxDecoration(
color: const Color.fromARGB(255, 28, 211, 129),
border: Border.all(
color: Colors.black,
width: 2,
),
child: Center(
child: Text(
'Verifier Tokens : ${_userProfileData!.verifierTokens}'))),
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 6,
offset: Offset(0, 3),
),
],
),
child: Center(
child:
Text('Care Tokens : ${_userProfileData!.careTokens}'))),
),
Padding(
padding: const EdgeInsets.all(4.0),
child: SizedBox(
height: 40,
width: 150,
child: Container(
decoration: BoxDecoration(
color: const Color.fromARGB(255, 28, 211, 129),
border: Border.all(
color: Colors.black,
width: 2,
),
Padding(
padding: const EdgeInsets.all(4.0),
child: SizedBox(
height: 40,
width: 150,
child: Container(
decoration: BoxDecoration(
color: const Color.fromARGB(255, 251, 251, 99),
border: Border.all(
color: Colors.black,
width: 2,
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 6,
offset: Offset(0, 3),
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 6,
offset: Offset(0, 3),
),
],
],
),
child: Center(
child: Text(
'Verifier Tokens : ${_userProfileData!.verifierTokens}'))),
),
),
Padding(
padding: const EdgeInsets.all(4.0),
child: SizedBox(
height: 40,
width: 150,
child: Container(
decoration: BoxDecoration(
color: const Color.fromARGB(255, 28, 211, 129),
border: Border.all(
color: Colors.black,
width: 2,
),
child: Center(
child: Text(
'Legacy Tokens : ${_userProfileData!.legacyTokens}'))),
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 6,
offset: Offset(0, 3),
),
],
),
child: Center(
child: Text(
'Legacy Tokens : ${_userProfileData!.legacyTokens}'))),
),
],
),
),
],
);
Widget _tokenWidget() {
final profile = _userProfileData;
if (profile == null) return const SizedBox.shrink();
Widget tile(String label, int value, Color color) {
return SizedBox(
height: 40,
width: 150,
child: Container(
decoration: BoxDecoration(
color: color,
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(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(4.0),
child: tile(
'Planter Tokens',
profile.planterTokens,
const Color.fromARGB(255, 251, 251, 99),
),
),
Padding(
padding: const EdgeInsets.all(4.0),
child: tile(
'Care Tokens',
profile.careTokens,
const Color.fromARGB(255, 28, 211, 129),
),
),
Padding(
padding: const EdgeInsets.all(4.0),
child: tile(
'Verifier Tokens',
profile.verifierTokens,
const Color.fromARGB(255, 251, 251, 99),
),
),
Padding(
padding: const EdgeInsets.all(4.0),
child: tile(
'Legacy Tokens',
profile.legacyTokens,
const Color.fromARGB(255, 28, 211, 129),
),
),
],
);
}
🤖 Prompt for AI Agents
In lib/widgets/profile_widgets/profile_section_widget.dart around lines 246 to
355, the _tokenWidget dereferences _userProfileData! multiple times (risking a
crash if null) and repeats near-identical tile code; change it to first guard
against a null profile (return a placeholder/empty SizedBox or a loading/error
tile when _userProfileData is null) and refactor the repeated
Container+Padding+SizedBox pattern into a small private helper method that
accepts label, tokenValue (use null-aware access like
_userProfileData?.planterTokens), and color to build each tile, then call that
helper for the four token types to eliminate duplication and avoid
force-unwrapping.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (4)
.github/workflows/flutter.yaml (4)

20-24: Enable Flutter action caching; keep channel stable or pin a version.

You already use channel: stable (good). Add cache: true for faster runs; optionally pin a 3.24.x version for reproducibility.

       - name: Setup Flutter
         uses: subosito/flutter-action@v2
         with:
-          channel: 'stable' 
+          channel: 'stable'
+          cache: true

25-33: Run Flutter tests in CI.

Tests exist in the repo; surface them to catch regressions.

       - name: Dart analyze
         run: flutter analyze
 
+      - name: Run Flutter tests
+        run: flutter test -r expanded
+
       - name: Check formatting
         run: dart format --output=none --set-exit-if-changed .

15-16: Upgrade checkout to v4 to avoid runner deprecation and actionlint failure.

actions/checkout@v3 is flagged and will eventually break on newer runners. Switch to v4 and fetch full history when needed.

-      - name: Checkout repository
-        uses: actions/checkout@v3
+      - name: Checkout repository
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 0

34-35: Don’t pipe build output to grep; it can mask failures and break exit codes.

Make the step fail on Flutter/Gradle errors and keep logs.

-      - name: Flutter build (apk)
-        run:  flutter build apk --release --no-tree-shake-icons | grep -v "deprecated"
+      - name: Flutter build (apk)
+        run: |
+          set -o pipefail
+          flutter build apk --release --no-tree-shake-icons 2>&1 | tee build-apk.log
🧹 Nitpick comments (6)
.github/workflows/flutter.yaml (6)

17-18: Fix YAML lint issues: trailing spaces and extra indent after colon.

These are failing yamllint: remove trailing spaces and the extra space after “run:”.

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

If you keep the multi-line step with tee from the prior comment, still ensure “run:” has a single space and no trailing spaces.

Also applies to: 23-23, 35-35


9-12: Harden the job: add explicit permissions and a timeout.

Least-privilege and timeboxing help security and CI stability.

 jobs:
   build:
     runs-on: ubuntu-latest
+    permissions:
+      contents: read
+    timeout-minutes: 30

13-13: Speed up Android builds with Java/Gradle cache.

Set up Temurin JDK and enable Gradle caching to cut APK build times.

     steps:
       - name: Checkout repository
         uses: actions/checkout@v4
         with:
           fetch-depth: 0
+
+      - name: Set up Java
+        uses: actions/setup-java@v4
+        with:
+            distribution: 'temurin'
+            java-version: '17'
+            cache: 'gradle'

Note: Java 17 is widely compatible with recent Android Gradle Plugin; adjust if your Gradle/AGP requires a different version.


3-8: Optional: add concurrency to cancel superseded runs on the same branch/PR.

Prevents wasting runners when pushing multiple commits rapidly.

 on:
   push:
     branches: [ main ]
   pull_request:
     branches: [ main ]
 
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true

9-36: Optional: add a separate web build job.

Your app targets web; building it in CI catches platform-specific breakages early.

 jobs:
   build:
@@
       - name: Flutter build (apk)
         run: flutter build apk --release --no-tree-shake-icons
+
+  build-web:
+    runs-on: ubuntu-latest
+    permissions:
+      contents: read
+    timeout-minutes: 20
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+      - name: Setup Flutter
+        uses: subosito/flutter-action@v2
+        with:
+          channel: 'stable'
+          cache: true
+      - name: Install dependencies
+        run: flutter pub get
+      - name: Analyze
+        run: flutter analyze
+      - name: Build web
+        run: flutter build web --release

If your web import graph includes dart:io, this will fail; consider conditional exports to separate mobile/web implementations before enabling this job.


17-19: Confirm env template sufficiency for CI builds.

Copying .env.stencil implies non-secret defaults. Verify required keys (e.g., APPLICATION_ID, contract addresses) have safe placeholders that allow analyze/test/build to pass without secrets.

If needed, I can propose a minimal .env.stencil that satisfies compile-time config but gates runtime features behind feature flags.

📜 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 1cd1feb and ab84dd5.

📒 Files selected for processing (1)
  • .github/workflows/flutter.yaml (1 hunks)
🧰 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)

@IronJam11
Copy link
Contributor Author

@ceilican Bhavik asked for your permission to merge it, he has reviewd it from his side.

@Zahnentferner Zahnentferner merged commit a59ec7b into main Sep 4, 2025
2 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Sep 24, 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