-
Notifications
You must be signed in to change notification settings - Fork 19
Merged org trees #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged org trees #21
Conversation
…at organisation level
merge allTreesPage with individualOrganisationsPage branch
WalkthroughThis PR introduces an organization-based NFT minting flow, new user profile viewing capabilities, enhanced contract functions with pagination support, updated contract ABIs, and widespread theming improvements across UI components. Environment variables are updated to Pinata-specific keys. Changes
Sequence DiagramsequenceDiagram
actor User
participant App as Flutter App
participant Wallet as WalletProvider
participant Contract as Smart Contract
participant Dialog as TransactionDialog
User->>App: Initiate NFT Mint
App->>Wallet: Check wallet connection
Wallet-->>App: Connected
App->>App: Show Mint Org Selection
User->>App: Select Organisation
App->>App: Store org address in provider
App->>App: Show Details (Coordinates, Species, Trees)
User->>App: Enter Details
App->>App: Show Images Upload
User->>App: Upload Images
App->>Wallet: Get session
Wallet-->>App: Session valid
alt Organisation Minting
App->>Contract: Call plantTreeProposal
Contract-->>App: Proposal submitted (hash)
else Individual Minting
App->>Contract: Call mintNft
Contract-->>App: NFT minted (hash)
end
App->>Dialog: Show TransactionDialog.showSuccess
Dialog-->>User: Display success with hash
App->>App: Clear mint provider data
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Areas requiring extra attention:
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 32
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (10)
lib/utils/services/contract_functions/organisation_factory_contract.dart/organisation_factory_contract_read_functions.dart (1)
45-45: Fix inconsistent error messages.Error messages reference "NFTs" instead of "organisations", which is misleading for this organisation-specific read function.
Apply this diff:
if (!walletProvider.isConnected) { logger.e("Wallet not connected for reading organisations"); return ContractReadResult.error( - errorMessage: 'Please connect your wallet before reading NFTs.', + errorMessage: 'Please connect your wallet before reading organisations.', ); } // ... } catch (e) { logger.e("Error reading organisations", error: e); return ContractReadResult.error( - errorMessage: 'Failed to read NFTs: ${e.toString()}', + errorMessage: 'Failed to read organisations: ${e.toString()}', ); }Also applies to: 86-86
lib/pages/organisations_pages/create_organisation.dart (2)
105-106: Remove unused variable.The
resultvariable is declared but never used. Either remove it or handle the result appropriately.Apply this diff:
final walletProvider = // ignore: use_build_context_synchronously Provider.of<WalletProvider>(context, listen: false); - // ignore: unused_local_variable - final result = - await OrganisationFactoryContractWriteFunctions.createOrganisation( + await OrganisationFactoryContractWriteFunctions.createOrganisation( walletProvider: walletProvider, name: name, description: description, organisationPhotoHash: _uploadedImageHash ?? "");
120-121: Add mounted check before navigation.After the async
submitDetails()operation completes, the widget may be unmounted. Verify the widget is still mounted before callingcontext.push().Apply this diff:
} // ignore: use_build_context_synchronously + if (!mounted) return; context.push('/organisations'); }lib/pages/mint_nft/mint_nft_images.dart (3)
40-50: Don’t use BuildContext after await; get Provider before awaiting.This removes the need to suppress
use_build_context_synchronously.Apply this diff:
Future<void> _pickAndUploadImages() async { try { - final List<XFile> images = await _picker.pickMultiImage(); + final provider = context.read<MintNftProvider>(); + final List<XFile> images = await _picker.pickMultiImage(); if (images.isEmpty) return; logger.d('Selected ${images.length} images for upload'); - // ignore: use_build_context_synchronously - final provider = Provider.of<MintNftProvider>(context, listen: false); + // provider captured before await; safe to use
57-71: Add mounted guard during per‑image updates.Prevents setState on a disposed widget if the user navigates away mid‑upload.
Apply this diff:
for (int i = 0; i < images.length; i++) { - setState(() { + if (!mounted) return; + setState(() { _uploadingIndex = i; }); try { File imageFile = File(images[i].path); String? hash = await uploadToIPFS(imageFile, (isUploading) {}); if (hash != null) { newHashes.add(hash); - setState(() { + if (!mounted) return; + setState(() { _uploadedHashes.add(hash); });
270-286: Spinner and overlay color are the same; progress indicator becomes invisible.Use a semi‑transparent overlay and contrasting spinner color.
Apply this diff:
- Container( + Container( width: 120, height: 120, decoration: BoxDecoration( - color: getThemeColors(context)['primary'], + color: getThemeColors(context)['secondaryBackground']?.withOpacity(0.7) ?? Colors.black54, borderRadius: BorderRadius.circular(8), ), child: Center( - child: CircularProgressIndicator( - color: getThemeColors(context)['primary'], - ), + child: CircularProgressIndicator( + color: getThemeColors(context)['textPrimary'] ?? Colors.white, + ), ), ),lib/components/universal_navbar.dart (1)
319-333: Incorrect use of Flexible inside SizedBox causes runtime assertionFlexible must be a descendant of a Flex (e.g., Row) and cannot be wrapped by SizedBox. This will assert at runtime and also constrains width to 10px, making the address unreadable.
- SizedBox( - width: 10, - child: Flexible( - child: Text( - formatAddress(walletProvider.currentAddress!), - ... - overflow: TextOverflow.ellipsis, - ), - ), - ), + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 90), + child: Text( + formatAddress(walletProvider.currentAddress!), + style: TextStyle( + color: Colors.green[700], + fontWeight: FontWeight.w600, + fontSize: 10, + ), + overflow: TextOverflow.ellipsis, + ), + ),lib/widgets/nft_display_utils/tree_nft_details_verifiers_widget.dart (1)
56-58: Guard substring on short/invalid addressesshortAddress assumes length >= 10; will throw on short strings.
- String get shortAddress { - return "${address.substring(0, 6)}...${address.substring(address.length - 4)}"; - } + String get shortAddress { + if (address.length <= 10) return address; + return "${address.substring(0, 6)}...${address.substring(address.length - 4)}"; + }lib/pages/mint_nft/mint_nft_details.dart (1)
313-317: Fix hint text contrast (currently same as background, effectively invisible) in TextField and Dropdown. This is an accessibility/readability issue.- hintStyle: TextStyle( - color: getThemeColors(context)['background'], - fontSize: 14, - ), + hintStyle: TextStyle( + color: getThemeColors(context)['textSecondary'], + fontSize: 14, + ),And similarly for the dropdown’s InputDecoration.hintStyle.
Also applies to: 373-385
lib/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_read_services.dart (1)
100-121: Validate the provided currentAddress, not walletProvider.currentAddress; current code checks an unused variable and may mask bad input.- final String address = walletProvider.currentAddress.toString(); - if (!address.startsWith('0x')) { + if (!currentAddress.startsWith('0x')) { return ContractReadResult.error( errorMessage: 'Invalid wallet address format', ); } - final EthereumAddress userAddress = - EthereumAddress.fromHex(currentAddress); + final EthereumAddress userAddress = EthereumAddress.fromHex(currentAddress);
🧹 Nitpick comments (52)
lib/utils/services/ipfs_services.dart (1)
6-7: LGTM! Correctly updated to use Pinata-specific environment variables.The changes align perfectly with the
.env.stencilupdates and maintain consistency across the codebase.Consider improving error handling for missing credentials. Currently, empty fallbacks will cause authentication failures that may be difficult to debug. You could add validation:
-String apiKey = dotenv.get('PINATA_API_KEY', fallback: ""); -String apiSecret = dotenv.get('PINATA_API_SECRET', fallback: ""); +String _getRequiredEnv(String key) { + final value = dotenv.get(key, fallback: ""); + if (value.isEmpty) { + throw Exception('Required environment variable $key is not set'); + } + return value; +} + +String apiKey = _getRequiredEnv('PINATA_API_KEY'); +String apiSecret = _getRequiredEnv('PINATA_API_SECRET');This would fail fast with a clear error message rather than silently using empty credentials.
lib/widgets/map_widgets/static_map_display_widget.dart (1)
92-98: Make initial zoom configurable.Hardcoding 10.0 reduces reuse. Expose an optional
initialZoomprop with a sensible default.-class StaticCoordinatesMap extends StatefulWidget { - final double lat; - final double lng; - const StaticCoordinatesMap({super.key, required this.lat, required this.lng}); +class StaticCoordinatesMap extends StatefulWidget { + final double lat; + final double lng; + final double initialZoom; + const StaticCoordinatesMap({ + super.key, + required this.lat, + required this.lng, + this.initialZoom = 10.0, + });- initialZoom: 10.0, + initialZoom: widget.initialZoom,lib/utils/constants/contract_abis/tree_nft_contract_details.dart (1)
4-1633: Consider moving large ABI strings to assets.Embedding a ~1.6k‑line string increases diff noise and risk of manual drift. Store JSON under
assets/abis/*.jsonand load at init; keep a small typed accessor.lib/utils/constants/contract_abis/organisation_contract_details.dart (1)
3-1144: ABI growth: adopt assets + generated types.Like the factory/tree ABIs, consider moving to asset JSON and generating bindings (e.g., via build_runner) to reduce manual errors and ease refactors.
lib/utils/services/contract_functions/organisation_factory_contract.dart/organisation_factory_contract_read_functions.dart (2)
57-58: Consider making pagination parameters configurable.The offset and limit values are hardcoded, which limits flexibility for pagination use cases. Consider accepting these as method parameters with defaults.
Apply this diff to make pagination configurable:
static Future<ContractReadResult> getOrganisationsByUser({ required WalletProvider walletProvider, + int offset = 0, + int limit = 100, }) async { try { if (!walletProvider.isConnected) { logger.e("Wallet not connected for reading organisations"); return ContractReadResult.error( errorMessage: 'Please connect your wallet before reading NFTs.', ); } final String address = walletProvider.currentAddress.toString(); if (!address.startsWith('0x')) { return ContractReadResult.error( errorMessage: 'Invalid wallet address format', ); } final EthereumAddress userAddress = EthereumAddress.fromHex(address); final List<dynamic> args = [ userAddress, - BigInt.from(0), // offset - BigInt.from(100), // limit - fetch up to 100 organisations + BigInt.from(offset), + BigInt.from(limit), ];
72-74: Add defensive null checks for totalCount parsing.The parsing logic assumes
result[1]exists and can be converted to int without error. Consider adding defensive checks.Apply this diff:
final organisations = result.length > 0 ? result[0] ?? [] : []; - final totalCount = - result.length > 1 ? int.parse(result[1].toString()) : 0; + final totalCount = result.length > 1 && result[1] != null + ? int.tryParse(result[1].toString()) ?? 0 + : 0; logger.d(organisations);lib/widgets/map_widgets/flutter_map_widget.dart (1)
586-605: Consider applying consistent theming to StaticDisplayMap.The
StaticDisplayMapwidget's bottom bar still uses hardcodedColors.blue(line 593), whileCoordinatesMapnow uses theme colors. For consistency, consider updating this as well.Apply this diff:
Positioned( bottom: 8, left: 8, right: 8, child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: Colors.blue, + color: getThemeColors(context)['primary'], borderRadius: BorderRadius.circular(4), ), child: const Text( "Static display • Use zoom buttons or pinch to zoom", style: TextStyle( color: Colors.white, fontSize: 11, ), textAlign: TextAlign.center, ), ), ),lib/utils/constants/ui/color_constants.dart (1)
59-64: LGTM! Consider consolidating duplicate color definitions.The new
secondaryBorderandshadowtheme colors properly support both dark and light modes. However,secondaryBorderandBNWBorderhave identical values in both modes, which suggests potential redundancy.Consider consolidating these duplicate definitions or documenting their distinct semantic purposes if they're intentionally separate.
lib/main.dart (1)
95-103: Simplify path parameter handling.Line 99 calls
.toString()on a value that's already aString?, then checksisNotEmpty. The null-coalescing and empty check can be simplified.Apply this diff:
GoRoute( path: '/user-profile/:address', name: 'user_profile', builder: (BuildContext context, GoRouterState state) { - final address = state.pathParameters['address'].toString(); + final address = state.pathParameters['address'] ?? ''; return UserProfilePage( - userAddress: address.isNotEmpty ? address : ''); + userAddress: address); }, ),lib/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_write_functions.dart (1)
93-101: Consider including numberOfTrees in the result data.The success result includes latitude, longitude, species, photos, and geoHash, but omits
numberOfTrees. For consistency and completeness, consider adding it to the returned data map.Apply this diff:
return ContractWriteResult.success( transactionHash: txHash, data: { 'latitude': latitude, 'longitude': longitude, 'species': species, 'photos': photos, 'geoHash': geoHash, + 'numberOfTrees': numberOfTrees, }, );lib/utils/services/clipboard_service.dart (1)
5-14: LGTM with minor optimization opportunity.The clipboard utility is well-implemented with proper user feedback via SnackBar and consistent theming.
Consider adding
constkeywords for compile-time optimizations:ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('Address copied to clipboard'), + content: const Text('Address copied to clipboard'), backgroundColor: getThemeColors(context)['primary'], - duration: Duration(seconds: 2), + duration: const Duration(seconds: 2), ), );lib/pages/user_profile_page.dart (1)
27-35: Consider responsive layout instead of fixed dimensions.The hardcoded widths (400) and height (600) may cause layout issues on smaller screens or wider displays:
- On mobile devices <400px width, content will overflow
- The fixed height on
UserNftsWidgetinsideSingleChildScrollViewmay interfere with natural scrolling behaviorConsider using responsive constraints:
child: Column( children: [ - SizedBox( - width: 400, - child: ProfileSectionWidget(userAddress: userAddress)), + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 600), + child: ProfileSectionWidget(userAddress: userAddress), + ), - SizedBox( - width: 400, - height: 600, + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 600), child: UserNftsWidget( isOwnerCalling: true, userAddress: userAddress), ), ], ),lib/pages/settings_page.dart (2)
34-39: Validate Ethereum address format before querying.Reject clearly invalid inputs to save a network call and produce clearer errors.
Apply this diff:
- if (_tokenAddressController.text.trim().isEmpty) { + final addr = _tokenAddressController.text.trim(); + if (addr.isEmpty) { setState(() { _tokenError = 'Please enter a token contract address'; }); return; } + final isEth = RegExp(r'^0x[a-fA-F0-9]{40}$').hasMatch(addr); + if (!isEth) { + setState(() { + _tokenError = 'Invalid address format (expected 0x + 40 hex chars)'; + }); + return; + }
88-89: Prefer route constants over hardcoded strings.Use a centralized constant to avoid drift during route refactors.
lib/pages/mint_nft/mint_nft_images.dart (1)
382-413: Use a route constant for navigation to the submit page.Prevents hardcoded path drift.
lib/utils/constants/tree_species_constants.dart (1)
115-117: Return categories in a stable (sorted) order.Improves UX for dropdowns and tests relying on order.
Apply this diff:
- static List<String> getCategories() { - return speciesByCategory.keys.toList(); - } + static List<String> getCategories() { + final list = speciesByCategory.keys.toList(); + list.sort(); + return list; + }lib/pages/mint_nft/mint_nft_organisation.dart (1)
564-573: Substring without length guard can throw if the string is shorter than expected.Use a safe formatter (like the one in SettingsPage) or guard the length.
Apply this diff:
- '${organisation.substring(0, 6)}...${organisation.substring(organisation.length - 4)}', + (organisation.length > 10) + ? '${organisation.substring(0, 6)}...${organisation.substring(organisation.length - 4)}' + : organisation,Or extract a shared address‑shortening util and reuse it across pages.
lib/utils/services/contract_functions/organisation_contract/organisation_write_functions.dart (1)
155-164: Return richer context in success payload.Include
numberOfTrees(and optionallymetadata) to simplify caller UX.return ContractWriteResult.success( transactionHash: txHash, data: { 'latitude': latitude, 'longitude': longitude, 'species': species, 'photos': photos, 'geoHash': geoHash, + 'numberOfTrees': numberOfTrees, + 'metadata': metadata, }, );lib/providers/mint_nft_provider.dart (2)
12-13: Encapsulate organisationAddress; add getter for consistency.Avoid public mutable field to prevent bypassing notifications and invariants.
- String organisationAddress = ""; + String _organisationAddress = ""; + String getOrganisationAddress() => _organisationAddress; @@ - void setOrganisationAddress(String address) { - organisationAddress = address; + void setOrganisationAddress(String address) { + _organisationAddress = address; notifyListeners(); } @@ - organisationAddress = ""; + _organisationAddress = "";Also applies to: 15-24, 40-43, 83-86
70-73: Guard against invalid tree counts.Prevent negatives (and optionally cap excessively large values).
- void setNumberOfTrees(int numberOfTrees) { - _numberOfTrees = numberOfTrees; + void setNumberOfTrees(int numberOfTrees) { + _numberOfTrees = numberOfTrees < 0 ? 0 : numberOfTrees; notifyListeners(); }lib/pages/trees_page.dart (3)
56-59: Use a route constant.Avoid hard-coded paths; reduces typo risk and eases refactors.
- context.push('/mint-nft'); + context.push(RouteConstants.mintNft);
138-169: Clean up async context use.You already guard with
mounted. Remove// ignore: use_build_context_synchronouslyand preferif (!context.mounted) return;for consistency.- await walletProvider.connectWallet(); - if (!mounted) return; + await walletProvider.connectWallet(); + if (!context.mounted) return; if (walletProvider.isConnected) { - // ignore: use_build_context_synchronously ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Text('Wallet connected successfully!'), - // ignore: use_build_context_synchronously backgroundColor: getThemeColors(context)['primary'], behavior: SnackBarBehavior.floating, ), ); } @@ - if (!mounted) return; - // ignore: use_build_context_synchronously + if (!context.mounted) return; ScaffoldMessenger.of(context).showSnackBar(
24-27: UX nudge (optional): allow viewing trees without a wallet.Consider showing RecentTreesWidget to all; gate only write actions (mint/interact). Helps discovery.
lib/widgets/organisation_details_page/tabs/planting_proposals_tab.dart (2)
396-449: Layout: avoid fixed height + outer scroll.
SingleChildScrollView+SizedBox(height: 500)can clip on small/large screens and fight inner ListView scrolling. Prefer filling available space.- @override - Widget build(BuildContext context) { - return SingleChildScrollView( - child: Column( + @override + Widget build(BuildContext context) { + return Column( children: [ @@ - SizedBox( - height: 500, // Fixed height for the TabBarView - child: TabBarView( + Expanded( + child: TabBarView( controller: _statusTabController, children: [ _buildStatusTabContent(0), // Pending _buildStatusTabContent(1), // Approved _buildStatusTabContent(2), // Rejected ], ), ), ], - ), - ); + ); }
44-49: Load tabs in parallel (optional).You can cut load time by fetching all statuses concurrently.
- Future<void> _loadAllProposals() async { - for (int status = 0; status <= 2; status++) { - await _loadProposalsByStatus(status); - } - } + Future<void> _loadAllProposals() async { + await Future.wait([0,1,2].map(_loadProposalsByStatus)); + }lib/widgets/organisation_details_page/tabs/members_tab.dart (2)
239-248: Border theming consistency for “Add Member” buttonHardcoded black border deviates from the rest of the component using theme border.
- side: const BorderSide(color: Colors.black, width: 1), + side: BorderSide( + color: getThemeColors(context)['border']!, + width: 1, + ),
208-211: List item keys for better diffing/reorder safetyMap-iterated widgets lack Keys, which can cause state mix-ups on updates.
-...organisationOwners - .map((owner) => _buildMemberTile(context, owner, true)), +...organisationOwners + .map((owner) => KeyedSubtree(key: ValueKey('owner_$owner'), + child: _buildMemberTile(context, owner, true))), @@ -...organisationMembers - .map((member) => _buildMemberTile(context, member, false)) +...organisationMembers + .map((member) => KeyedSubtree(key: ValueKey('member_$member'), + child: _buildMemberTile(context, member, false)))Also applies to: 254-256
lib/widgets/nft_display_utils/recent_trees_widget.dart (2)
146-151: Color.withValues is not widely supported; use withOpacitywithValues may not exist on older stable channels; withOpacity is portable.
- color: Colors.black.withValues(alpha: 0.2), + color: Colors.black.withOpacity(0.2),
563-572: Infinite-scroll trigger and loading indicator duplication
- Equality check against maxScrollExtent is brittle; use a threshold.
- Loading indicator is rendered both in the list and below it.
- itemCount arithmetic includes a dead term and doesn’t reserve space for the loader when hasMore.
- onNotification: (ScrollNotification scrollInfo) { - if (!_isLoading && - _hasMore && - scrollInfo.metrics.pixels == - scrollInfo.metrics.maxScrollExtent) { - _loadTrees(loadMore: true); - } - return false; - }, + onNotification: (ScrollNotification sn) { + if (!_isLoading && _hasMore && + sn.metrics.pixels >= sn.metrics.maxScrollExtent - 200) { + _loadTrees(loadMore: true); + } + return false; + }, @@ - itemCount: _trees.length + - (_hasMore && !_isLoading ? 0 : 0) + - (_isLoading ? 1 : 0), + itemCount: _trees.length + (_isLoading && _hasMore ? 1 : 0), @@ - } else if (_isLoading) { + } else if (_isLoading && _hasMore) { return _buildLoadingIndicator(); } return const SizedBox.shrink();And remove the extra bottom indicator:
- // Loading indicator at bottom when loading more - if (_isLoading && _trees.isNotEmpty) _buildLoadingIndicator(),Also applies to: 574-586, 590-592
lib/utils/services/contract_functions/planter_token_contract/planter_token_read_services.dart (1)
129-136: Null-safety on read results and reduce latency via parallel reads
- You check success flags but not data presence; accessing data[0] may throw if provider returns empty data on success=false edge cases.
- The four reads can be parallelized.
- // Call owner function - final ownerResult = await walletProvider.readContract( ... ); - // Call getPlanterAddress function - final planterResult = await walletProvider.readContract( ... ); - // Call name function - final nameResult = await walletProvider.readContract( ... ); - // Call symbol function - final symbolResult = await walletProvider.readContract( ... ); + final futures = await Future.wait([ + walletProvider.readContract( + contractAddress: tokenContractAddress, + abi: contractAbi, + functionName: 'owner', + params: [], + ), + walletProvider.readContract( + contractAddress: tokenContractAddress, + abi: contractAbi, + functionName: 'getPlanterAddress', + params: [], + ), + walletProvider.readContract( + contractAddress: tokenContractAddress, + abi: contractAbi, + functionName: 'name', + params: [], + ), + walletProvider.readContract( + contractAddress: tokenContractAddress, + abi: contractAbi, + functionName: 'symbol', + params: [], + ), + ]); + final ownerResult = futures[0]; + final planterResult = futures[1]; + final nameResult = futures[2]; + final symbolResult = futures[3]; @@ - if (!ownerResult.success || - !planterResult.success || - !nameResult.success || - !symbolResult.success) { + if (!ownerResult.success || ownerResult.data == null || + !planterResult.success || planterResult.data == null || + !nameResult.success || nameResult.data == null || + !symbolResult.success || symbolResult.data == null) { return PlanterTokenReadResult.error( errorMessage: 'Failed to fetch token details from contract.', ); }Also applies to: 144-152
lib/pages/organisations_pages/organisation_details_page.dart (2)
114-130: Avoid using BuildContext after await without mounted checkThe lints are suppressed, but these calls can throw if the page is disposed mid-await.
- if (result.success) { - // ignore: use_build_context_synchronously - TransactionDialog.showSuccess( + if (!mounted) return; + if (result.success) { + TransactionDialog.showSuccess( context, ... ); - } else { - // ignore: use_build_context_synchronously - TransactionDialog.showError( + } else { + if (!mounted) return; + TransactionDialog.showError( context, ... );Also applies to: 161-177
301-307: Validate entered address before submitting addMemberPrevent cheap contract reverts and improve UX by rejecting invalid addresses locally.
- if (address.isNotEmpty) { + final isValid = RegExp(r'^0x[a-fA-F0-9]{40}$').hasMatch(address); + if (isValid) { Navigator.of(context).pop(); addMember(address.toString()); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: const Text('Enter a valid 0x wallet address'), + backgroundColor: getThemeColors(context)['error'],), + ); }lib/pages/mint_nft/submit_nft_page.dart (1)
21-23: Remove unused state.isLoading is declared but not used.
- bool isLoading = false; bool isMinting = false;lib/utils/services/contract_functions/organisation_contract/organisation_read_functions.dart (2)
51-57: Use canonical address access and validation.currentAddress.toString() + startsWith('0x') is brittle. Prefer EthereumAddress.hex and validate inputs with EthereumAddress.fromHex to catch malformed addresses early.
- final String address = walletProvider.currentAddress.toString(); - if (!address.startsWith('0x')) { + final String address = walletProvider.currentAddress.hexEip55; + if (address.isEmpty) { return ContractReadResult.error( errorMessage: 'Invalid wallet address format', ); }Optionally, validate organisationContractAddress similarly before readContract.
132-170: Pagination args: add basic bounds.Clamp offset/limit (e.g., offset ≥ 0, 1 ≤ limit ≤ 1000) to avoid large reads from untrusted callers.
lib/widgets/organisation_details_page/tabs/verification_requests_tab.dart (1)
45-49: Load tabs in parallel to reduce latency.Requests are awaited serially. Use Future.wait to fetch all three statuses concurrently.
- Future<void> _loadAllRequests() async { - for (int status = 0; status <= 2; status++) { - await _loadRequestsByStatus(status); - } - } + Future<void> _loadAllRequests() async { + await Future.wait([0, 1, 2].map(_loadRequestsByStatus)); + }lib/widgets/nft_display_utils/tree_nft_view_details_with_map.dart (1)
382-401: Consider higher-contrast text on colored value chips.Text uses textPrimary over a colored background; ensure theme guarantees contrast, or switch to textSecondary/white for readability.
lib/pages/mint_nft/mint_nft_details.dart (5)
35-41: Min count guard is good; consider also capping at the UI max (100) in submitDetails to keep business rules consistent if this widget is reused.- if (numberOfTrees <= 0) { + if (numberOfTrees <= 0 || numberOfTrees > 100) { _showCustomSnackBar( - "Please select at least 1 tree.", + "Please select between 1 and 100 trees.", isError: true, ); return; }
43-49: Batch provider updates to avoid multiple notifyListeners-triggered rebuilds. Expose a single setDetails({...}) or wrap notifies.-Provider.of<MintNftProvider>(context, listen: false).setDescription(description); -Provider.of<MintNftProvider>(context, listen: false).setSpecies(selectedSpecies!); -Provider.of<MintNftProvider>(context, listen: false).setNumberOfTrees(numberOfTrees); +final mint = Provider.of<MintNftProvider>(context, listen: false); +mint.setDetails( + description: description, + species: selectedSpecies!, + numberOfTrees: numberOfTrees, +);
332-405: Dropdown theming mixes hard-coded Colors.white/FAEB96 with themed surfaces. Use theme tokens for dark/light support.- Container( - decoration: BoxDecoration( - color: Colors.white, + Container( + decoration: BoxDecoration( + color: getThemeColors(context)['background'], borderRadius: BorderRadius.circular(16), - border: Border.all( - color: const Color(0xFFFAEB96), + border: Border.all( + color: getThemeColors(context)['border']!, width: 2, ),
283-289: Avoid hard-coded brand colors in labels; route through theme tokens to keep skins consistent.- style: const TextStyle( + style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, - color: Color(0xFF1CD381), + color: getThemeColors(context)['primary'], ),Apply similarly to “Tree Species”, “Number of Trees”, and the count label styles.
Also applies to: 350-357, 429-435, 493-504
450-471: Accessibility: add Semantics for the +/- buttons and the number display for screen readers.- child: ElevatedButton( + child: Semantics( + button: true, + label: 'Decrease number of trees', + child: ElevatedButton( onPressed: () { ... } ), + ) ... - child: ElevatedButton( + child: Semantics( + button: true, + label: 'Increase number of trees', + child: ElevatedButton( onPressed: () { ... } ), + )Also applies to: 511-532
lib/widgets/profile_widgets/user_profile_viewer_widget.dart (2)
163-171: Remove the Access-Control-Allow-Origin request header; it’s a response header and has no effect here.- headers: { - 'Access-Control-Allow-Origin': '*', - },
466-501: Token amount math divides BigInt-like values by 1e18 as double; large values will lose precision. Prefer formatting from BigInt with fixed decimals.String formatAmountFromWei(BigInt raw, {int decimals = 18, int sig = 2}) { final s = raw.toString().padLeft(decimals + 1, '0'); final i = s.substring(0, s.length - decimals); final f = s.substring(s.length - decimals).replaceFirst(RegExp(r'0+$'), ''); final base = f.isEmpty ? i : '$i.$f'; final d = double.tryParse(base) ?? 0; if (d >= 1e6) return '${(d/1e6).toStringAsFixed(1)}M'; if (d >= 1e3) return '${(d/1e3).toStringAsFixed(1)}K'; return d >= 1 ? d.toStringAsFixed(sig) : d.toStringAsFixed(4); }Use it in collapsed/expanded token renderers with BigInt input.
Also applies to: 550-568
lib/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_read_services.dart (2)
230-271: Guard against unexpected contract tuples (length/shape) to avoid RangeError when ABI changes.- final tree = - treeDetailsResult.length > 0 ? treeDetailsResult[0] ?? [] : []; + final tree = (treeDetailsResult.isNotEmpty && treeDetailsResult[0] is List) + ? treeDetailsResult[0] + : []; ... - final verifiers = - treeVerifiersResult.length > 0 ? treeVerifiersResult[0] ?? [] : []; + final verifiers = (treeVerifiersResult.isNotEmpty && treeVerifiersResult[0] is List) + ? treeVerifiersResult[0] + : [];Similarly, verify tree list element counts before indexing in getRecentTreesPaginated.
Also applies to: 341-364
162-228: getProfileDetails and getProfileDetailsByAddress largely duplicate logic; consider a private helper that accepts a target address to reduce drift.lib/widgets/profile_widgets/profile_section_widget.dart (6)
30-88: Parsing handles heterogeneous tuples; good fallback. Consider early-length checks to avoid noisy exceptions and logs.- try { - if (data is List && data.length >= 7) { + try { + if (data is! List || data.length < 7) { + throw Exception('Invalid VerificationDetails structure'); + }
139-174: fromContractData requires ≥8 fields; good. Consider logging at debug level only; current logs are quite verbose and run on every profile load.
371-438: Avoid logging from within build (expensive and noisy). Remove logger.d calls inside builders unless behind a debug flag.
728-741: Token amount uses double division by 1e18; prefer a BigInt→decimal formatter to avoid precision loss (see earlier suggestion in user_profile_viewer_widget).
810-839: Nice expandable token bubbles. Add Semantics labels for copy actions and token amounts to improve accessibility.- Text( + Semantics( + label: 'Token amount $amount', + child: Text( formatAmount(amount), ... - ), + ), + ),And add Semantics to the copy IconButton (label: 'Copy contract address').
Also applies to: 842-937
984-1006: Great not-registered state. Consider adding a small explanatory line about wallet connection if that’s a common cause.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (50)
.env.stencil(1 hunks)lib/components/bottom_navigation_widget.dart(2 hunks)lib/components/transaction_dialog.dart(1 hunks)lib/components/universal_navbar.dart(4 hunks)lib/components/wallet_connect_dialog.dart(2 hunks)lib/main.dart(3 hunks)lib/pages/home_page.dart(1 hunks)lib/pages/mint_nft/mint_nft_coordinates.dart(9 hunks)lib/pages/mint_nft/mint_nft_details.dart(12 hunks)lib/pages/mint_nft/mint_nft_images.dart(4 hunks)lib/pages/mint_nft/mint_nft_organisation.dart(1 hunks)lib/pages/mint_nft/submit_nft_page.dart(10 hunks)lib/pages/organisations_pages/create_organisation.dart(1 hunks)lib/pages/organisations_pages/organisation_details_page.dart(1 hunks)lib/pages/organisations_pages/user_organisations_page.dart(1 hunks)lib/pages/register_user_page.dart(6 hunks)lib/pages/settings_page.dart(2 hunks)lib/pages/tree_details_page.dart(16 hunks)lib/pages/trees_page.dart(1 hunks)lib/pages/user_profile_page.dart(1 hunks)lib/providers/mint_nft_provider.dart(4 hunks)lib/providers/wallet_provider.dart(2 hunks)lib/utils/constants/contract_abis/organisation_contract_details.dart(1 hunks)lib/utils/constants/contract_abis/organisation_factory_contract_details.dart(1 hunks)lib/utils/constants/contract_abis/tree_nft_contract_details.dart(1 hunks)lib/utils/constants/route_constants.dart(1 hunks)lib/utils/constants/tree_species_constants.dart(1 hunks)lib/utils/constants/ui/color_constants.dart(1 hunks)lib/utils/services/clipboard_service.dart(1 hunks)lib/utils/services/contract_functions/organisation_contract/organisation_read_functions.dart(1 hunks)lib/utils/services/contract_functions/organisation_contract/organisation_write_functions.dart(1 hunks)lib/utils/services/contract_functions/organisation_factory_contract.dart/organisation_factory_contract_read_functions.dart(2 hunks)lib/utils/services/contract_functions/planter_token_contract/planter_token_read_services.dart(1 hunks)lib/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_read_services.dart(8 hunks)lib/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_write_functions.dart(3 hunks)lib/utils/services/ipfs_services.dart(1 hunks)lib/widgets/basic_scaffold.dart(2 hunks)lib/widgets/map_widgets/flutter_map_widget.dart(1 hunks)lib/widgets/map_widgets/static_map_display_widget.dart(1 hunks)lib/widgets/nft_display_utils/recent_trees_widget.dart(1 hunks)lib/widgets/nft_display_utils/tree_nft_details_verifiers_widget.dart(16 hunks)lib/widgets/nft_display_utils/tree_nft_view_details_with_map.dart(1 hunks)lib/widgets/nft_display_utils/user_nfts_widget.dart(5 hunks)lib/widgets/organisation_details_page/tabs/info_tab.dart(1 hunks)lib/widgets/organisation_details_page/tabs/members_tab.dart(1 hunks)lib/widgets/organisation_details_page/tabs/planting_proposals_tab.dart(1 hunks)lib/widgets/organisation_details_page/tabs/tabs.dart(1 hunks)lib/widgets/organisation_details_page/tabs/verification_requests_tab.dart(1 hunks)lib/widgets/profile_widgets/profile_section_widget.dart(19 hunks)lib/widgets/profile_widgets/user_profile_viewer_widget.dart(1 hunks)
🔇 Additional comments (40)
.env.stencil (1)
3-4: Verification complete—rename is safe and properly implemented.The search confirms no references to the old generic
API_KEYorAPI_SECRETenvironment variables exist in the codebase. The newPINATA_API_KEYandPINATA_API_SECRETare already correctly used inlib/utils/services/ipfs_services.dart(lines 6-7), and no other.envfiles require updating.The change is complete and introduces no breaking dependencies.
lib/pages/organisations_pages/user_organisations_page.dart (2)
8-8: Original review comment is incorrect; no action required.The import path is valid. File discovery confirms the directory is actually named
organisation_factory_contract.dart, and Dart's package resolution correctly resolves the import to the existing file. While the.dartsuffix on a directory is unconventional, it poses no technical issue. The suggested diff would break the import.Likely an incorrect or invalid review comment.
111-118: Data structure is correct, but pagination parameters are hardcoded (not exposed).The wrapper correctly maps ABI field
orgsto theorganisationskey and returnstotalCountas expected. The consumer code at line 115 properly accessesdata['organisations']. However,getOrganisationsByUser()hardcodes offset to0and limit to100without exposing them as parameters—unlike other similar paginated functions in the codebase. If pagination control is needed later, the function signature must be updated to accept optionaloffsetandlimitparameters.lib/utils/constants/contract_abis/organisation_factory_contract_details.dart (1)
3-839: Now let me search for the actual call sites where these organisation factory functions are invoked:Now let me search for the actual usage/call sites of these functions in the codebase:The web search isn't helping find the specific call sites. Let me run a script directly on the codebase to find where these functions are called:Based on my analysis of the provided script output and codebase context, I have enough information to generate a rewritten review comment. The script output shows that the ABI has been updated with pagination parameters and return structures, but I need to conclusively determine if there are actual integration issues or if the comment is speculative.Verify client read functions after ABI pagination/renames.
Multiple organisation factory functions now require
offset/limitparameters and returnorgs(or structured details) alongsidetotalCount. However, from the codebase examination:
ABI Updated: The
organisation_factory_contract_details.dartABI correctly reflects the new signatures with pagination inputs and outputs renamed toorgs/totalCount.Read Functions Updated:
organisation_factory_contract_read_functions.dartcorrectly parsesresult[0]as organisations array andresult[1]astotalCount(lines 72-80), returning them in the result data map.Consumer Code: Existing call sites in widgets (e.g.,
user_nfts_widget.dart,recent_trees_widget.dart) properly extracttotalCountand handle pagination withhasMorechecks. Similar patterns exist for tree NFT functions that already use paginated returns.Missing Bounds Validation: No explicit validation on
limitparameter bounds is evident. When calling contract functions, ensure maximum page size is enforced (e.g.,limit > 0andlimit <= maxPageSize).Key Names Consistency: Returned data keys match expectations (
organisations,totalCount, etc.), though verify consumer code maps align with actual function return names (e.g.,getAllOrganisationsreturns(orgs, totalCount)— confirm mappers use the correct keys).Action: Add bounds validation for pagination
limitparameter; verify all call sites passoffsetandlimitto the updated functions and handle the paginated response structure.lib/utils/constants/contract_abis/organisation_contract_details.dart (1)
1145-1146: Remove unused constant reading from incorrect env var.The
organisationContractAddressconstant (lines 1145–1146) is dead code that reads fromORGANISATION_FACTORY_CONTRACT_ADDRESS. Verification confirms the constant is never referenced anywhere in the codebase—all contract operations dynamically pass org addresses via function parameters. Remove it entirely.-final String organisationContractAddress = - dotenv.env['ORGANISATION_FACTORY_CONTRACT_ADDRESS'] ?? '';lib/utils/services/contract_functions/organisation_factory_contract.dart/organisation_factory_contract_read_functions.dart (1)
7-35: LGTM!The
ContractReadResultclass provides a clean, standardized wrapper for contract read operations with proper success/error handling and optional data payload.lib/pages/home_page.dart (1)
24-28: LGTM!The
ProfileSectionWidgetnow correctly receives the current wallet address as a parameter, aligning with the existing pattern used forUserNftsWidget. The null-safe fallback to an empty string is appropriate.lib/widgets/map_widgets/flutter_map_widget.dart (1)
279-279: LGTM!The bottom bar now uses dynamic theme colors via
getThemeColors(context)['primary'], ensuring consistent theming across the application.lib/main.dart (1)
104-110: LGTM!The new organisation-based NFT minting route is properly configured using route constants and wired to
MintNftOrganisationPage.lib/pages/organisations_pages/create_organisation.dart (1)
164-166: LGTM!The migration to
BaseScaffold's back-button API (showBackButton,onBackPressed) and loading state (isLoading) is clean and aligns with the broader theming and navigation enhancements across the codebase.lib/utils/constants/route_constants.dart (1)
5-5: LGTM!The new route constants for organisation-based NFT minting follow the established pattern and are properly defined for both the route name and path.
Also applies to: 12-12
lib/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_write_functions.dart (1)
44-44: LGTM!The
numberOfTreesparameter is properly added to themintNftmethod signature, converted toBigInt, and included in the contract call arguments. This aligns with the PR's enhancement of the minting flow to track tree quantities.Also applies to: 67-67, 81-81
lib/components/bottom_navigation_widget.dart (1)
28-30: LGTM!The migration to centralized theme colors via
getThemeColors(context)improves consistency across the app and aligns with the broader theming refactor in this PR.lib/widgets/nft_display_utils/user_nfts_widget.dart (4)
156-156: LGTM!The addition of the
userAddressparameter enables viewing NFTs for any user, not just the current wallet owner. This aligns with the new user profile viewing capabilities introduced in this PR.
362-373: LGTM!The conditional header text based on address comparison provides clear context to users about whose trees they're viewing. This enhances UX when navigating between personal and other users' profiles.
441-443: LGTM!The personalized empty state messaging improves user experience by distinguishing between viewing your own profile versus another user's profile.
255-255: Formula is correct but inconsistencies exist elsewhere in coordinate handling.The transformation at line 255 correctly inverts the contract's storage formula. The contract stores coordinates as
(value + offset) * 1e6, and line 255 properly reverses this with(value / 1000000) - offset.However, verification revealed inconsistent implementations:
- recent_trees_widget.dart (_convertCoordinate, line 96-98): Subtracts 90 from all coordinates, including longitude (should subtract 180 for longitude)
- planting_proposals_tab.dart (_convertCoordinate, line 122-124): Applies no offset at all, only divides by 1e6
- tree_details_page.dart (192-195) and user_nfts_widget.dart (line 255): Both correctly apply distinct offsets per coordinate
Verify that coordinate handling is intentionally different in these files or if recent_trees_widget.dart and planting_proposals_tab.dart require corrections to match the contract's storage contract.
lib/widgets/organisation_details_page/tabs/tabs.dart (1)
1-4: LGTM!The barrel file provides a clean public API for the organisation details tabs, improving import organization and maintainability.
lib/pages/register_user_page.dart (2)
36-52: LGTM!The migration to
TransactionDialogfor both success and error scenarios provides a consistent user experience across the app and properly displays transaction hashes for blockchain operations.
147-148: LGTM!The updated
BaseScaffoldAPI with explicitshowBackButtonandisLoadingparameters improves clarity and aligns with the scaffold refactoring in this PR.lib/widgets/organisation_details_page/tabs/info_tab.dart (2)
15-19: LGTM!The date formatting helper properly handles the edge case of
timestamp == 0by returning "Unknown", preventing display of incorrect dates.
66-68: LGTM!Graceful handling of empty organization descriptions with a fallback message improves the user experience and prevents displaying an empty container.
lib/providers/wallet_provider.dart (2)
400-407: LGTM!The condition fix from
!sessions.isNotEmptytosessions.isEmptyimproves code readability by removing the double negative, though the logic remains functionally equivalent.
486-522: Excellent session validation guards!The comprehensive session validation before transaction execution prevents common failure scenarios:
- Detects disconnected wallets (empty sessions)
- Identifies expired sessions
- Validates account availability
This provides better error messages to users and prevents transaction failures downstream. The logging additions will aid in debugging connection issues.
lib/widgets/basic_scaffold.dart (1)
72-83: All new parameters verified in UniversalNavbar component.Confirmed that UniversalNavbar (lib/components/universal_navbar.dart) defines and implements all five parameters: showBackButton, isLoading, onBackPressed, onReload, and showReloadButton. The pass-through from basic_scaffold.dart is correct and will compile without issues.
lib/pages/mint_nft/mint_nft_organisation.dart (1)
10-10: The import path is valid and correct. The file exists at the exact location specified:lib/utils/services/contract_functions/organisation_factory_contract.dart/organisation_factory_contract_read_functions.dart. Whileorganisation_factory_contract.dartis an unconventional directory name (typically.dartdenotes files), it is a legitimate directory in this codebase, and the import path resolves properly.Likely an incorrect or invalid review comment.
lib/widgets/nft_display_utils/recent_trees_widget.dart (1)
96-105: Fix inconsistent coordinate conversion offsetThe review comment's BigInt concern is incorrect—contract read services already convert
BigInttoint(lines 345-346 of tree_nft_contract_read_services.dart). However, the coordinate offset issue is real but incomplete.The
-90offset applied to both latitude and longitude is inconsistent. The planting_proposals_tab.dart widget (line 124) correctly uses onlycoordinate / 1000000.0without offset, which aligns with standard fixed-point coordinate encoding. Remove the- 90.0offset:double _convertCoordinate(int coordinate) { // Convert from fixed-point representation to decimal degrees - return (coordinate / 1000000.0) - 90.0; + return coordinate / 1000000.0; }The
_formatDate()function is already safe—the contract read service ensures all numeric fields are converted tointbefore reaching this widget. No type changes needed.Likely an incorrect or invalid review comment.
lib/pages/mint_nft/submit_nft_page.dart (2)
134-136: Clarify scope of clearData after success.clearData() currently runs for both org and individual flows, while the comment mentions only organisation address. Confirm intended behavior; gate by isOrganisationMint if you mean to clear only org-related drafts, or update the comment.
- // Clear the organisation address after successful submission - mintNftProvider.clearData(); + // Clear minting draft data after success (org and individual) + mintNftProvider.clearData(); + // If only org data should be cleared, gate with: if (isOrganisationMint) { ... }
121-132: Nice: unified success dialog with optional hash.lib/components/transaction_dialog.dart (1)
211-255: Good API: post-frame, mounted checks, non-dismissible.lib/pages/mint_nft/mint_nft_coordinates.dart (2)
200-209: Verify downstream acceptance of “fallback_…” geohash.Contracts or indexers might require a valid geohash charset/length. Confirm this sentinel format won’t break writes/reads. If uncertain, prefer blocking with an error and asking the user to retry.
128-136: Good: strict numeric parsing and explicit range errors.lib/utils/services/contract_functions/organisation_contract/organisation_read_functions.dart (1)
106-123: Nice: member/owner checks with case-insensitive compare.lib/widgets/organisation_details_page/tabs/verification_requests_tab.dart (1)
364-419: Solid tabbed UI with clear empty/error states.lib/widgets/nft_display_utils/tree_nft_view_details_with_map.dart (1)
76-118: Nice header/container refactor; consistent theming.lib/pages/mint_nft/mint_nft_details.dart (2)
25-33: Strong validation for description/species; null/empty checks look correct.
52-53: Route constant verified and registered. The constantRouteConstants.mintNftOrganisationPathis defined atlib/utils/constants/route_constants.dart:12and registered as a GoRoute atlib/main.dart:105. The navigation call is correct.lib/pages/tree_details_page.dart (2)
560-570: BaseScaffold flags and loading indicator integration look good; back/reload wired appropriately.Also applies to: 590-614
56-63: Confirm pagination intent: using_visibleVerifiersCountstops loading before_totalVerifiersCount.The pagination condition uses only
_visibleVerifiersCountand ignores_totalVerifiersCount. If the contract returnsvisibleCount < totalCount(indicating hidden/invisible verifiers), pagination will halt before reaching the total count. Verify this is the intended UX behavior—whether invisible verifiers should remain unloaded or whether pagination should instead usetotalCount.lib/widgets/profile_widgets/profile_section_widget.dart (1)
1139-1171: Overall render flow and states are clean and predictable. LGTM.
| child: Text( | ||
| '${transactionHash!.substring(0, 10)}...${transactionHash!.substring(transactionHash!.length - 8)}', | ||
| style: TextStyle( | ||
| fontSize: 12, | ||
| fontFamily: 'monospace', | ||
| color: colors['textPrimary'], | ||
| ), | ||
| ), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard short hashes to prevent RangeError.
Substring indexes assume length ≥ 18. Build the preview defensively.
- Expanded(
- child: Text(
- '${transactionHash!.substring(0, 10)}...${transactionHash!.substring(transactionHash!.length - 8)}',
+ Expanded(
+ child: Text(
+ () {
+ final h = transactionHash!;
+ if (h.length <= 10) return h;
+ final left = h.substring(0, 10);
+ final right = h.length > 18 ? h.substring(h.length - 8) : '';
+ return right.isEmpty ? left : '$left...$right';
+ }(),
style: TextStyle(
fontSize: 12,
fontFamily: 'monospace',
color: colors['textPrimary'],
),
),Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In lib/components/transaction_dialog.dart around lines 128 to 135, the substring
calls assume the transactionHash length is at least 18 and will throw a
RangeError for shorter hashes; update the UI rendering to defensively handle
short or null hashes by computing safe start/end indices (or simply display the
full hash when length is below the truncation threshold) and avoid
force-unwrapping nulls—use length checks (or fallback empty string) and only
perform substring/truncation when indexes are valid so the preview never tries
to access out-of-range positions.
| onTap: () async { | ||
| try { | ||
| await walletProvider.openWallet(wallet, uri); | ||
| // ignore: use_build_context_synchronously | ||
| Navigator.of(context).pop(); | ||
| } catch (e) { | ||
| // ignore: use_build_context_synchronously | ||
| Navigator.of(context).pop(); | ||
| // ignore: use_build_context_synchronously | ||
| ScaffoldMessenger.of(context).showSnackBar( | ||
| SnackBar( | ||
| content: Text(e.toString()), | ||
| backgroundColor: | ||
| // ignore: use_build_context_synchronously | ||
| getThemeColors(context)['error'], | ||
| ), | ||
| ); | ||
| } | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add context.mounted guard after await.
Avoid using a stale BuildContext; remove lint suppressions.
- onTap: () async {
+ onTap: () async {
try {
await walletProvider.openWallet(wallet, uri);
- // ignore: use_build_context_synchronously
- Navigator.of(context).pop();
+ if (!context.mounted) return;
+ Navigator.of(context).pop();
} catch (e) {
- // ignore: use_build_context_synchronously
- Navigator.of(context).pop();
- // ignore: use_build_context_synchronously
- ScaffoldMessenger.of(context).showSnackBar(
+ if (!context.mounted) return;
+ Navigator.of(context).pop();
+ ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(e.toString()),
backgroundColor:
- // ignore: use_build_context_synchronously
getThemeColors(context)['error'],
),
);
}
},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| onTap: () async { | |
| try { | |
| await walletProvider.openWallet(wallet, uri); | |
| // ignore: use_build_context_synchronously | |
| Navigator.of(context).pop(); | |
| } catch (e) { | |
| // ignore: use_build_context_synchronously | |
| Navigator.of(context).pop(); | |
| // ignore: use_build_context_synchronously | |
| ScaffoldMessenger.of(context).showSnackBar( | |
| SnackBar( | |
| content: Text(e.toString()), | |
| backgroundColor: | |
| // ignore: use_build_context_synchronously | |
| getThemeColors(context)['error'], | |
| ), | |
| ); | |
| } | |
| }, | |
| onTap: () async { | |
| try { | |
| await walletProvider.openWallet(wallet, uri); | |
| if (!context.mounted) return; | |
| Navigator.of(context).pop(); | |
| } catch (e) { | |
| if (!context.mounted) return; | |
| Navigator.of(context).pop(); | |
| ScaffoldMessenger.of(context).showSnackBar( | |
| SnackBar( | |
| content: Text(e.toString()), | |
| backgroundColor: | |
| getThemeColors(context)['error'], | |
| ), | |
| ); | |
| } | |
| }, |
🤖 Prompt for AI Agents
In lib/components/wallet_connect_dialog.dart around lines 76 to 94, the code
uses the BuildContext (Navigator and ScaffoldMessenger) after an await without
checking if the widget is still mounted and currently suppresses the lints;
update the onTap handler to check context.mounted immediately after the await
(e.g., if (!context.mounted) return;) before calling Navigator.of(context).pop()
or showing a SnackBar, and remove the // ignore: use_build_context_synchronously
comments so the code no longer suppresses the lint.
| onTap: () async { | ||
| await Clipboard.setData(ClipboardData(text: uri)); | ||
| // ignore: use_build_context_synchronously | ||
| Navigator.of(context).pop(); | ||
| // ignore: use_build_context_synchronously | ||
| ScaffoldMessenger.of(context).showSnackBar( | ||
| SnackBar( | ||
| content: const Text('URI copied to clipboard!'), | ||
| // ignore: use_build_context_synchronously | ||
| backgroundColor: getThemeColors(context)['primary'], | ||
| ), | ||
| ); | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Guard Copy URI flow too.
Same mounted check after Clipboard.setData.
- onTap: () async {
- await Clipboard.setData(ClipboardData(text: uri));
- // ignore: use_build_context_synchronously
- Navigator.of(context).pop();
- // ignore: use_build_context_synchronously
- ScaffoldMessenger.of(context).showSnackBar(
+ onTap: () async {
+ await Clipboard.setData(ClipboardData(text: uri));
+ if (!context.mounted) return;
+ Navigator.of(context).pop();
+ ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('URI copied to clipboard!'),
- // ignore: use_build_context_synchronously
backgroundColor: getThemeColors(context)['primary'],
),
);
},🤖 Prompt for AI Agents
In lib/components/wallet_connect_dialog.dart around lines 145 to 157, after
awaiting Clipboard.setData the code uses Navigator and ScaffoldMessenger with
the build context without confirming the widget is still mounted; add a mounted
check immediately after the await (return early if not mounted) before calling
Navigator.of(context).pop() and showing the SnackBar so you don't use a stale
context.
| try { | ||
| logger.d("Attempting: encode(lat=$lat, lng=$lng)"); | ||
| geohash = geoHasher.encode(lat, lng, precision: 12); | ||
| logger.d("Success with lat, lng order"); | ||
| } catch (e1) { | ||
| logger.d("Failed with lat, lng order: $e1"); | ||
| try { | ||
| // Alternative order: longitude first, latitude second | ||
| logger.d("Attempting: encode(lng=$lng, lat=$lat)"); | ||
| geohash = geoHasher.encode(lng, lat, precision: 12); | ||
| logger.d("Success with lng, lat order"); | ||
| } catch (e2) { | ||
| logger.e("Both parameter orders failed:"); | ||
| logger.e(" lat, lng order error: $e1"); | ||
| logger.e(" lng, lat order error: $e2"); | ||
| throw Exception( | ||
| "Geohash generation failed with both parameter orders. lat,lng: $e1 | lng,lat: $e2"); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don’t swap geohash parameter order.
GeoHash encode is lat, lng. Retrying with lng, lat generates a valid-looking but wrong hash and silently corrupts location.
- try {
- logger.d("Attempting: encode(lat=$lat, lng=$lng)");
- geohash = geoHasher.encode(lat, lng, precision: 12);
- logger.d("Success with lat, lng order");
- } catch (e1) {
- logger.d("Failed with lat, lng order: $e1");
- try {
- // Alternative order: longitude first, latitude second
- logger.d("Attempting: encode(lng=$lng, lat=$lat)");
- geohash = geoHasher.encode(lng, lat, precision: 12);
- logger.d("Success with lng, lat order");
- } catch (e2) {
- logger.e("Both parameter orders failed:");
- logger.e(" lat, lng order error: $e1");
- logger.e(" lng, lat order error: $e2");
- throw Exception(
- "Geohash generation failed with both parameter orders. lat,lng: $e1 | lng,lat: $e2");
- }
- }
+ try {
+ logger.d("Attempting: encode(lat=$lat, lng=$lng)");
+ geohash = geoHasher.encode(lat, lng, precision: 12);
+ logger.d("Geohash generated");
+ } catch (e1) {
+ logger.e("Geohash generation failed: $e1");
+ throw Exception("Geohash generation failed: $e1");
+ }🤖 Prompt for AI Agents
In lib/pages/mint_nft/mint_nft_coordinates.dart around lines 174 to 191, remove
the fallback that retries geoHasher.encode with swapped parameters (lng, lat);
always call geoHasher.encode(lat, lng, precision: 12) and if it throws, log the
original error and rethrow (including the error details) instead of attempting
the lng,lat order which produces incorrect geohashes; ensure any catch only
reports the lat,lng error and fails fast, and optionally validate lat/lng ranges
before encoding to catch bad inputs early.
| Future<void> _loadUserOrganisations() async { | ||
| setState(() { | ||
| _isLoading = true; | ||
| _errorMessage = null; | ||
| }); | ||
|
|
||
| try { | ||
| final walletProvider = | ||
| Provider.of<WalletProvider>(context, listen: false); | ||
|
|
||
| final result = await ContractReadFunctions.getOrganisationsByUser( | ||
| walletProvider: walletProvider, | ||
| ); | ||
|
|
||
| if (result.success && result.data != null) { | ||
| final data = result.data as Map<String, dynamic>; | ||
| final organisations = data['organisations'] as List<dynamic>; | ||
|
|
||
| setState(() { | ||
| _userOrganisations = organisations.map((e) => e.toString()).toList(); | ||
| _isLoading = false; | ||
| }); | ||
|
|
||
| logger.d("User organisations loaded: $_userOrganisations"); | ||
| } else { | ||
| setState(() { | ||
| _errorMessage = result.errorMessage ?? 'Failed to load organisations'; | ||
| _isLoading = false; | ||
| }); | ||
| } | ||
| } catch (e) { | ||
| logger.e("Error loading organisations: $e"); | ||
| setState(() { | ||
| _errorMessage = 'Error loading organisations: $e'; | ||
| _isLoading = false; | ||
| }); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add mounted checks around post‑await setState in _loadUserOrganisations.
Prevents setState after dispose on slow networks.
Apply this diff:
final result = await ContractReadFunctions.getOrganisationsByUser(
walletProvider: walletProvider,
);
+ if (!mounted) return;
if (result.success && result.data != null) {
final data = result.data as Map<String, dynamic>;
final organisations = data['organisations'] as List<dynamic>;
- setState(() {
+ setState(() {
_userOrganisations = organisations.map((e) => e.toString()).toList();
_isLoading = false;
});
} else {
- setState(() {
+ if (!mounted) return;
+ setState(() {
_errorMessage = result.errorMessage ?? 'Failed to load organisations';
_isLoading = false;
});
}
} catch (e) {
logger.e("Error loading organisations: $e");
- setState(() {
+ if (!mounted) return;
+ setState(() {
_errorMessage = 'Error loading organisations: $e';
_isLoading = false;
});
}🤖 Prompt for AI Agents
In lib/pages/mint_nft/mint_nft_organisation.dart around lines 33 to 70, the
method _loadUserOrganisations performs awaits then calls setState which can run
after the widget is disposed; add mounted checks before every post-await
setState (i.e., after the await
ContractReadFunctions.getOrganisationsByUser(...) and before the setState in
both the success and error branches, and inside the catch block before setState)
and return early if !mounted to prevent calling setState after dispose.
| if (result.success && result.data != null) { | ||
| setState(() { | ||
| _requestsByStatus[status] = | ||
| List<Map<String, dynamic>>.from(result.data['requests'] ?? []); | ||
| }); | ||
| } else { | ||
| setState(() { | ||
| _errorMessages[status] = | ||
| result.errorMessage ?? 'Failed to load verification requests'; | ||
| }); | ||
| } | ||
| } catch (e) { | ||
| setState(() { | ||
| _errorMessages[status] = 'Error loading verification requests: $e'; | ||
| }); | ||
| logger.e('Error loading verification requests for status $status: $e'); | ||
| } finally { | ||
| setState(() { | ||
| _isLoading[status] = false; | ||
| }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add mounted checks before setState in async flows.
Widget may be disposed before responses arrive.
- if (result.success && result.data != null) {
- setState(() {
+ if (!mounted) return;
+ if (result.success && result.data != null) {
+ setState(() {
_requestsByStatus[status] =
List<Map<String, dynamic>>.from(result.data['requests'] ?? []);
});
} else {
- setState(() {
+ setState(() {
_errorMessages[status] =
result.errorMessage ?? 'Failed to load verification requests';
});
}
...
- } finally {
- setState(() {
+ } finally {
+ if (!mounted) return;
+ setState(() {
_isLoading[status] = false;
});
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In lib/widgets/organisation_details_page/tabs/verification_requests_tab.dart
around lines 69 to 89, the async result handling calls setState after awaits and
may run after the widget is disposed; add mounted checks before every setState
call (i.e., return early if !mounted) in the success, error, catch, and finally
branches so state is only updated when the State object is still mounted.
| BoxShadow( | ||
| color: getThemeColors(context)['shadow']!.withValues(alpha: 0.1), | ||
| blurRadius: 4, | ||
| offset: const Offset(0, 2), | ||
| ), | ||
| ], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix: Color.withValues doesn’t exist on Flutter 3.16. Use withOpacity.
This won’t compile on v3.16. Replace with withOpacity(0.1).
- BoxShadow(
- color: getThemeColors(context)['shadow']!.withValues(alpha: 0.1),
+ BoxShadow(
+ color: getThemeColors(context)['shadow']!.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| BoxShadow( | |
| color: getThemeColors(context)['shadow']!.withValues(alpha: 0.1), | |
| blurRadius: 4, | |
| offset: const Offset(0, 2), | |
| ), | |
| ], | |
| BoxShadow( | |
| color: getThemeColors(context)['shadow']!.withOpacity(0.1), | |
| blurRadius: 4, | |
| offset: const Offset(0, 2), | |
| ), |
🤖 Prompt for AI Agents
In lib/widgets/organisation_details_page/tabs/verification_requests_tab.dart
around lines 140 to 145, the code calls
getThemeColors(context)['shadow']!.withValues(alpha: 0.1) which does not exist
in Flutter 3.16; replace that call with withOpacity(0.1) on the Color instance
(i.e., getThemeColors(context)['shadow']!.withOpacity(0.1)) to fix the
compilation error.
| logger.d("Calling ContractReadFunctions.getProfileDetails..."); | ||
| final String currentAddress = widget.userAddress; | ||
| final result = await ContractReadFunctions.getProfileDetails( | ||
| walletProvider: walletProvider, | ||
| currentAddress: currentAddress, | ||
| ); | ||
|
|
||
| logger.d("Contract call completed:"); | ||
| logger.d(" - Success: ${result.success}"); | ||
| logger.d(" - Data not null: ${result.data != null}"); | ||
| logger.d(" - Full result data: ${result.data}"); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Use currentAddress param pattern matches read service; good. Add mounted checks before setState after awaits to prevent lifecycle issues.
- final result = await ContractReadFunctions.getProfileDetails(
+ final result = await ContractReadFunctions.getProfileDetails(
walletProvider: walletProvider,
currentAddress: currentAddress,
);
+ if (!mounted) return;Apply similarly to subsequent setState blocks in this method.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| logger.d("Calling ContractReadFunctions.getProfileDetails..."); | |
| final String currentAddress = widget.userAddress; | |
| final result = await ContractReadFunctions.getProfileDetails( | |
| walletProvider: walletProvider, | |
| currentAddress: currentAddress, | |
| ); | |
| logger.d("Contract call completed:"); | |
| logger.d(" - Success: ${result.success}"); | |
| logger.d(" - Data not null: ${result.data != null}"); | |
| logger.d(" - Full result data: ${result.data}"); | |
| logger.d("Calling ContractReadFunctions.getProfileDetails..."); | |
| final String currentAddress = widget.userAddress; | |
| final result = await ContractReadFunctions.getProfileDetails( | |
| walletProvider: walletProvider, | |
| currentAddress: currentAddress, | |
| ); | |
| if (!mounted) return; | |
| logger.d("Contract call completed:"); | |
| logger.d(" - Success: ${result.success}"); | |
| logger.d(" - Data not null: ${result.data != null}"); | |
| logger.d(" - Full result data: ${result.data}"); |
🤖 Prompt for AI Agents
In lib/widgets/profile_widgets/profile_section_widget.dart around lines 254 to
265, after the await call to ContractReadFunctions.getProfileDetails you must
guard any following setState calls with a mounted check to avoid updating state
after the widget is disposed; capture the result, then add "if (!mounted)
return;" before calling setState and wrap the state mutation inside that
setState, and apply the same mounted-check-before-setState pattern to every
other setState block later in this method that runs after an await.
| try { | ||
| final walletProvider = | ||
| Provider.of<WalletProvider>(context, listen: false); | ||
|
|
||
| final result = await ContractReadFunctions.getProfileDetailsByAddress( | ||
| walletProvider: walletProvider, | ||
| userAddress: widget.userAddress, | ||
| ); | ||
|
|
||
| if (result.success && result.data != null) { | ||
| final List data = result.data['profile'] ?? []; | ||
| final List verifierTokensData = result.data['verifierTokens'] ?? []; | ||
|
|
||
| setState(() { | ||
| _isLoading = false; | ||
| if (data.isEmpty) { | ||
| _isNotRegistered = true; | ||
| } else { | ||
| _userProfileData = UserProfileData.fromContractData(data); | ||
| _verifierTokens = verifierTokensData | ||
| .map((token) => VerificationDetails.fromContractData(token)) | ||
| .toList(); | ||
| } | ||
| }); | ||
| } else { | ||
| setState(() { | ||
| _isLoading = false; | ||
| _errorMessage = result.errorMessage ?? 'Failed to load user profile'; | ||
| if (result.errorMessage?.contains('not registered') ?? false) { | ||
| _isNotRegistered = true; | ||
| } | ||
| }); | ||
| } | ||
| } catch (e) { | ||
| logger.e("Error loading user profile", error: e); | ||
| setState(() { | ||
| _isLoading = false; | ||
| _errorMessage = 'Error: ${e.toString()}'; | ||
| }); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Add mounted checks before calling setState after awaits to avoid setState on disposed widget when navigating away mid-load.
- if (result.success && result.data != null) {
+ if (!mounted) return;
+ if (result.success && result.data != null) {
setState(() { ... });
- } else {
+ } else {
+ if (!mounted) return;
setState(() { ... });
}
- } catch (e) {
+ } catch (e) {
logger.e("Error loading user profile", error: e);
- setState(() {
+ if (!mounted) return;
+ setState(() {
_isLoading = false;
_errorMessage = 'Error: ${e.toString()}';
});
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| try { | |
| final walletProvider = | |
| Provider.of<WalletProvider>(context, listen: false); | |
| final result = await ContractReadFunctions.getProfileDetailsByAddress( | |
| walletProvider: walletProvider, | |
| userAddress: widget.userAddress, | |
| ); | |
| if (result.success && result.data != null) { | |
| final List data = result.data['profile'] ?? []; | |
| final List verifierTokensData = result.data['verifierTokens'] ?? []; | |
| setState(() { | |
| _isLoading = false; | |
| if (data.isEmpty) { | |
| _isNotRegistered = true; | |
| } else { | |
| _userProfileData = UserProfileData.fromContractData(data); | |
| _verifierTokens = verifierTokensData | |
| .map((token) => VerificationDetails.fromContractData(token)) | |
| .toList(); | |
| } | |
| }); | |
| } else { | |
| setState(() { | |
| _isLoading = false; | |
| _errorMessage = result.errorMessage ?? 'Failed to load user profile'; | |
| if (result.errorMessage?.contains('not registered') ?? false) { | |
| _isNotRegistered = true; | |
| } | |
| }); | |
| } | |
| } catch (e) { | |
| logger.e("Error loading user profile", error: e); | |
| setState(() { | |
| _isLoading = false; | |
| _errorMessage = 'Error: ${e.toString()}'; | |
| }); | |
| } | |
| } | |
| try { | |
| final walletProvider = | |
| Provider.of<WalletProvider>(context, listen: false); | |
| final result = await ContractReadFunctions.getProfileDetailsByAddress( | |
| walletProvider: walletProvider, | |
| userAddress: widget.userAddress, | |
| ); | |
| if (!mounted) return; | |
| if (result.success && result.data != null) { | |
| final List data = result.data['profile'] ?? []; | |
| final List verifierTokensData = result.data['verifierTokens'] ?? []; | |
| setState(() { | |
| _isLoading = false; | |
| if (data.isEmpty) { | |
| _isNotRegistered = true; | |
| } else { | |
| _userProfileData = UserProfileData.fromContractData(data); | |
| _verifierTokens = verifierTokensData | |
| .map((token) => VerificationDetails.fromContractData(token)) | |
| .toList(); | |
| } | |
| }); | |
| } else { | |
| if (!mounted) return; | |
| setState(() { | |
| _isLoading = false; | |
| _errorMessage = result.errorMessage ?? 'Failed to load user profile'; | |
| if (result.errorMessage?.contains('not registered') ?? false) { | |
| _isNotRegistered = true; | |
| } | |
| }); | |
| } | |
| } catch (e) { | |
| logger.e("Error loading user profile", error: e); | |
| if (!mounted) return; | |
| setState(() { | |
| _isLoading = false; | |
| _errorMessage = 'Error: ${e.toString()}'; | |
| }); | |
| } | |
| } |
🤖 Prompt for AI Agents
lib/widgets/profile_widgets/user_profile_viewer_widget.dart around lines 55 to
95: after the async await call to
ContractReadFunctions.getProfileDetailsByAddress (and any other await), guard
any subsequent setState calls with a mounted check to avoid calling setState on
a disposed widget; specifically, immediately after the await(s) add a check like
if (!mounted) return; then proceed to call setState in both the success and
error branches (and the catch block) so setState is only invoked when the widget
is still mounted.
| Widget _buildErrorWidget() { | ||
| return Container( | ||
| padding: const EdgeInsets.all(20), | ||
| decoration: BoxDecoration( | ||
| color: getThemeColors(context)['error']!, | ||
| borderRadius: BorderRadius.circular(buttonCircularRadius), | ||
| border: Border.all( | ||
| color: getThemeColors(context)['error']!, | ||
| width: 2, | ||
| ), | ||
| ), | ||
| child: Column( | ||
| mainAxisAlignment: MainAxisAlignment.center, | ||
| children: [ | ||
| Icon( | ||
| Icons.error_outline, | ||
| size: 64, | ||
| color: getThemeColors(context)['error'], | ||
| ), | ||
| const SizedBox(height: 16), | ||
| Text( | ||
| 'Error Loading Profile', | ||
| style: TextStyle( | ||
| color: getThemeColors(context)['error'], | ||
| fontSize: 20, | ||
| fontWeight: FontWeight.bold, | ||
| ), | ||
| ), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error UI uses the same “error” color for background, icon, and text, making it unreadable. Use contrasting text/icon colors.
- decoration: BoxDecoration(
- color: getThemeColors(context)['error']!,
+ decoration: BoxDecoration(
+ color: getThemeColors(context)['background'],
...
- child: Column(
+ child: Column(
children: [
- Icon(
- Icons.error_outline,
- size: 64,
- color: getThemeColors(context)['error'],
- ),
+ Icon(Icons.error_outline, size: 64, color: getThemeColors(context)['error']),
...
- Text('Error Loading Profile', style: TextStyle(color: getThemeColors(context)['error'], ...)),
+ Text('Error Loading Profile', style: TextStyle(color: getThemeColors(context)['textPrimary'], ...)),
...
- Text(_errorMessage ?? 'Unknown error', style: TextStyle(color: getThemeColors(context)['textSecondary'], ...)),
+ Text(_errorMessage ?? 'Unknown error', style: TextStyle(color: getThemeColors(context)['textSecondary'], ...)),Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In lib/widgets/profile_widgets/user_profile_viewer_widget.dart around lines 612
to 639 the error widget is using the same getThemeColors(context)['error'] value
for background, border, icon and text which makes content unreadable; change it
so the container background uses the error color but icon and text use a
contrasting foreground (for example getThemeColors(context)['onError'] or
Colors.white), and keep the border as either a darker error shade or transparent
to preserve contrast; update all color references in this block (container
decoration color, border color, Icon color, and Text style color) to use the
appropriate background vs foreground keys from the theme so the icon and text
are readable against the error background.
Summary by CodeRabbit
Release Notes
New Features
Improvements