diff --git a/.env.stencil b/.env.stencil index 3f246c3..32eedc7 100644 --- a/.env.stencil +++ b/.env.stencil @@ -1,17 +1,19 @@ -WALLETCONNECT_PROJECT_ID= -API_KEY= -API_SECRET= -ALCHEMY_API_KEY= -CONTRACT_ADDRESS= -APPLICATION_ID= +#API KEYS AND SECRETS +API_KEY= +API_SECRET= +ALCHEMY_API_KEY= -//CONTRACT ADDRESSES +#APPLICATION CONFIGURATION +APPLICATION_CHAIN_ID= +WALLETCONNECT_PROJECT_ID= -TREE_NFT_CONTRACT_ADDRESS= -ORGANISATION_FACTORY_CONTRACT_ADDRESS= -CARE_TOKEN_CONTRACT_ADDRESS= -VERIFIER_TOKEN_CONTRACT_ADDRESS= -LEGACY_TOKEN_CONTRACT_ADDRESS= -PLANTER_TOKEN_CONTRACT_ADDRESS= +#CONTRACT ADDRESSES + +TREE_NFT_CONTRACT_ADDRESS= +ORGANISATION_FACTORY_CONTRACT_ADDRESS= +CARE_TOKEN_CONTRACT_ADDRESS= +VERIFIER_TOKEN_CONTRACT_ADDRESS= +LEGACY_TOKEN_CONTRACT_ADDRESS= +PLANTER_TOKEN_CONTRACT_ADDRESS= diff --git a/lib/main.dart b/lib/main.dart index 156f25b..be64716 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,6 +6,9 @@ import 'package:tree_planting_protocol/pages/home_page.dart'; import 'package:tree_planting_protocol/pages/mint_nft/mint_nft_details.dart'; import 'package:tree_planting_protocol/pages/mint_nft/mint_nft_images.dart'; import 'package:tree_planting_protocol/pages/mint_nft/submit_nft_page.dart'; +import 'package:tree_planting_protocol/pages/organisations_pages/create_organisation.dart'; +import 'package:tree_planting_protocol/pages/organisations_pages/organisation_details_page.dart'; +import 'package:tree_planting_protocol/pages/organisations_pages/user_organisations_page.dart'; import 'package:tree_planting_protocol/pages/register_user_page.dart'; import 'package:tree_planting_protocol/pages/settings_page.dart'; import 'package:tree_planting_protocol/pages/tree_details_page.dart'; @@ -63,6 +66,30 @@ class MyApp extends StatelessWidget { return const SettingsPage(); }, ), + GoRoute( + path: '/organisations', + name: 'organisations_page', + builder: (BuildContext context, GoRouterState state) { + return const OrganisationsPage(); + }, + routes: [ + GoRoute( + path: ':address', + name: 'organisation_details', + builder: (BuildContext context, GoRouterState state) { + final address = state.pathParameters['address']; + return OrganisationDetailsPage(organisationAddress: address!); + }, + ), + ], + ), + GoRoute( + path: '/create-organisation', + name: 'create_organisation_page', + builder: (BuildContext context, GoRouterState state) { + return const CreateOrganisationPage(); + }, + ), GoRoute( path: RouteConstants.mintNftPath, name: RouteConstants.mintNft, diff --git a/lib/pages/mint_nft/mint_nft_coordinates.dart b/lib/pages/mint_nft/mint_nft_coordinates.dart index 0d30f52..978eb7b 100644 --- a/lib/pages/mint_nft/mint_nft_coordinates.dart +++ b/lib/pages/mint_nft/mint_nft_coordinates.dart @@ -8,7 +8,6 @@ import 'package:tree_planting_protocol/utils/constants/ui/color_constants.dart'; import 'package:tree_planting_protocol/utils/logger.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/utils/services/get_current_location.dart'; import 'package:dart_geohash/dart_geohash.dart'; @@ -606,44 +605,6 @@ class _MintNftCoordinatesPageState extends State { ); } - // ignore: unused_element - Widget _buildPreviewSection() { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 16), - child: Row( - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: const Color(0xFFFAEB96), - borderRadius: BorderRadius.circular(10), - ), - child: Icon( - Icons.preview, - color: const Color(0xFF1CD381), - size: 20, - ), - ), - const SizedBox(width: 12), - const Text( - 'Live Preview', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Color(0xFF1CD381), - ), - ), - ], - ), - ), - const NewNFTWidget(), - ], - ); - } - @override void dispose() { _locationTimer?.cancel(); diff --git a/lib/pages/mint_nft/submit_nft_page.dart b/lib/pages/mint_nft/submit_nft_page.dart index 550386a..9b4e48e 100644 --- a/lib/pages/mint_nft/submit_nft_page.dart +++ b/lib/pages/mint_nft/submit_nft_page.dart @@ -6,7 +6,7 @@ import 'package:tree_planting_protocol/utils/constants/ui/color_constants.dart'; import 'package:tree_planting_protocol/widgets/basic_scaffold.dart'; import 'package:tree_planting_protocol/widgets/nft_display_utils/tree_nft_view_details_with_map.dart'; import 'package:tree_planting_protocol/utils/logger.dart'; -import 'package:tree_planting_protocol/utils/services/contract_write_functions.dart'; +import 'package:tree_planting_protocol/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_write_functions.dart'; class SubmitNFTPage extends StatefulWidget { const SubmitNFTPage({super.key}); diff --git a/lib/pages/organisations_pages/create_organisation.dart b/lib/pages/organisations_pages/create_organisation.dart new file mode 100644 index 0000000..de0f320 --- /dev/null +++ b/lib/pages/organisations_pages/create_organisation.dart @@ -0,0 +1,613 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:provider/provider.dart'; +import 'package:tree_planting_protocol/providers/wallet_provider.dart'; +import 'package:tree_planting_protocol/utils/constants/ui/color_constants.dart'; +import 'package:tree_planting_protocol/utils/services/contract_functions/organisation_factory_contract.dart/organisation_factory_contract_write_functions.dart'; +import 'package:tree_planting_protocol/utils/services/ipfs_services.dart'; // Add this import for your IPFS function +import 'package:tree_planting_protocol/widgets/basic_scaffold.dart'; + +class CreateOrganisationPage extends StatefulWidget { + const CreateOrganisationPage({super.key}); + + @override + State createState() => _CreateOrganisationPageState(); +} + +class _CreateOrganisationPageState extends State { + final descriptionController = TextEditingController(); + final nameController = TextEditingController(); + final ImagePicker _picker = ImagePicker(); + + File? _selectedImage; + bool _isUploading = false; + String? _uploadedImageHash; + + Future _pickImage() async { + try { + final XFile? image = await _picker.pickImage( + source: ImageSource.gallery, + maxWidth: 1920, + maxHeight: 1920, + imageQuality: 85, + ); + + if (image != null) { + setState(() { + _selectedImage = File(image.path); + _uploadedImageHash = null; + }); + } + } catch (e) { + _showCustomSnackBar( + "Error selecting image: $e", + isError: true, + ); + } + } + + Future _uploadImageToIPFS() async { + if (_selectedImage == null) return; + + try { + final hash = await uploadToIPFS(_selectedImage!, (uploading) { + setState(() { + _isUploading = uploading; + }); + }); + + if (hash != null) { + setState(() { + _uploadedImageHash = hash; + }); + _showCustomSnackBar("Image uploaded successfully!"); + } else { + _showCustomSnackBar("Failed to upload image", isError: true); + } + } catch (e) { + _showCustomSnackBar( + "Error uploading image: $e", + isError: true, + ); + } + } + + Future submitDetails() async { + final description = descriptionController.text; + final name = nameController.text; + + if (description.isEmpty || name.isEmpty) { + _showCustomSnackBar( + "Enter all the details", + isError: true, + ); + return; + } + + // If image is selected but not uploaded, upload it first + if (_selectedImage != null && _uploadedImageHash == null) { + await _uploadImageToIPFS(); + if (_uploadedImageHash == null) { + _showCustomSnackBar( + "Please wait for image upload to complete or remove the image", + isError: true, + ); + return; + } + } + + try { + final walletProvider = + // ignore: use_build_context_synchronously + Provider.of(context, listen: false); + // ignore: unused_local_variable + final result = + await OrganisationFactoryContractWriteFunctions.createOrganisation( + walletProvider: walletProvider, + name: name, + description: description, + organisationPhotoHash: _uploadedImageHash ?? ""); + _showCustomSnackBar("Organisation details submitted successfully!"); + } catch (e) { + _showCustomSnackBar( + "Error submitting organisation details: $e", + isError: true, + ); + return; + } + // ignore: use_build_context_synchronously + context.push('/organisations'); + } + + void _showCustomSnackBar(String message, {bool isError = false}) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Row( + children: [ + Icon( + isError ? Icons.error_outline : Icons.check_circle_outline, + color: Colors.white, + size: 20, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + message, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + backgroundColor: isError + ? getThemeColors(context)['secondaryButton'] + : getThemeColors(context)['primaryButton'], + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + margin: const EdgeInsets.all(16), + ), + ); + } + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + + return BaseScaffold( + title: "Organisation Details", + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => context.pop(), + ), + body: SingleChildScrollView( + padding: EdgeInsets.symmetric( + horizontal: screenWidth * 0.05, + vertical: 20, + ), + child: Column( + children: [ + _buildFormSection(screenWidth), + const SizedBox(height: 32), + ], + ), + ), + ); + } + + Widget _buildFormSection(double screenWidth) { + return Container( + width: double.infinity, + constraints: BoxConstraints(maxWidth: screenWidth * 0.92), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(24), + topRight: Radius.circular(24), + ), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: getThemeColors(context)['background'], + borderRadius: BorderRadius.circular(14), + ), + child: Icon( + Icons.edit_note, + color: getThemeColors(context)['icon'], + size: 28, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Organisation Details', + style: TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + color: getThemeColors(context)['textPrimary'], + ), + ), + Text( + 'Tell us about your organisation', + style: TextStyle( + fontSize: 14, + color: getThemeColors(context)['textPrimary'], + ), + ), + ], + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildFormField( + controller: nameController, + label: 'Organisation Name', + hint: 'e.g., Green Earth, Tree Lovers...', + icon: Icons.eco, + maxLines: 1, + ), + const SizedBox(height: 20), + _buildFormField( + controller: descriptionController, + label: 'Description', + hint: 'Describe your organisation...', + icon: Icons.description, + maxLines: 5, + minLines: 3, + ), + const SizedBox(height: 20), + _buildImageUploadSection(), + const SizedBox(height: 32), + SizedBox( + width: double.infinity, + height: 56, + child: ElevatedButton( + onPressed: _isUploading ? null : submitDetails, + style: ElevatedButton.styleFrom( + backgroundColor: getThemeColors(context)['primary'], + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + shadowColor: primaryGreenColor, + ), + child: _isUploading + ? Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation( + Colors.white, + ), + ), + ), + const SizedBox(width: 12), + const Text( + 'Uploading...', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + ], + ) + : Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'Continue', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(width: 8), + Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: getThemeColors(context)['primary'], + borderRadius: BorderRadius.circular(6), + ), + child: const Icon( + Icons.arrow_forward, + size: 18, + ), + ), + ], + ), + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildImageUploadSection() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + Icons.image, + color: getThemeColors(context)['icon'], + size: 18, + ), + ), + const SizedBox(width: 8), + const Text( + 'Organisation Logo', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Color(0xFF1CD381), + ), + ), + const Text( + ' (Optional)', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: Colors.grey, + ), + ), + ], + ), + const SizedBox(height: 12), + Container( + width: double.infinity, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: const Color(0xFFFAEB96), + width: 2, + ), + ), + child: _selectedImage == null + ? InkWell( + onTap: _pickImage, + borderRadius: BorderRadius.circular(16), + child: SizedBox( + height: 120, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.cloud_upload_outlined, + size: 40, + color: getThemeColors(context)['icon'], + ), + const SizedBox(height: 8), + Text( + 'Tap to upload logo', + style: TextStyle( + fontSize: 16, + color: getThemeColors(context)['textPrimary'], + fontWeight: FontWeight.w500, + ), + ), + Text( + 'PNG, JPG up to 10MB', + style: TextStyle( + fontSize: 12, + color: getThemeColors(context)['textSecondary'], + ), + ), + ], + ), + ), + ) + : Column( + children: [ + Container( + height: 200, + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(12), + child: Image.file( + _selectedImage!, + fit: BoxFit.cover, + ), + ), + ), + const SizedBox(height: 12), + Row( + children: [ + Expanded( + child: TextButton.icon( + onPressed: _pickImage, + icon: const Icon(Icons.edit, size: 16), + label: const Text('Change'), + style: TextButton.styleFrom( + foregroundColor: + getThemeColors(context)['primary'], + ), + ), + ), + if (_uploadedImageHash == null) ...[ + const SizedBox(width: 8), + Expanded( + child: ElevatedButton.icon( + onPressed: + _isUploading ? null : _uploadImageToIPFS, + icon: _isUploading + ? SizedBox( + height: 16, + width: 16, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: + AlwaysStoppedAnimation( + Colors.white, + ), + ), + ) + : const Icon(Icons.cloud_upload, size: 16), + label: Text( + _isUploading ? 'Uploading...' : 'Upload'), + style: ElevatedButton.styleFrom( + backgroundColor: + getThemeColors(context)['primary'], + foregroundColor: Colors.white, + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ), + ), + ] else ...[ + const SizedBox(width: 8), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: getThemeColors(context)['primary'], + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.green, width: 1), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.check_circle, + color: getThemeColors(context)['icon'], + size: 16, + ), + const SizedBox(width: 4), + const Text( + 'Uploaded', + style: TextStyle( + color: Colors.green, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ], + const SizedBox(width: 8), + IconButton( + onPressed: () { + setState(() { + _selectedImage = null; + _uploadedImageHash = null; + }); + }, + icon: const Icon(Icons.delete_outline), + style: IconButton.styleFrom( + foregroundColor: getThemeColors(context)['error'], + ), + ), + ], + ), + const SizedBox(height: 12), + ], + ), + ), + ], + ); + } + + Widget _buildFormField({ + required TextEditingController controller, + required String label, + required String hint, + required IconData icon, + int maxLines = 1, + int? minLines, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + icon, + color: getThemeColors(context)['icon'], + size: 18, + ), + ), + const SizedBox(width: 8), + Text( + label, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: getThemeColors(context)['primary'], + ), + ), + ], + ), + const SizedBox(height: 12), + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: getThemeColors(context)['border']!, + width: 2, + ), + ), + child: TextField( + controller: controller, + maxLines: maxLines, + minLines: minLines, + style: TextStyle( + fontSize: 16, + color: getThemeColors(context)['textPrimary'], + height: 1.4, + ), + decoration: InputDecoration( + hintText: hint, + hintStyle: TextStyle( + color: getThemeColors(context)['background'], + fontSize: 14, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide.none, + ), + contentPadding: const EdgeInsets.all(20), + filled: true, + fillColor: getThemeColors(context)['background'], + ), + ), + ), + ], + ); + } + + @override + void dispose() { + descriptionController.dispose(); + nameController.dispose(); + super.dispose(); + } +} diff --git a/lib/pages/organisations_pages/organisation_details_page.dart b/lib/pages/organisations_pages/organisation_details_page.dart new file mode 100644 index 0000000..6576d2f --- /dev/null +++ b/lib/pages/organisations_pages/organisation_details_page.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +class OrganisationDetailsPage extends StatelessWidget { + final String organisationAddress; + + const OrganisationDetailsPage({super.key, required this.organisationAddress}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("Organisation Details"), + ), + body: Center( + child: Text( + "Organisation Address: $organisationAddress", + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + ), + ); + } +} diff --git a/lib/pages/organisations_pages/user_organisations_page.dart b/lib/pages/organisations_pages/user_organisations_page.dart index 8b13789..619551e 100644 --- a/lib/pages/organisations_pages/user_organisations_page.dart +++ b/lib/pages/organisations_pages/user_organisations_page.dart @@ -1 +1,192 @@ +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/ui/color_constants.dart'; +import 'package:tree_planting_protocol/utils/constants/ui/dimensions.dart'; +import 'package:tree_planting_protocol/utils/logger.dart'; +import 'package:tree_planting_protocol/utils/services/contract_functions/organisation_factory_contract.dart/organisation_factory_read_functions.dart'; +import 'package:tree_planting_protocol/widgets/basic_scaffold.dart'; +class OrganisationsPage extends StatefulWidget { + const OrganisationsPage({super.key}); + + @override + State createState() => _OrganisationsPageState(); +} + +class _OrganisationsPageState extends State { + final GlobalKey<_UserOrganisationsWidgetState> _organisationsKey = + GlobalKey(); + + @override + Widget build(BuildContext context) { + final walletProvider = Provider.of(context); + return BaseScaffold( + title: 'Organisations', + body: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + IconButton( + onPressed: () { + _organisationsKey.currentState?.fetchUserOrganisations(); + }, + icon: const Icon(Icons.refresh), + style: IconButton.styleFrom( + backgroundColor: getThemeColors(context)['primary'], + foregroundColor: getThemeColors(context)['secondary'], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(buttonCircularRadius), + ), + padding: const EdgeInsets.all(12), + elevation: 4, + side: const BorderSide(color: Colors.black, width: 2), + ), + ), + const SizedBox(width: 10), + ElevatedButton( + style: ElevatedButton.styleFrom( + elevation: 4, + backgroundColor: getThemeColors(context)['primary'], + foregroundColor: getThemeColors(context)['secondary'], + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(buttonCircularRadius), + ), + padding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 12), + side: const BorderSide(color: Colors.black, width: 2), + ), + onPressed: () { + context.push('/create-organisation'); + }, + child: const Text('Create Organisation')), + ], + ), + const SizedBox(height: 20), + UserOrganisationsWidget( + key: _organisationsKey, + userAddress: walletProvider.userAddress.toString()), + ], + ), + ), + ); + } +} + +class UserOrganisationsWidget extends StatefulWidget { + final String userAddress; + const UserOrganisationsWidget({super.key, required this.userAddress}); + + @override + State createState() => + _UserOrganisationsWidgetState(); +} + +class _UserOrganisationsWidgetState extends State { + List userOrganisations = []; + bool _isLoading = false; + String _errorMessage = ''; + + @override + void initState() { + super.initState(); + fetchUserOrganisations(); + } + + Future fetchUserOrganisations() async { + if (_isLoading) return; + setState(() { + _isLoading = true; + _errorMessage = ''; + userOrganisations.clear(); + }); + try { + final walletProvider = + Provider.of(context, listen: false); + final result = await ContractReadFunctions.getOrganisationsByUser( + walletProvider: walletProvider); + if (result.success && result.data != null) { + final data = result.data as Map; + final organisations = data['organisations'] as List; + setState(() { + userOrganisations = organisations.map((e) => e.toString()).toList(); + }); + } else { + setState(() { + _errorMessage = result.errorMessage ?? 'Unknown error occurred'; + }); + } + logger.d("User organisations fetched:"); + logger.d(userOrganisations); + } catch (e) { + setState(() { + _errorMessage = e.toString(); + }); + } finally { + setState(() { + _isLoading = false; + }); + } + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + userOrganisations.isNotEmpty + ? _buildUserOrganisationsList(context) + : const Text('No organisations found for this user.'), + if (_isLoading) const CircularProgressIndicator(), + if (_errorMessage.isNotEmpty) Text(_errorMessage), + ], + ); + } + + Widget _buildUserOrganisationsList(BuildContext context) { + return ListView.builder( + shrinkWrap: true, + itemCount: userOrganisations.length, + itemBuilder: (context, index) { + return _buildUserOrganisationCard(context, userOrganisations[index]); + }, + ); + } + + Widget _buildUserOrganisationCard( + BuildContext context, String organisationAddress) { + return InkWell( + borderRadius: BorderRadius.circular(12), + onTap: () { + context.push('/organisations/$organisationAddress'); + }, + child: Card( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + elevation: 8, + shadowColor: Colors.black, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + side: BorderSide( + color: getThemeColors(context)['border']!, + width: 2, + ), + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Text( + organisationAddress, + style: TextStyle( + color: getThemeColors(context)['textPrimary'], + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/register_user_page.dart b/lib/pages/register_user_page.dart index 1048c7e..0059682 100644 --- a/lib/pages/register_user_page.dart +++ b/lib/pages/register_user_page.dart @@ -5,7 +5,7 @@ import 'package:image_picker/image_picker.dart'; import 'package:provider/provider.dart'; import 'package:tree_planting_protocol/providers/wallet_provider.dart'; import 'package:tree_planting_protocol/utils/constants/ui/color_constants.dart'; -import 'package:tree_planting_protocol/utils/services/contract_write_functions.dart'; +import 'package:tree_planting_protocol/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_write_functions.dart'; import 'package:tree_planting_protocol/utils/services/ipfs_services.dart'; import 'package:tree_planting_protocol/widgets/basic_scaffold.dart'; diff --git a/lib/pages/tree_details_page.dart b/lib/pages/tree_details_page.dart index a4e7ae3..fa47bfe 100644 --- a/lib/pages/tree_details_page.dart +++ b/lib/pages/tree_details_page.dart @@ -4,8 +4,8 @@ import 'package:provider/provider.dart'; import 'package:tree_planting_protocol/providers/wallet_provider.dart'; import 'package:tree_planting_protocol/utils/constants/ui/color_constants.dart'; import 'package:tree_planting_protocol/utils/logger.dart'; -import 'package:tree_planting_protocol/utils/services/contract_read_services.dart'; -import 'package:tree_planting_protocol/utils/services/contract_write_functions.dart'; +import 'package:tree_planting_protocol/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_read_services.dart'; +import 'package:tree_planting_protocol/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_write_functions.dart'; import 'package:tree_planting_protocol/utils/services/conversion_functions.dart'; import 'package:tree_planting_protocol/utils/services/ipfs_services.dart'; import 'package:tree_planting_protocol/widgets/basic_scaffold.dart'; diff --git a/lib/utils/constants/contract_abis/organisation_contract_details.dart b/lib/utils/constants/contract_abis/organisation_contract_details.dart new file mode 100644 index 0000000..4e38d9b --- /dev/null +++ b/lib/utils/constants/contract_abis/organisation_contract_details.dart @@ -0,0 +1,6 @@ +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +const String organisationContractAbi = + '''[{"type":"constructor","inputs":[{"name":"_name","type":"string","internalType":"string"},{"name":"_description","type":"string","internalType":"string"},{"name":"_photoIpfsHash","type":"string","internalType":"string"},{"name":"_creator","type":"address","internalType":"address"},{"name":"_factoryAddress","type":"address","internalType":"address"},{"name":"_treeNFTContractAddress","type":"address","internalType":"address"},{"name":"_founder","type":"address","internalType":"address"}],"stateMutability":"nonpayable"},{"type":"function","name":"addMember","inputs":[{"name":"user","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"changePaginationLimit","inputs":[{"name":"_limit","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"checkMembership","inputs":[{"name":"user","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"function","name":"checkOwnership","inputs":[{"name":"user","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"function","name":"description","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"type":"function","name":"founder","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"getMemberCount","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getMembers","inputs":[],"outputs":[{"name":"","type":"address[]","internalType":"address[]"}],"stateMutability":"view"},{"type":"function","name":"getOrganisationInfo","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"},{"name":"","type":"string","internalType":"string"},{"name":"","type":"string","internalType":"string"},{"name":"","type":"string","internalType":"string"},{"name":"","type":"address[]","internalType":"address[]"},{"name":"","type":"address[]","internalType":"address[]"},{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getOwners","inputs":[],"outputs":[{"name":"","type":"address[]","internalType":"address[]"}],"stateMutability":"view"},{"type":"function","name":"getTreePlantingProposal","inputs":[{"name":"proposalID","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"tuple","internalType":"struct TreePlantingProposal","components":[{"name":"id","type":"uint256","internalType":"uint256"},{"name":"latitude","type":"uint256","internalType":"uint256"},{"name":"longitude","type":"uint256","internalType":"uint256"},{"name":"species","type":"string","internalType":"string"},{"name":"imageUri","type":"string","internalType":"string"},{"name":"qrIpfsHash","type":"string","internalType":"string"},{"name":"photos","type":"string[]","internalType":"string[]"},{"name":"geoHash","type":"string","internalType":"string"},{"name":"metadata","type":"string","internalType":"string"},{"name":"status","type":"uint256","internalType":"uint256"}]}],"stateMutability":"view"},{"type":"function","name":"getTreePlantingProposals","inputs":[{"name":"status","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"tuple[]","internalType":"struct TreePlantingProposal[]","components":[{"name":"id","type":"uint256","internalType":"uint256"},{"name":"latitude","type":"uint256","internalType":"uint256"},{"name":"longitude","type":"uint256","internalType":"uint256"},{"name":"species","type":"string","internalType":"string"},{"name":"imageUri","type":"string","internalType":"string"},{"name":"qrIpfsHash","type":"string","internalType":"string"},{"name":"photos","type":"string[]","internalType":"string[]"},{"name":"geoHash","type":"string","internalType":"string"},{"name":"metadata","type":"string","internalType":"string"},{"name":"status","type":"uint256","internalType":"uint256"}]}],"stateMutability":"view"},{"type":"function","name":"getTreePlantingProposalsByStatus","inputs":[{"name":"status","type":"uint256","internalType":"uint256"},{"name":"offset","type":"uint256","internalType":"uint256"},{"name":"limit","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"proposals","type":"tuple[]","internalType":"struct TreePlantingProposal[]","components":[{"name":"id","type":"uint256","internalType":"uint256"},{"name":"latitude","type":"uint256","internalType":"uint256"},{"name":"longitude","type":"uint256","internalType":"uint256"},{"name":"species","type":"string","internalType":"string"},{"name":"imageUri","type":"string","internalType":"string"},{"name":"qrIpfsHash","type":"string","internalType":"string"},{"name":"photos","type":"string[]","internalType":"string[]"},{"name":"geoHash","type":"string","internalType":"string"},{"name":"metadata","type":"string","internalType":"string"},{"name":"status","type":"uint256","internalType":"uint256"}]},{"name":"totalMatching","type":"uint256","internalType":"uint256"},{"name":"hasMore","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"function","name":"getVerificationRequest","inputs":[{"name":"verificationID","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"tuple","internalType":"struct OrganisationVerificationRequest","components":[{"name":"id","type":"uint256","internalType":"uint256"},{"name":"initialMember","type":"address","internalType":"address"},{"name":"organisationContract","type":"address","internalType":"address"},{"name":"status","type":"uint256","internalType":"uint256"},{"name":"description","type":"string","internalType":"string"},{"name":"timestamp","type":"uint256","internalType":"uint256"},{"name":"proofHashes","type":"string[]","internalType":"string[]"},{"name":"treeNftId","type":"uint256","internalType":"uint256"}]}],"stateMutability":"view"},{"type":"function","name":"getVerificationRequests","inputs":[{"name":"status","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"tuple[]","internalType":"struct OrganisationVerificationRequest[]","components":[{"name":"id","type":"uint256","internalType":"uint256"},{"name":"initialMember","type":"address","internalType":"address"},{"name":"organisationContract","type":"address","internalType":"address"},{"name":"status","type":"uint256","internalType":"uint256"},{"name":"description","type":"string","internalType":"string"},{"name":"timestamp","type":"uint256","internalType":"uint256"},{"name":"proofHashes","type":"string[]","internalType":"string[]"},{"name":"treeNftId","type":"uint256","internalType":"uint256"}]}],"stateMutability":"view"},{"type":"function","name":"getVerificationRequestsByStatus","inputs":[{"name":"status","type":"uint256","internalType":"uint256"},{"name":"offset","type":"uint256","internalType":"uint256"},{"name":"limit","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"requests","type":"tuple[]","internalType":"struct OrganisationVerificationRequest[]","components":[{"name":"id","type":"uint256","internalType":"uint256"},{"name":"initialMember","type":"address","internalType":"address"},{"name":"organisationContract","type":"address","internalType":"address"},{"name":"status","type":"uint256","internalType":"uint256"},{"name":"description","type":"string","internalType":"string"},{"name":"timestamp","type":"uint256","internalType":"uint256"},{"name":"proofHashes","type":"string[]","internalType":"string[]"},{"name":"treeNftId","type":"uint256","internalType":"uint256"}]},{"name":"totalMatching","type":"uint256","internalType":"uint256"},{"name":"hasMore","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"function","name":"leaveOrganisation","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"makeOwner","inputs":[{"name":"newOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"members","inputs":[{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"name","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"type":"function","name":"organisationFactoryContract","inputs":[],"outputs":[{"name":"","type":"address","internalType":"contract OrganisationFactory"}],"stateMutability":"view"},{"type":"function","name":"owners","inputs":[{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"paginationLimit","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"photoIpfsHash","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"type":"function","name":"plantTreeProposal","inputs":[{"name":"_latitude","type":"uint256","internalType":"uint256"},{"name":"_longitude","type":"uint256","internalType":"uint256"},{"name":"_species","type":"string","internalType":"string"},{"name":"_imageURI","type":"string","internalType":"string"},{"name":"_qrIpfshash","type":"string","internalType":"string"},{"name":"_metadata","type":"string","internalType":"string"},{"name":"photos","type":"string[]","internalType":"string[]"},{"name":"geoHash","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"removeMember","inputs":[{"name":"member","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"requestVerification","inputs":[{"name":"_description","type":"string","internalType":"string"},{"name":"_proofHashes","type":"string[]","internalType":"string[]"},{"name":"_treeNftID","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"nonpayable"},{"type":"function","name":"timeOfCreation","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"treeNFTContract","inputs":[],"outputs":[{"name":"","type":"address","internalType":"contract TreeNft"}],"stateMutability":"view"},{"type":"function","name":"voteOnTreePlantingProposal","inputs":[{"name":"proposalID","type":"uint256","internalType":"uint256"},{"name":"vote","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"voteOnVerificationRequest","inputs":[{"name":"verificationID","type":"uint256","internalType":"uint256"},{"name":"vote","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"event","name":"UserAddedToOrganisation","inputs":[{"name":"user","type":"address","indexed":true,"internalType":"address"},{"name":"organisationContract","type":"address","indexed":true,"internalType":"address"},{"name":"timestamp","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"by_user","type":"address","indexed":false,"internalType":"address"}],"anonymous":false},{"type":"event","name":"UserRemovedFromOrganisation","inputs":[{"name":"user","type":"address","indexed":true,"internalType":"address"},{"name":"organisationContract","type":"address","indexed":true,"internalType":"address"},{"name":"timestamp","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"by_user","type":"address","indexed":false,"internalType":"address"}],"anonymous":false},{"type":"error","name":"AlreadyMember","inputs":[]},{"type":"error","name":"AlreadyOwner","inputs":[]},{"type":"error","name":"AlreadyProcessed","inputs":[]},{"type":"error","name":"AlreadyVoted","inputs":[]},{"type":"error","name":"InvalidAddressInput","inputs":[]},{"type":"error","name":"InvalidCoordinates","inputs":[]},{"type":"error","name":"InvalidInput","inputs":[]},{"type":"error","name":"InvalidNameInput","inputs":[]},{"type":"error","name":"InvalidProposalId","inputs":[]},{"type":"error","name":"InvalidVerificationId","inputs":[]},{"type":"error","name":"NeedAnotherOwner","inputs":[]},{"type":"error","name":"NotOrganisationMember","inputs":[]},{"type":"error","name":"OnlyOwner","inputs":[]},{"type":"error","name":"PaginationLimitExceeded","inputs":[]}]'''; +final String organisationContractAddress = + dotenv.env['ORGANISATION_FACTORY_CONTRACT_ADDRESS'] ?? ''; diff --git a/lib/utils/constants/contract_abis/organisation_factory_contract_details.dart b/lib/utils/constants/contract_abis/organisation_factory_contract_details.dart new file mode 100644 index 0000000..7d985ac --- /dev/null +++ b/lib/utils/constants/contract_abis/organisation_factory_contract_details.dart @@ -0,0 +1,7 @@ +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +const String organisationFactoryContractAbi = + '''[{"type":"constructor","inputs":[{"name":"_treeNFTContract","type":"address","internalType":"address"}],"stateMutability":"nonpayable"},{"type":"function","name":"addMemberToOrganisation","inputs":[{"name":"_member","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"createOrganisation","inputs":[{"name":"_name","type":"string","internalType":"string"},{"name":"_description","type":"string","internalType":"string"},{"name":"_photoIpfsHash","type":"string","internalType":"string"}],"outputs":[{"name":"organisationId","type":"uint256","internalType":"uint256"},{"name":"organisationAddress","type":"address","internalType":"address"}],"stateMutability":"nonpayable"},{"type":"function","name":"getAllOrganisationDetails","inputs":[],"outputs":[{"name":"organizationDetails","type":"tuple[]","internalType":"struct OrganisationDetails[]","components":[{"name":"contractAddress","type":"address","internalType":"address"},{"name":"name","type":"string","internalType":"string"},{"name":"description","type":"string","internalType":"string"},{"name":"photoIpfsHash","type":"string","internalType":"string"},{"name":"owners","type":"address[]","internalType":"address[]"},{"name":"members","type":"address[]","internalType":"address[]"},{"name":"ownerCount","type":"uint256","internalType":"uint256"},{"name":"memberCount","type":"uint256","internalType":"uint256"},{"name":"isActive","type":"bool","internalType":"bool"},{"name":"timeOfCreation","type":"uint256","internalType":"uint256"}]}],"stateMutability":"view"},{"type":"function","name":"getAllOrganisations","inputs":[],"outputs":[{"name":"","type":"address[]","internalType":"address[]"}],"stateMutability":"view"},{"type":"function","name":"getMyOrganisations","inputs":[],"outputs":[{"name":"","type":"address[]","internalType":"address[]"}],"stateMutability":"view"},{"type":"function","name":"getOrganisationCount","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getOrganisationInfo","inputs":[{"name":"_organisationAddress","type":"address","internalType":"address"}],"outputs":[{"name":"organisationAddress","type":"address","internalType":"address"},{"name":"name","type":"string","internalType":"string"},{"name":"description","type":"string","internalType":"string"},{"name":"photoIpfsHash","type":"string","internalType":"string"},{"name":"owners","type":"address[]","internalType":"address[]"},{"name":"members","type":"address[]","internalType":"address[]"},{"name":"timeOfCreation","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getTreeNFTContract","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"getUserOrganisations","inputs":[{"name":"_user","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"address[]","internalType":"address[]"}],"stateMutability":"view"},{"type":"function","name":"owner","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"removeOrganisation","inputs":[{"name":"_organisationAddress","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"renounceOwnership","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"s_organisationAddressToOrganisation","inputs":[{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"address","internalType":"contract Organisation"}],"stateMutability":"view"},{"type":"function","name":"s_userToOrganisations","inputs":[{"name":"","type":"address","internalType":"address"},{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"transferOwnership","inputs":[{"name":"newOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"treeNFTContract","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"updateTreeNFTContract","inputs":[{"name":"_newTreeNFTContract","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"event","name":"OwnershipTransferred","inputs":[{"name":"previousOwner","type":"address","indexed":true,"internalType":"address"},{"name":"newOwner","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"error","name":"InvalidDescriptionInput","inputs":[]},{"type":"error","name":"InvalidInput","inputs":[]},{"type":"error","name":"InvalidNameInput","inputs":[]},{"type":"error","name":"InvalidOrganisation","inputs":[]},{"type":"error","name":"OrganisationDoesNotExist","inputs":[]},{"type":"error","name":"OwnableInvalidOwner","inputs":[{"name":"owner","type":"address","internalType":"address"}]},{"type":"error","name":"OwnableUnauthorizedAccount","inputs":[{"name":"account","type":"address","internalType":"address"}]}]'''; + +final String organisationFactoryContractAddress = + dotenv.env['ORGANISATION_FACTORY_CONTRACT_ADDRESS'] ?? ''; diff --git a/lib/utils/constants/contract_abis/tree_nft_contract_abi.dart b/lib/utils/constants/contract_abis/tree_nft_contract_details.dart similarity index 97% rename from lib/utils/constants/contract_abis/tree_nft_contract_abi.dart rename to lib/utils/constants/contract_abis/tree_nft_contract_details.dart index c41b5cd..ab2f0d5 100644 --- a/lib/utils/constants/contract_abis/tree_nft_contract_abi.dart +++ b/lib/utils/constants/contract_abis/tree_nft_contract_details.dart @@ -6,10 +6,3 @@ const String treeNftContractABI = final String treeNFtContractAddress = dotenv.env['TREE_NFT_CONTRACT_ADDRESS'] ?? ''; - -// CareToken Address: 0xF9C45610FEA0382Ab5d28c7CaEe44F6aC26Fe956 -// PlanterToken Address: 0x18a3BB9E8b6a692b3B29Dcf49Ce58f4bf2CB0E93 -// VerifierToken Address: 0x52db3eEff09D1dBE30007fA06AE14aF9849D29ba -// LegacyToken Address: 0xD5C0F25B883f018133d1Ce46fdb3365B660EF1db -// TreeNft Address: 0xeD3D3a4f30ad25d29BD6cB46Bb705a120809DB23 -// OrganisationFactory: 0x75da54F30d347040e977860a9C3495b2C52b4F23 diff --git a/lib/utils/constants/ui/color_constants.dart b/lib/utils/constants/ui/color_constants.dart index 11b8246..3eef26d 100644 --- a/lib/utils/constants/ui/color_constants.dart +++ b/lib/utils/constants/ui/color_constants.dart @@ -17,6 +17,9 @@ Map getThemeColors(BuildContext context) { 'primaryBorder': themeProvider.isDarkMode ? const Color.fromARGB(255, 1, 135, 12) : const Color.fromARGB(255, 28, 211, 129), + 'border': themeProvider.isDarkMode + ? const Color.fromARGB(255, 255, 255, 255) + : const Color.fromARGB(255, 18, 18, 18), 'BNWBorder': themeProvider.isDarkMode ? const Color.fromARGB(255, 1, 135, 12) : const Color.fromARGB(255, 28, 211, 129), @@ -50,5 +53,8 @@ Map getThemeColors(BuildContext context) { 'marker': themeProvider.isDarkMode ? const Color.fromARGB(255, 255, 100, 100) : const Color.fromARGB(255, 255, 0, 0), + 'primaryShadow': themeProvider.isDarkMode + ? const Color.fromARGB(255, 255, 255, 255) + : const Color.fromARGB(255, 0, 0, 0), }; } diff --git a/lib/utils/constants/ui/dimensions.dart b/lib/utils/constants/ui/dimensions.dart new file mode 100644 index 0000000..4ac0c23 --- /dev/null +++ b/lib/utils/constants/ui/dimensions.dart @@ -0,0 +1,6 @@ +// import 'package:flutter/material.dart'; + +final double buttonCircularRadius = 12.0; + +final double buttonBlurRadius = 6.0; +final double buttonborderWidth = 2.0; diff --git a/lib/utils/services/contract_functions/organisation_contract/organisation_read_functions.dart b/lib/utils/services/contract_functions/organisation_contract/organisation_read_functions.dart new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/lib/utils/services/contract_functions/organisation_contract/organisation_read_functions.dart @@ -0,0 +1 @@ + diff --git a/lib/utils/services/contract_functions/organisation_contract/organisation_write_functions.dart b/lib/utils/services/contract_functions/organisation_contract/organisation_write_functions.dart new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/lib/utils/services/contract_functions/organisation_contract/organisation_write_functions.dart @@ -0,0 +1 @@ + diff --git a/lib/utils/services/contract_functions/organisation_factory_contract.dart/organisation_factory_contract_write_functions.dart b/lib/utils/services/contract_functions/organisation_factory_contract.dart/organisation_factory_contract_write_functions.dart new file mode 100644 index 0000000..747effd --- /dev/null +++ b/lib/utils/services/contract_functions/organisation_factory_contract.dart/organisation_factory_contract_write_functions.dart @@ -0,0 +1,77 @@ +import 'package:tree_planting_protocol/providers/wallet_provider.dart'; +import 'package:tree_planting_protocol/utils/constants/contract_abis/organisation_factory_contract_details.dart'; +import 'package:tree_planting_protocol/utils/logger.dart'; + +class ContractWriteResult { + final bool success; + final String? transactionHash; + final String? errorMessage; + final dynamic data; + + ContractWriteResult({ + required this.success, + this.transactionHash, + this.errorMessage, + this.data, + }); + + ContractWriteResult.success({ + required String transactionHash, + dynamic data, + }) : this( + success: true, + transactionHash: transactionHash, + data: data, + ); + + ContractWriteResult.error({ + required String errorMessage, + }) : this( + success: false, + errorMessage: errorMessage, + ); +} + +class OrganisationFactoryContractWriteFunctions { + static Future createOrganisation({ + required WalletProvider walletProvider, + required String name, + required String description, + required String organisationPhotoHash, + String additionalData = "", + }) async { + try { + if (!walletProvider.isConnected) { + logger.e("Wallet not connected for minting NFT"); + return ContractWriteResult.error( + errorMessage: 'Please connect your wallet before minting.', + ); + } + + final List args = [name, description, organisationPhotoHash]; + final txHash = await walletProvider.writeContract( + contractAddress: organisationFactoryContractAddress, + functionName: 'createOrganisation', + params: args, + abi: organisationFactoryContractAbi, + chainId: walletProvider.currentChainId, + ); + + logger.i("NFT minting transaction sent: $txHash"); + + return ContractWriteResult.success( + transactionHash: txHash, + data: { + 'name': name, + 'description': description, + 'organisationPhotoHash': organisationPhotoHash, + }, + ); + } catch (e) { + logger.e("Error minting NFT", error: e); + return ContractWriteResult.error( + errorMessage: e.toString(), + ); + } + } +} diff --git a/lib/utils/services/contract_functions/organisation_factory_contract.dart/organisation_factory_read_functions.dart b/lib/utils/services/contract_functions/organisation_factory_contract.dart/organisation_factory_read_functions.dart new file mode 100644 index 0000000..dbb5f84 --- /dev/null +++ b/lib/utils/services/contract_functions/organisation_factory_contract.dart/organisation_factory_read_functions.dart @@ -0,0 +1,87 @@ +// ignore: depend_on_referenced_packages +import 'package:web3dart/web3dart.dart'; +import 'package:tree_planting_protocol/providers/wallet_provider.dart'; +import 'package:tree_planting_protocol/utils/logger.dart'; +import 'package:tree_planting_protocol/utils/constants/contract_abis/organisation_factory_contract_details.dart'; + +class ContractReadResult { + final bool success; + final String? transactionHash; + final String? errorMessage; + final dynamic data; + + ContractReadResult({ + required this.success, + this.transactionHash, + this.errorMessage, + this.data, + }); + + ContractReadResult.success({ + String? transactionHash, + dynamic data, + }) : this( + success: true, + transactionHash: transactionHash, + data: data, + ); + + ContractReadResult.error({ + required String errorMessage, + }) : this( + success: false, + errorMessage: errorMessage, + ); +} + +class ContractReadFunctions { + static Future getOrganisationsByUser({ + required WalletProvider walletProvider, + }) 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 args = [ + userAddress, + ]; + final result = await walletProvider.readContract( + contractAddress: organisationFactoryContractAddress, + functionName: 'getUserOrganisations', + params: args, + abi: organisationFactoryContractAbi, + ); + logger.i("Organisations read successfully: $result"); + if (result == null || result.isEmpty) { + return ContractReadResult.error( + errorMessage: 'No data returned from contract', + ); + } + final organisations = result.length > 0 ? result[0] ?? [] : []; + final totalCount = + result.length > 1 ? int.parse(result[1].toString()) : 0; + logger.d(organisations); + return ContractReadResult.success( + data: { + 'organisations': organisations, + 'totalCount': totalCount, + }, + ); + } catch (e) { + logger.e("Error reading organisations", error: e); + return ContractReadResult.error( + errorMessage: 'Failed to read NFTs: ${e.toString()}', + ); + } + } +} diff --git a/lib/utils/services/contract_read_services.dart b/lib/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_read_services.dart similarity index 99% rename from lib/utils/services/contract_read_services.dart rename to lib/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_read_services.dart index 007e452..1c5f59a 100644 --- a/lib/utils/services/contract_read_services.dart +++ b/lib/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_read_services.dart @@ -2,7 +2,7 @@ import 'package:web3dart/web3dart.dart'; import 'package:tree_planting_protocol/providers/wallet_provider.dart'; import 'package:tree_planting_protocol/utils/logger.dart'; -import 'package:tree_planting_protocol/utils/constants/contract_abis/tree_nft_contract_abi.dart'; +import 'package:tree_planting_protocol/utils/constants/contract_abis/tree_nft_contract_details.dart'; class ContractReadResult { final bool success; @@ -47,14 +47,12 @@ class ContractReadFunctions { 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); if (offset < 0 || limit <= 0 || limit > 100) { return ContractReadResult.error( @@ -62,7 +60,6 @@ class ContractReadFunctions { 'Invalid pagination parameters. Offset must be >= 0 and limit must be between 1-100', ); } - final List args = [ userAddress, BigInt.from(offset), @@ -81,7 +78,6 @@ class ContractReadFunctions { errorMessage: 'No data returned from contract', ); } - final trees = result.length > 0 ? result[0] ?? [] : []; final totalCount = result.length > 1 ? int.parse(result[1].toString()) : 0; @@ -110,7 +106,6 @@ class ContractReadFunctions { errorMessage: 'Please connect your wallet before pinging.', ); } - final String address = walletProvider.currentAddress.toString(); if (!address.startsWith('0x')) { return ContractReadResult.error( @@ -141,7 +136,6 @@ class ContractReadFunctions { ); } catch (e) { logger.e("Error pinging contract", error: e); - String detailedError = 'Ping failed: ${e.toString()}'; return ContractReadResult.error( errorMessage: detailedError, @@ -160,14 +154,12 @@ class ContractReadFunctions { 'Please connect your wallet before fetching user details from blockchain', ); } - final String address = walletProvider.currentAddress.toString(); if (!address.startsWith('0x')) { return ContractReadResult.error( errorMessage: 'Invalid wallet address format', ); } - final String currentAddress = walletProvider.currentAddress!.toString(); final EthereumAddress userAddress = EthereumAddress.fromHex(currentAddress); @@ -201,7 +193,6 @@ class ContractReadFunctions { errorMessage: 'Please connect your wallet first', ); } - final String address = walletProvider.currentAddress.toString(); if (!address.startsWith('0x')) { return ContractReadResult.error( @@ -209,37 +200,31 @@ class ContractReadFunctions { ); } final List args = [BigInt.from(id)]; - final treeDetailsResult = await walletProvider.readContract( contractAddress: treeNFtContractAddress, functionName: 'getTreeDetailsbyID', params: args, abi: treeNftContractABI, ); - final tree = treeDetailsResult.length > 0 ? treeDetailsResult[0] ?? [] : []; logger.d("Tree Info"); logger.d(tree); - final treeVerifiersResult = await walletProvider.readContract( contractAddress: treeNFtContractAddress, functionName: 'getTreeNftVerifiersPaginated', params: [BigInt.from(id), BigInt.from(offset), BigInt.from(limit)], abi: treeNftContractABI); - final verifiers = treeVerifiersResult.length > 0 ? treeVerifiersResult[0] ?? [] : []; logger.d("Tree Verifiers Info"); logger.d(verifiers); - final ownerResult = await walletProvider.readContract( contractAddress: treeNFtContractAddress, functionName: 'ownerOf', params: [BigInt.from(id)], abi: treeNftContractABI, ); - final owner = ownerResult.isNotEmpty ? ownerResult[0] : null; return ContractReadResult.success( data: {'details': tree, 'verifiers': verifiers, 'owner': owner}, diff --git a/lib/utils/services/contract_write_functions.dart b/lib/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_write_functions.dart similarity index 99% rename from lib/utils/services/contract_write_functions.dart rename to lib/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_write_functions.dart index 7debf81..fd55286 100644 --- a/lib/utils/services/contract_write_functions.dart +++ b/lib/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_write_functions.dart @@ -1,6 +1,6 @@ import 'package:tree_planting_protocol/providers/wallet_provider.dart'; import 'package:tree_planting_protocol/utils/logger.dart'; -import 'package:tree_planting_protocol/utils/constants/contract_abis/tree_nft_contract_abi.dart'; +import 'package:tree_planting_protocol/utils/constants/contract_abis/tree_nft_contract_details.dart'; import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart'; class ContractWriteResult { diff --git a/lib/widgets/nft_display_utils/tree_nft_details_verifiers_widget.dart b/lib/widgets/nft_display_utils/tree_nft_details_verifiers_widget.dart index 87d2502..f92cc1f 100644 --- a/lib/widgets/nft_display_utils/tree_nft_details_verifiers_widget.dart +++ b/lib/widgets/nft_display_utils/tree_nft_details_verifiers_widget.dart @@ -4,7 +4,7 @@ import 'package:provider/provider.dart'; import 'package:tree_planting_protocol/providers/wallet_provider.dart'; import 'package:tree_planting_protocol/utils/constants/ui/color_constants.dart'; import 'package:tree_planting_protocol/utils/logger.dart'; -import 'package:tree_planting_protocol/utils/services/contract_write_functions.dart'; +import 'package:tree_planting_protocol/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_write_functions.dart'; class Verifier { final String address; diff --git a/lib/widgets/nft_display_utils/user_nfts_widget.dart b/lib/widgets/nft_display_utils/user_nfts_widget.dart index df526db..6326e34 100644 --- a/lib/widgets/nft_display_utils/user_nfts_widget.dart +++ b/lib/widgets/nft_display_utils/user_nfts_widget.dart @@ -3,8 +3,9 @@ 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/ui/color_constants.dart'; +import 'package:tree_planting_protocol/utils/constants/ui/dimensions.dart'; import 'package:tree_planting_protocol/utils/logger.dart'; -import 'package:tree_planting_protocol/utils/services/contract_read_services.dart'; +import 'package:tree_planting_protocol/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_read_services.dart'; class Tree { final int id; @@ -216,14 +217,14 @@ class _UserNftsWidgetState extends State { decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: Border.all( - color: Colors.black, + color: getThemeColors(context)['border']!, width: 0.5, ), boxShadow: [ BoxShadow( color: Colors.black, spreadRadius: 1, - blurRadius: 2, + blurRadius: buttonBlurRadius, offset: const Offset(0, 2), ), ], @@ -245,12 +246,14 @@ class _UserNftsWidgetState extends State { const SizedBox(height: 8), Row( children: [ - const Icon(Icons.location_on, color: Colors.green, size: 16), + Icon(Icons.location_on, + color: getThemeColors(context)['icon'], size: 16), const SizedBox(width: 4), Expanded( child: Text( 'Location: ${tree.latitude / 1000000}, ${tree.longitude / 1000000}', - style: const TextStyle(color: Colors.grey), + style: TextStyle( + color: getThemeColors(context)['textPrimary']), ), ), ], @@ -258,22 +261,26 @@ class _UserNftsWidgetState extends State { const SizedBox(height: 4), Row( children: [ - const Icon(Icons.calendar_today, color: Colors.green, size: 16), + Icon(Icons.calendar_today, + color: getThemeColors(context)['icon'], size: 16), const SizedBox(width: 4), Text( 'Planted: ${DateTime.fromMillisecondsSinceEpoch(tree.planting * 1000).toString().split(' ')[0]}', - style: const TextStyle(color: Colors.grey), + style: + TextStyle(color: getThemeColors(context)['textPrimary']), ), ], ), const SizedBox(height: 4), Row( children: [ - const Icon(Icons.favorite, color: Colors.red, size: 16), + Icon(Icons.favorite, + color: getThemeColors(context)['icon'], size: 16), const SizedBox(width: 4), Text( 'Care Count: ${tree.careCount}', - style: const TextStyle(color: Colors.grey), + style: + TextStyle(color: getThemeColors(context)['textPrimary']), ), ], ), @@ -281,15 +288,17 @@ class _UserNftsWidgetState extends State { Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: tree.death == 0 ? Colors.green : Colors.red, + color: tree.death == 0 + ? getThemeColors(context)['primary'] + : getThemeColors(context)['error'], borderRadius: BorderRadius.circular(12), ), child: Text( tree.death < DateTime.now().millisecondsSinceEpoch ~/ 1000 ? 'Deceased' : 'Alive', - style: const TextStyle( - color: Colors.white, + style: TextStyle( + color: getThemeColors(context)['textSecondary'], fontSize: 12, fontWeight: FontWeight.bold, ), @@ -301,34 +310,42 @@ class _UserNftsWidgetState extends State { padding: const EdgeInsets.only(right: 6.0, top: 8.0), child: ElevatedButton( style: ElevatedButton.styleFrom( - backgroundColor: - const Color.fromARGB(255, 28, 211, 129), + backgroundColor: getThemeColors(context)['primary'], shadowColor: Colors.black, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(buttonCircularRadius), + ), elevation: 4, side: const BorderSide(color: Colors.black, width: 2), ), onPressed: () { context.push('/trees/${tree.id}'); }, - child: const Text( + child: Text( "View details", - style: TextStyle(color: Colors.black), + style: TextStyle( + color: getThemeColors(context)['textPrimary']), ), )), Padding( padding: const EdgeInsets.only(right: 16.0, top: 8.0), child: ElevatedButton( style: ElevatedButton.styleFrom( - backgroundColor: - const Color.fromARGB(255, 251, 251, 99), + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(buttonCircularRadius), + ), + backgroundColor: getThemeColors(context)['secondary'], shadowColor: Colors.black, elevation: 4, side: const BorderSide(color: Colors.black, width: 2), ), onPressed: () {}, - child: const Text( + child: Text( "View on the map", - style: TextStyle(color: Colors.black), + style: TextStyle( + color: getThemeColors(context)['textPrimary']), ), )) ], diff --git a/lib/widgets/profile_widgets/profile_section_widget.dart b/lib/widgets/profile_widgets/profile_section_widget.dart index b8c6ee6..ceac22d 100644 --- a/lib/widgets/profile_widgets/profile_section_widget.dart +++ b/lib/widgets/profile_widgets/profile_section_widget.dart @@ -3,8 +3,9 @@ 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/ui/color_constants.dart'; +import 'package:tree_planting_protocol/utils/constants/ui/dimensions.dart'; import 'package:tree_planting_protocol/utils/logger.dart'; -import 'package:tree_planting_protocol/utils/services/contract_read_services.dart'; +import 'package:tree_planting_protocol/utils/services/contract_functions/tree_nft_contract/tree_nft_contract_read_services.dart'; class UserProfileData { String name; @@ -216,28 +217,24 @@ class _ProfileSectionWidgetState extends State { color: Colors.black, ), ), - Padding( - padding: const EdgeInsets.all(4.0), - child: SizedBox( - height: 40, - width: 135, - child: Container( - decoration: BoxDecoration( - color: const Color(0xFFFF4E63), - border: Border.all( - color: Colors.black, - width: 2, - ), - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.black12, // shadow color - blurRadius: 6, // shadow softness - offset: Offset(0, 3), // shadow position - ), - ], - ), - child: Center(child: Text('Organisations'))), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: getThemeColors(context)['secondaryButton'], + foregroundColor: getThemeColors(context)['textPrimary'], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(buttonCircularRadius), + ), + elevation: 4, + side: const BorderSide(color: Colors.black, width: 2), + ), + onPressed: () { + context.push('/organisations'); + }, + child: Text( + "Organisations", + style: TextStyle( + color: getThemeColors(context)['textPrimary'], + ), ), ), ], @@ -249,109 +246,116 @@ class _ProfileSectionWidgetState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ Padding( - padding: const EdgeInsets.all(4.0), - child: SizedBox( - height: 40, - width: 150, - child: Container( - decoration: BoxDecoration( - color: const Color.fromARGB(255, 251, 251, 99), - border: Border.all( - color: Colors.black, - width: 2, - ), - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.black12, - blurRadius: 6, - offset: Offset(0, 3), + padding: const EdgeInsets.all(4.0), + child: SizedBox( + height: 40, + width: 150, + child: Material( + elevation: 4, + borderRadius: BorderRadius.circular(buttonCircularRadius), + child: Container( + decoration: BoxDecoration( + color: getThemeColors(context)['secondary'], + border: Border.all( + color: getThemeColors(context)['border']!, + width: buttonborderWidth, ), - ], - ), - child: Center( + borderRadius: BorderRadius.circular(buttonCircularRadius), + ), + child: Center( child: Text( - 'Planter Tokens : ${_userProfileData!.planterTokens}'))), - ), - ), - Padding( - padding: const EdgeInsets.all(4.0), - child: SizedBox( - height: 40, - width: 150, - child: Container( - decoration: BoxDecoration( - color: const Color.fromARGB(255, 28, 211, 129), - border: Border.all( - color: Colors.black, - width: 2, + 'Planter Tokens : ${_userProfileData!.planterTokens}', + style: TextStyle( + color: getThemeColors(context)['textPrimary'], + fontWeight: FontWeight.bold, + )), ), - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.black12, - blurRadius: 6, - offset: Offset(0, 3), - ), - ], ), - child: Center( - child: - Text('Care Tokens : ${_userProfileData!.careTokens}'))), - ), - ), + ), + )), Padding( - padding: const EdgeInsets.all(4.0), - child: SizedBox( - height: 40, - width: 150, - child: Container( - decoration: BoxDecoration( - color: const Color.fromARGB(255, 251, 251, 99), - border: Border.all( - color: Colors.black, - width: 2, - ), - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.black12, - blurRadius: 6, - offset: Offset(0, 3), + padding: const EdgeInsets.all(4.0), + child: SizedBox( + height: 40, + width: 150, + child: Material( + elevation: 4, + borderRadius: BorderRadius.circular(buttonCircularRadius), + child: Container( + decoration: BoxDecoration( + color: getThemeColors(context)['primary'], + border: Border.all( + color: getThemeColors(context)['border']!, + width: buttonborderWidth, ), - ], + borderRadius: BorderRadius.circular(buttonCircularRadius), + ), + child: Center( + child: Text('Care Tokens : ${_userProfileData!.careTokens}', + style: TextStyle( + color: getThemeColors(context)['textPrimary'], + fontWeight: FontWeight.bold, + )), + ), ), - child: Center( - child: Text( - 'Verifier Tokens : ${_userProfileData!.verifierTokens}'))), - ), - ), + ), + )), Padding( - padding: const EdgeInsets.all(4.0), - child: SizedBox( - height: 40, - width: 150, - child: Container( - decoration: BoxDecoration( - color: const Color.fromARGB(255, 28, 211, 129), - border: Border.all( - color: Colors.black, - width: 2, - ), - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.black12, - blurRadius: 6, - offset: Offset(0, 3), + padding: const EdgeInsets.all(4.0), + child: SizedBox( + height: 40, + width: 150, + child: Material( + elevation: 4, + borderRadius: BorderRadius.circular(buttonCircularRadius), + child: Container( + decoration: BoxDecoration( + color: getThemeColors(context)['secondary'], + border: Border.all( + color: getThemeColors(context)['border']!, + width: buttonborderWidth, ), - ], + borderRadius: BorderRadius.circular(buttonCircularRadius), + ), + child: Center( + child: Text( + 'Verifier Tokens : ${_userProfileData!.verifierTokens}', + style: TextStyle( + color: getThemeColors(context)['textPrimary'], + fontWeight: FontWeight.bold, + )), + ), ), - child: Center( + ), + )), + Padding( + padding: const EdgeInsets.all(4.0), + child: SizedBox( + height: 40, + width: 150, + child: Material( + elevation: 4, + borderRadius: BorderRadius.circular(buttonCircularRadius), + child: Container( + decoration: BoxDecoration( + color: getThemeColors(context)['primary'], + border: Border.all( + color: getThemeColors(context)['border']!, + width: buttonborderWidth, + ), + borderRadius: BorderRadius.circular(buttonCircularRadius), + ), + child: Center( child: Text( - 'Legacy Tokens : ${_userProfileData!.legacyTokens}'))), - ), - ), + 'Legacy Tokens : ${_userProfileData!.legacyTokens}', + style: TextStyle( + color: getThemeColors(context)['textPrimary'], + fontWeight: FontWeight.bold, + )), + ), + ), + ), + )), ], ); } @@ -374,7 +378,7 @@ class _ProfileSectionWidgetState extends State { style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, - color: Colors.red.shade700, + color: getThemeColors(context)['error']!, ), ), const SizedBox(height: 8), @@ -382,7 +386,7 @@ class _ProfileSectionWidgetState extends State { _errorMessage ?? 'Unknown error occurred', style: TextStyle( fontSize: 14, - color: Colors.red.shade600, + color: getThemeColors(context)['error']!, ), textAlign: TextAlign.center, ),