Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .env.stencil
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ 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>
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.

35 changes: 35 additions & 0 deletions .github/workflows/flutter.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Flutter CI

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build:
runs-on: ubuntu-latest

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

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

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

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

- name: Install dependencies
run: flutter pub get

- name: Dart analyze
run: flutter analyze

Comment on lines +28 to +30
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.

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

- name: Flutter build (apk)
run: flutter build apk --release --no-tree-shake-icons | grep -v "deprecated"
25 changes: 12 additions & 13 deletions lib/components/universal_navbar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class UniversalNavbar extends StatelessWidget implements PreferredSizeWidget {
bottom: 0,
left: 0,
right: 0,
child: Container(
child: SizedBox(
height: 40,
child: _buildPlantIllustrations(),
),
Expand All @@ -54,7 +54,7 @@ class UniversalNavbar extends StatelessWidget implements PreferredSizeWidget {
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Colors.white.withOpacity(0.3),
color: Colors.white,
width: 1,
),
),
Expand Down Expand Up @@ -151,14 +151,13 @@ class UniversalNavbar extends StatelessWidget implements PreferredSizeWidget {
Widget _buildPlantIllustrations() {
return Container(
decoration: BoxDecoration(
color: const Color.fromARGB(255, 251, 251, 99)
.withOpacity(0.9), // Beige background
color: const Color.fromARGB(255, 251, 251, 99),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(40),
topRight: Radius.circular(40),
),
border: Border.all(
color: Colors.black.withOpacity(0.2),
color: Colors.black,
width: 1,
),
),
Expand All @@ -183,7 +182,7 @@ class UniversalNavbar extends StatelessWidget implements PreferredSizeWidget {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: treeImages.map((imagePath) {
return Container(
return SizedBox(
width: plantWidth,
height: plantWidth,
child: Image.asset(
Expand Down Expand Up @@ -241,15 +240,15 @@ class UniversalNavbar extends StatelessWidget implements PreferredSizeWidget {
constraints: const BoxConstraints(maxWidth: 100, minHeight: 20),
// Limit max width
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.95),
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: Colors.green.withOpacity(0.3),
color: Colors.green,
width: 1,
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
color: Colors.black,
blurRadius: 4,
offset: const Offset(0, 2),
),
Expand All @@ -267,7 +266,7 @@ class UniversalNavbar extends StatelessWidget implements PreferredSizeWidget {
color: Colors.green[700],
),
const SizedBox(width: 4),
Container(
SizedBox(
width: 10,
child: Flexible(
child: Text(
Comment on lines +269 to 272
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!

Expand Down Expand Up @@ -366,15 +365,15 @@ class UniversalNavbar extends StatelessWidget implements PreferredSizeWidget {
return Container(
constraints: const BoxConstraints(maxWidth: 80), // Limit max width
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.95),
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: Colors.green.withOpacity(0.3),
color: Colors.green,
width: 1,
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
color: Colors.black,
blurRadius: 4,
offset: const Offset(0, 2),
),
Expand Down
5 changes: 5 additions & 0 deletions lib/components/wallet_connect_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,12 @@ class WalletConnectDialog extends StatelessWidget {
onPressed: () async {
try {
await walletProvider.openWallet(wallet, uri);
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
} catch (e) {
// ignore: use_build_context_synchronously
Navigator.of(context).pop();
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context).showSnackBar(
Comment on lines +43 to 49
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.

SnackBar(
content: Text(e.toString()),
Expand Down Expand Up @@ -75,7 +78,9 @@ class WalletConnectDialog extends StatelessWidget {
child: OutlinedButton.icon(
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(
const SnackBar(
content: Text('URI copied to clipboard!'),
Expand Down
10 changes: 5 additions & 5 deletions lib/models/wallet_chain_option.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
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.


class WalletOption {
final String name;
Expand Down Expand Up @@ -30,14 +30,14 @@ final List<WalletOption> walletOptionsList = [
];

final Map<String, String> rpcUrls = {
'11155111': 'https://eth-sepolia.g.alchemy.com/v2/$ALCHEMY_API_KEY',
'1': 'https://eth-mainnet.g.alchemy.com/v2/$ALCHEMY_API_KEY',
'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 = {
'1': {
'name': 'Ethereum Mainnet',
'rpcUrl': 'https://eth-mainnet.g.alchemy.com/v2/$ALCHEMY_API_KEY',
'rpcUrl': 'https://eth-mainnet.g.alchemy.com/v2/$alchemyApiKey',
'nativeCurrency': {
'name': 'Ether',
'symbol': 'ETH',
Expand All @@ -47,7 +47,7 @@ final Map<String, Map<String, dynamic>> chainInfoList = {
},
'11155111': {
'name': 'Sepolia Testnet',
'rpcUrl': 'https://eth-sepolia.g.alchemy.com/v2/$ALCHEMY_API_KEY',
'rpcUrl': 'https://eth-sepolia.g.alchemy.com/v2/$alchemyApiKey',
'nativeCurrency': {
'name': 'Sepolia Ether',
'symbol': 'SEP',
Expand Down
2 changes: 0 additions & 2 deletions lib/pages/home_page.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

import 'package:provider/provider.dart';
import 'package:tree_planting_protocol/providers/wallet_provider.dart';

import 'package:tree_planting_protocol/utils/constants/navbar_constants.dart';
import 'package:tree_planting_protocol/utils/constants/route_constants.dart';

import 'package:tree_planting_protocol/widgets/basic_scaffold.dart';
import 'package:tree_planting_protocol/widgets/profile_widgets/profile_section_widget.dart';
Expand Down
3 changes: 2 additions & 1 deletion lib/pages/mint_nft/mint_nft_coordinates.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import 'package:tree_planting_protocol/providers/mint_nft_provider.dart';
import 'package:tree_planting_protocol/utils/constants/route_constants.dart';
import 'package:tree_planting_protocol/widgets/basic_scaffold.dart';
import 'package:tree_planting_protocol/widgets/map_widgets/flutter_map_widget.dart';
import 'package:tree_planting_protocol/widgets/nft_display_utils/tree_NFT_view_widget.dart';
import 'package:tree_planting_protocol/widgets/nft_display_utils/tree_nft_view_widget.dart';
import 'package:tree_planting_protocol/utils/services/get_current_location.dart';
import 'package:dart_geohash/dart_geohash.dart';

Expand Down Expand Up @@ -618,6 +618,7 @@ class _MintNftCoordinatesPageState extends State<MintNftCoordinatesPage> {
);
}

// ignore: unused_element
Widget _buildPreviewSection() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
Expand Down
24 changes: 11 additions & 13 deletions lib/pages/mint_nft/mint_nft_details.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import 'package:provider/provider.dart';
import 'package:tree_planting_protocol/providers/mint_nft_provider.dart';
import 'package:tree_planting_protocol/utils/constants/route_constants.dart';
import 'package:tree_planting_protocol/widgets/basic_scaffold.dart';
import 'package:tree_planting_protocol/widgets/map_widgets/flutter_map_widget.dart';
import 'package:tree_planting_protocol/widgets/nft_display_utils/tree_NFT_view_widget.dart';
import 'package:tree_planting_protocol/widgets/nft_display_utils/tree_nft_view_details_with_map.dart';

class MintNftDetailsPage extends StatefulWidget {
Expand Down Expand Up @@ -103,14 +101,14 @@ class _MintNftCoordinatesPageState extends State<MintNftDetailsPage> {
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
const Color(0xFF1CD381).withOpacity(0.05),
const Color(0xFFFAEB96).withOpacity(0.1),
const Color(0xFF1CD381),
const Color(0xFFFAEB96),
],
),
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: const Color(0xFF1CD381).withOpacity(0.15),
color: const Color(0xFF1CD381),
blurRadius: 20,
offset: const Offset(0, 8),
),
Expand All @@ -126,7 +124,7 @@ class _MintNftCoordinatesPageState extends State<MintNftDetailsPage> {
gradient: LinearGradient(
colors: [
const Color(0xFF1CD381),
const Color(0xFF1CD381).withOpacity(0.8),
const Color(0xFF1CD381),
],
),
borderRadius: const BorderRadius.only(
Expand All @@ -139,7 +137,7 @@ class _MintNftCoordinatesPageState extends State<MintNftDetailsPage> {
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
color: Colors.white,
borderRadius: BorderRadius.circular(14),
),
child: const Icon(
Expand Down Expand Up @@ -208,7 +206,7 @@ class _MintNftCoordinatesPageState extends State<MintNftDetailsPage> {
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
shadowColor: const Color(0xFF1CD381).withOpacity(0.3),
shadowColor: const Color(0xFF1CD381),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
Expand All @@ -224,7 +222,7 @@ class _MintNftCoordinatesPageState extends State<MintNftDetailsPage> {
Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
color: Colors.white,
borderRadius: BorderRadius.circular(6),
),
child: const Icon(
Expand Down Expand Up @@ -260,7 +258,7 @@ class _MintNftCoordinatesPageState extends State<MintNftDetailsPage> {
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: const Color(0xFF1CD381).withOpacity(0.1),
color: const Color(0xFF1CD381),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
Expand All @@ -286,12 +284,12 @@ class _MintNftCoordinatesPageState extends State<MintNftDetailsPage> {
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: const Color(0xFFFAEB96).withOpacity(0.5),
color: const Color(0xFFFAEB96),
width: 2,
),
boxShadow: [
BoxShadow(
color: const Color(0xFF1CD381).withOpacity(0.05),
color: const Color(0xFF1CD381),
blurRadius: 8,
offset: const Offset(0, 2),
),
Expand Down Expand Up @@ -337,7 +335,7 @@ class _MintNftCoordinatesPageState extends State<MintNftDetailsPage> {
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: const Color(0xFFFAEB96).withOpacity(0.3),
color: const Color(0xFFFAEB96),
borderRadius: BorderRadius.circular(10),
),
child: Icon(
Expand Down
7 changes: 5 additions & 2 deletions lib/pages/mint_nft/mint_nft_images.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,15 @@ class _MultipleImageUploadPageState extends State<MultipleImageUploadPage> {
if (images.isEmpty) return;

logger.d('Selected ${images.length} images for upload');

// ignore: use_build_context_synchronously
final provider = Provider.of<MintNftProvider>(context, listen: false);

Comment on lines +46 to +48
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.

setState(() {
_processingImages = images.map((image) => File(image.path)).toList();
_isUploading = true;
});
Comment on lines 49 to 52
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 setState/snackbars with mounted after awaits

There are multiple UI updates that can run after awaited calls; add mounted checks to prevent setState on a disposed State and avoid using a stale context for SnackBars.

Minimal inline guards you can apply:

@@
-      setState(() {
+      if (!mounted) return;
+      setState(() {
         _processingImages = images.map((image) => File(image.path)).toList();
         _isUploading = true;
       });
@@
-        setState(() {
+        if (!mounted) return;
+        setState(() {
           _uploadingIndex = i;
         });
@@
-            setState(() {
+            if (!mounted) return;
+            setState(() {
               _uploadedHashes.add(hash);
             });
@@
-      setState(() {
+      if (!mounted) return;
+      setState(() {
         _isUploading = false;
         _uploadingIndex = -1;
         _processingImages.clear();
       });
@@
-      setState(() {
+      if (!mounted) return;
+      setState(() {
         _isUploading = false;
         _uploadingIndex = -1;
         _processingImages.clear();
       });
-      _showSnackBar('Error selecting images: $e');
+      if (mounted) _showSnackBar('Error selecting images: $e');

Also applies to: 57-59, 67-69, 80-85, 91-97

🤖 Prompt for AI Agents
In lib/pages/mint_nft/mint_nft_images.dart around lines 49-52 (and likewise at
57-59, 67-69, 80-85, 91-97): currently setState and any ScaffoldMessenger/
SnackBar usage can run after awaits which may call them on a disposed State or
use a stale context; after every await return point insert a guard "if
(!mounted) return;" before calling setState or showing SnackBars, and wrap any
subsequent setState calls in that mounted check so UI updates and context-based
snackbars only run when the State is still mounted.


final provider = Provider.of<MintNftProvider>(context, listen: false);
List<String> newHashes = [];

for (int i = 0; i < images.length; i++) {
Expand Down Expand Up @@ -252,7 +255,7 @@ class _MultipleImageUploadPageState extends State<MultipleImageUploadPage> {
width: 120,
height: 120,
decoration: BoxDecoration(
color: Colors.green.withOpacity(0.8),
color: Colors.green,
borderRadius:
BorderRadius.circular(8),
),
Expand Down
12 changes: 6 additions & 6 deletions lib/pages/register_user_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ class _RegisterUserPageState extends State<RegisterUserPage> {
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: const Color(0xFF1CD381).withOpacity(0.1),
color: const Color(0xFF1CD381),
borderRadius: BorderRadius.circular(8),
),
child: const Icon(
Expand Down Expand Up @@ -308,12 +308,12 @@ class _RegisterUserPageState extends State<RegisterUserPage> {
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: const Color(0xFFFAEB96).withOpacity(0.5),
color: const Color(0xFFFAEB96),
width: 2,
),
boxShadow: [
BoxShadow(
color: const Color(0xFF1CD381).withOpacity(0.05),
color: const Color(0xFF1CD381),
blurRadius: 8,
offset: const Offset(0, 2),
),
Expand Down Expand Up @@ -444,7 +444,7 @@ Widget _buildFormField({
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: const Color(0xFF1CD381).withOpacity(0.1),
color: const Color(0xFF1CD381),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
Expand All @@ -470,12 +470,12 @@ Widget _buildFormField({
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: const Color(0xFFFAEB96).withOpacity(0.5),
color: const Color(0xFFFAEB96),
width: 2,
),
boxShadow: [
BoxShadow(
color: const Color(0xFF1CD381).withOpacity(0.05),
color: const Color(0xFF1CD381),
blurRadius: 8,
offset: const Offset(0, 2),
),
Expand Down
Loading