diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7ed994a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "files.associations": { + "variant": "cpp" + }, + "cmake.sourceDirectory": "D:/Work/pay_evm/linux" +} \ No newline at end of file diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 39f05b2..d9ba378 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -7,7 +7,8 @@ plugins { android { namespace = "site.kanari.kanaripay" - compileSdk = flutter.compileSdkVersion + // Use explicit SDK versions to ensure newer Android attributes (eg. allowTaskSnapshot) are available + compileSdk = 36 ndkVersion = flutter.ndkVersion compileOptions { @@ -24,8 +25,9 @@ android { applicationId = "site.kanari.kanaripay" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. - minSdk = flutter.minSdkVersion - targetSdk = flutter.targetSdkVersion + minSdk = flutter.minSdkVersion + // Explicit targetSdk to match compileSdk and enable newer platform attributes + targetSdk = 36 versionCode = flutter.versionCode versionName = flutter.versionName } diff --git a/android/app/src/main/kotlin/site/kanari/kanaripay/MainActivity.kt b/android/app/src/main/kotlin/site/kanari/kanaripay/MainActivity.kt index 381b3bd..07abdef 100644 --- a/android/app/src/main/kotlin/site/kanari/kanaripay/MainActivity.kt +++ b/android/app/src/main/kotlin/site/kanari/kanaripay/MainActivity.kt @@ -1,6 +1,20 @@ package site.kanari.kanaripay +import android.os.Bundle +import android.view.WindowManager import io.flutter.embedding.android.FlutterFragmentActivity class MainActivity: FlutterFragmentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // Prevent screenshots and screen recording for this activity + // This sets the FLAG_SECURE window flag which tells the system + // to treat the content of the window as secure, preventing it from + // appearing in screenshots or on non-secure displays. + window?.setFlags( + WindowManager.LayoutParams.FLAG_SECURE, + WindowManager.LayoutParams.FLAG_SECURE + ) + } } diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 6266644..8ddb711 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -3,11 +3,85 @@ import UIKit @main @objc class AppDelegate: FlutterAppDelegate { + var privacyOverlay: UIView? + override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) + + // Observe for screen capture (screenshot & recording) state changes + NotificationCenter.default.addObserver( + self, + selector: #selector(screenCaptureChanged), + name: UIScreen.capturedDidChangeNotification, + object: nil + ) + + // Observe for user screenshots (note: iOS does not allow blocking screenshots, + // but we can detect them and immediately cover sensitive content) + NotificationCenter.default.addObserver( + self, + selector: #selector(userDidTakeScreenshot), + name: UIApplication.userDidTakeScreenshotNotification, + object: nil + ) + + // Initial check + if UIScreen.main.isCaptured { + showPrivacyOverlay() + } + return super.application(application, didFinishLaunchingWithOptions: launchOptions) } + + @objc func screenCaptureChanged() { + DispatchQueue.main.async { + if UIScreen.main.isCaptured { + self.showPrivacyOverlay() + } else { + self.hidePrivacyOverlay() + } + } + } + + @objc func userDidTakeScreenshot() { + // Show the privacy overlay briefly when a screenshot is taken + showPrivacyOverlay() + // Hide after a short delay + DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { + self.hidePrivacyOverlay() + } + } + + func showPrivacyOverlay() { + guard let window = UIApplication.shared.windows.first else { return } + if privacyOverlay != nil { return } + + let overlay = UIView(frame: window.bounds) + overlay.backgroundColor = UIColor.black + overlay.alpha = 0.95 + overlay.isUserInteractionEnabled = false + + // Optional: Add a label explaining why the screen is hidden + let label = UILabel() + label.text = "Screen capture is disabled" + label.textColor = .white + label.textAlignment = .center + label.translatesAutoresizingMaskIntoConstraints = false + overlay.addSubview(label) + NSLayoutConstraint.activate([ + label.centerXAnchor.constraint(equalTo: overlay.centerXAnchor), + label.centerYAnchor.constraint(equalTo: overlay.centerYAnchor) + ]) + + window.addSubview(overlay) + privacyOverlay = overlay + } + + func hidePrivacyOverlay() { + privacyOverlay?.removeFromSuperview() + privacyOverlay = nil + } } diff --git a/lib/main.dart b/lib/main.dart index 44c5a7c..3661fd9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:kanaripay/screenpage/WelcomeScreen.dart'; import 'package:kanaripay/screenpage/WalletScreen.dart'; @@ -9,6 +10,9 @@ import 'package:kanaripay/services/security_service.dart'; import 'package:kanaripay/providers/theme_provider.dart'; void main() { + WidgetsFlutterBinding.ensureInitialized(); + // Enter immersive fullscreen mode (hides status bar) + SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); runApp(const MyApp()); } @@ -121,75 +125,41 @@ class _AppInitializerState extends State @override Widget build(BuildContext context) { + // Ensure AppTheme dynamic colors are in sync with the active Theme + AppTheme.updateThemeColors(Theme.of(context).brightness == Brightness.dark); + if (_isLoading) { final bool isDark = Theme.of(context).brightness == Brightness.dark; return Scaffold( - backgroundColor: AppTheme.surfaceColor, + backgroundColor: Theme.of(context).scaffoldBackgroundColor, body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - // App Logo with pulse animation TweenAnimationBuilder( duration: const Duration(milliseconds: 1500), - tween: Tween(begin: 0.8, end: 1.0), + tween: Tween(begin: 0.86, end: 1.0), builder: (context, scale, child) { return Transform.scale( scale: scale, child: Container( - width: 120, - height: 120, + width: 110, + height: 110, decoration: BoxDecoration( - color: isDark - ? AppTheme.primaryColor.withOpacity(0.15) - : AppTheme.primaryColor.withOpacity(0.18), - borderRadius: BorderRadius.circular(30), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity( - isDark ? 0.2 : 0.08, - ), - blurRadius: 30, - offset: const Offset(0, 15), - ), - ], - ), - child: Icon( - Icons.account_balance_wallet, - size: 60, - color: isDark ? Colors.white : AppTheme.primaryColor, + color: isDark ? Theme.of(context).cardColor : Theme.of(context).colorScheme.primary.withOpacity(0.06), + borderRadius: BorderRadius.circular(26), + boxShadow: AppTheme.elevatedShadow, ), + child: Icon(Icons.account_balance_wallet, size: 54, color: Theme.of(context).colorScheme.primary), ), ); }, ), - const SizedBox(height: 32), - Text( - 'Kanari Wallet', - style: TextStyle( - fontSize: 28, - fontWeight: FontWeight.bold, - color: isDark - ? Colors.white - : Theme.of(context).textTheme.titleLarge?.color ?? - AppTheme.textPrimary, - letterSpacing: 1.2, - ), - ), - const SizedBox(height: 48), - SizedBox( - width: 40, - height: 40, - child: CircularProgressIndicator( - strokeWidth: 3, - valueColor: AlwaysStoppedAnimation( - isDark - ? AppTheme.primaryColor.withOpacity(0.95) - : AppTheme.primaryColor, - ), - ), - ), + const SizedBox(height: 20), + Text('Kanari Wallet', style: Theme.of(context).textTheme.headlineSmall), + const SizedBox(height: 36), + SizedBox(width: 44, height: 44, child: CircularProgressIndicator(strokeWidth: 3, valueColor: AlwaysStoppedAnimation(Theme.of(context).colorScheme.primary))), ], ), ), diff --git a/lib/screenpage/AddCustomNetworkScreen.dart b/lib/screenpage/AddCustomNetworkScreen.dart index acec4da..f47e62c 100644 --- a/lib/screenpage/AddCustomNetworkScreen.dart +++ b/lib/screenpage/AddCustomNetworkScreen.dart @@ -393,7 +393,7 @@ class _AddCustomNetworkScreenState extends State { padding: const EdgeInsets.all(8.0), child: CircleAvatar( radius: 12, - backgroundColor: Colors.transparent, + backgroundColor: AppTheme.transparent, child: ClipOval( child: Image.network( _iconUrlController.text.trim(), diff --git a/lib/screenpage/AddTokenScreen.dart b/lib/screenpage/AddTokenScreen.dart index c5acec6..be455ef 100644 --- a/lib/screenpage/AddTokenScreen.dart +++ b/lib/screenpage/AddTokenScreen.dart @@ -296,7 +296,7 @@ class _AddTokenScreenState extends State ], ), elevation: 0, - backgroundColor: Colors.transparent, + backgroundColor: AppTheme.transparent, ), body: SingleChildScrollView( padding: const EdgeInsets.all(16.0), diff --git a/lib/screenpage/CreateWalletScreen.dart b/lib/screenpage/CreateWalletScreen.dart index 25f65c7..5bcef91 100644 --- a/lib/screenpage/CreateWalletScreen.dart +++ b/lib/screenpage/CreateWalletScreen.dart @@ -267,8 +267,8 @@ class _CreateWalletScreenState extends State const Text('Create Wallet'), ], ), - elevation: 0, - backgroundColor: Colors.transparent, + elevation: 0, + backgroundColor: AppTheme.transparent, ), body: FadeTransition( opacity: _fadeAnimation, @@ -280,9 +280,9 @@ class _CreateWalletScreenState extends State padding: const EdgeInsets.all(4), decoration: BoxDecoration( // Use theme-aware background so it looks correct in dark mode - color: Theme.of(context).brightness == Brightness.dark - ? Theme.of(context).cardColor - : Colors.white, + color: Theme.of(context).brightness == Brightness.dark + ? Theme.of(context).cardColor + : AppTheme.cardColor, borderRadius: BorderRadius.circular(AppTheme.borderRadiusLarge), boxShadow: AppTheme.elevatedShadow, ), @@ -302,9 +302,9 @@ class _CreateWalletScreenState extends State ], ), indicatorSize: TabBarIndicatorSize.tab, - dividerColor: Colors.transparent, - // Keep selected label visible (white text on colored indicator) - labelColor: Colors.white, + dividerColor: AppTheme.transparent, + // Keep selected label visible (use theme's onPrimary so it's correct in all modes) + labelColor: Theme.of(context).colorScheme.onPrimary, // Use theme text color for unselected labels so they remain readable unselectedLabelColor: Theme.of(context).textTheme.bodySmall?.color ?? AppTheme.textSecondary, @@ -410,7 +410,10 @@ class _CreateWalletScreenState extends State children: [ Row( children: [ - Icon(Icons.info, color: Theme.of(context).primaryColor), + Icon( + Icons.info, + color: Theme.of(context).iconTheme.color ?? Theme.of(context).primaryColor, + ), const SizedBox(width: 8), Text( _isSui ? 'Create New Sui Wallet' : 'Create New Wallet', diff --git a/lib/screenpage/MarkdownPage.dart b/lib/screenpage/MarkdownPage.dart index bf72ab5..219013b 100644 --- a/lib/screenpage/MarkdownPage.dart +++ b/lib/screenpage/MarkdownPage.dart @@ -48,7 +48,7 @@ class _MarkdownPageState extends State { backgroundColor: AppTheme.surfaceColor, appBar: AppBar( title: Text(widget.title), - backgroundColor: Colors.transparent, + backgroundColor: AppTheme.transparent, elevation: 0, ), body: _isLoading diff --git a/lib/screenpage/NetworkSelectionScreen.dart b/lib/screenpage/NetworkSelectionScreen.dart index 42e3604..e1f47d9 100644 --- a/lib/screenpage/NetworkSelectionScreen.dart +++ b/lib/screenpage/NetworkSelectionScreen.dart @@ -97,7 +97,7 @@ class _NetworkSelectionScreenState extends State { ), TextButton( onPressed: () => Navigator.pop(context, true), - style: TextButton.styleFrom(foregroundColor: Colors.red), + style: TextButton.styleFrom(foregroundColor: AppTheme.errorColor), child: const Text('Delete'), ), ], @@ -152,7 +152,7 @@ class _NetworkSelectionScreenState extends State { return Card( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), elevation: isActive ? 4 : 1, - color: isActive ? Theme.of(context).primaryColor.withOpacity(0.1) : null, + color: isActive ? (Theme.of(context).iconTheme.color ?? Theme.of(context).primaryColor).withOpacity(0.1) : null, child: ListTile( leading: _buildNetworkAvatar(network), title: Text( @@ -188,7 +188,7 @@ class _NetworkSelectionScreenState extends State { trailing: Row( mainAxisSize: MainAxisSize.min, children: [ - if (isActive) const Icon(Icons.check_circle, color: Colors.green), + if (isActive) const Icon(Icons.check_circle, color: AppTheme.successColor), if (showMenu) PopupMenuButton( onSelected: (value) { @@ -327,7 +327,7 @@ class _NetworkSelectionScreenState extends State { children: [const Text('Select Network')], ), elevation: 0, - backgroundColor: Colors.transparent, + backgroundColor: AppTheme.transparent, ), body: _isLoading ? Center(child: CircularProgressIndicator(valueColor: AlwaysStoppedAnimation(AppTheme.primaryColor))) diff --git a/lib/screenpage/PinSetupScreen.dart b/lib/screenpage/PinSetupScreen.dart index 4db5d08..e0c3d4e 100644 --- a/lib/screenpage/PinSetupScreen.dart +++ b/lib/screenpage/PinSetupScreen.dart @@ -3,7 +3,8 @@ import 'package:flutter/services.dart'; import '../utils/app_theme.dart'; class PinSetupScreen extends StatefulWidget { - final Function(String) onPinSetup; + // Expect an async callback so callers can perform async setup (e.g. store PIN) + final Future Function(String) onPinSetup; const PinSetupScreen({ super.key, @@ -119,9 +120,14 @@ class _PinSetupScreenState extends State with TickerProviderStat } else { if (pin == _firstPin) { setState(() => _isLoading = true); - try { - widget.onPinSetup(pin); + // Await caller's async setup so we can update loading state correctly. + await widget.onPinSetup(pin); + if (mounted) { + setState(() { + _isLoading = false; + }); + } } catch (e) { if (mounted) { setState(() { diff --git a/lib/screenpage/PinVerificationScreen.dart b/lib/screenpage/PinVerificationScreen.dart index 9503baa..58bdce9 100644 --- a/lib/screenpage/PinVerificationScreen.dart +++ b/lib/screenpage/PinVerificationScreen.dart @@ -150,7 +150,8 @@ class _PinVerificationScreenState extends State for (var controller in _pinControllers) { controller.clear(); } - // ไม่ต้อง focus เพราะเราใช้ numpad + // No focus needed because we use the numpad — ensure UI updates + if (mounted) setState(() {}); } void _showLockDialog() { @@ -209,7 +210,7 @@ class _PinVerificationScreenState extends State backgroundColor: AppTheme.surfaceColor, appBar: widget.onCancel != null ? AppBar( - backgroundColor: Colors.transparent, + backgroundColor: AppTheme.transparent, elevation: 0, leading: IconButton( icon: const Icon(Icons.close), diff --git a/lib/screenpage/ReceiveScreen.dart b/lib/screenpage/ReceiveScreen.dart index 26a560b..5ab501f 100644 --- a/lib/screenpage/ReceiveScreen.dart +++ b/lib/screenpage/ReceiveScreen.dart @@ -30,7 +30,7 @@ class ReceiveScreen extends StatelessWidget { ], ), elevation: 0, - backgroundColor: Colors.transparent, + backgroundColor: AppTheme.transparent, ), body: SingleChildScrollView( padding: const EdgeInsets.all(24.0), @@ -90,7 +90,7 @@ class ReceiveScreen extends StatelessWidget { children: [ Icon( Icons.account_balance_wallet, - color: Theme.of(context).primaryColor, + color: Theme.of(context).iconTheme.color ?? Theme.of(context).primaryColor, ), const SizedBox(width: 8), Text( diff --git a/lib/screenpage/SendScreen.dart b/lib/screenpage/SendScreen.dart index 57279ce..fe7226c 100644 --- a/lib/screenpage/SendScreen.dart +++ b/lib/screenpage/SendScreen.dart @@ -404,10 +404,10 @@ class _SendScreenState extends State { title: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, - children: [const Text('Send Money')], + children: [const Text('Send Coin(ERC20)')], ), elevation: 0, - backgroundColor: Colors.transparent, + backgroundColor: AppTheme.transparent, ), body: SingleChildScrollView( padding: const EdgeInsets.all(16.0), @@ -425,7 +425,7 @@ class _SendScreenState extends State { children: [ Icon( Icons.account_balance_wallet, - color: Theme.of(context).primaryColor, + color: Theme.of(context).iconTheme.color ?? Theme.of(context).primaryColor, ), const SizedBox(width: 8), Text( @@ -464,7 +464,7 @@ class _SendScreenState extends State { children: [ Icon( Icons.token, - color: Theme.of(context).primaryColor, + color: Theme.of(context).iconTheme.color ?? Theme.of(context).primaryColor, size: 22, ), const SizedBox(width: 8), diff --git a/lib/screenpage/SendSuiScreen.dart b/lib/screenpage/SendSuiScreen.dart index 6cdfdc3..2cb5e65 100644 --- a/lib/screenpage/SendSuiScreen.dart +++ b/lib/screenpage/SendSuiScreen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:local_auth/local_auth.dart'; +import 'package:mobile_scanner/mobile_scanner.dart'; import '../sui/sui_wallet_service.dart'; import '../services/transaction_service.dart'; import '../services/token_service.dart'; @@ -34,6 +35,7 @@ class _SendSuiScreenState extends State { // No gas controls in SUI UI as requested bool _isLoading = false; + bool _showScanner = false; NetworkModel? _currentNetwork; double _balance = 0.0; List _tokens = []; @@ -241,6 +243,23 @@ class _SendSuiScreenState extends State { } } + void _scanQRCode() { + setState(() => _showScanner = true); + } + + void _onQRCodeDetected(BarcodeCapture capture) { + final List barcodes = capture.barcodes; + if (barcodes.isNotEmpty) { + final String code = barcodes.first.rawValue ?? ''; + if (code.isNotEmpty) { + setState(() { + _showScanner = false; + _addressController.text = code; + }); + } + } + } + void _setMaxAmount() { // Use selected token balance as max (native or token) final max = (_selectedTokenBalance).clamp(0.0, _selectedTokenBalance); @@ -261,11 +280,15 @@ class _SendSuiScreenState extends State { @override Widget build(BuildContext context) { + if (_showScanner) { + return _buildQRScanner(); + } + return Scaffold( backgroundColor: AppTheme.surfaceColor, appBar: AppBar( title: const Text('Send SUI'), - backgroundColor: Colors.transparent, + backgroundColor: AppTheme.transparent, elevation: 0, ), body: SingleChildScrollView( @@ -273,15 +296,34 @@ class _SendSuiScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ + // Balance card (shows selected token balance) Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(widget.wallet.name, style: const TextStyle(fontWeight: FontWeight.bold)), + Row( + children: [ + Icon( + Icons.account_balance_wallet, + color: Theme.of(context).iconTheme.color ?? Theme.of(context).primaryColor, + ), + const SizedBox(width: 8), + Text( + widget.wallet.name, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ], + ), const SizedBox(height: 8), - Text('Balance: ${_balance.toStringAsFixed(6)} SUI'), + Text( + 'Balance: ${_selectedTokenBalance.toStringAsFixed(6)} ${_selectedToken?.symbol ?? 'SUI'}', + style: TextStyle( + fontSize: 16, + color: Theme.of(context).textTheme.bodyMedium?.color, + ), + ), ], ), ), @@ -289,34 +331,146 @@ class _SendSuiScreenState extends State { const SizedBox(height: 16), - // Token selector - if (_tokens.isNotEmpty) ...[ - DropdownButtonFormField( - value: _selectedToken, - decoration: const InputDecoration( - labelText: 'Token', - border: OutlineInputBorder(), + // Token selector card (styled like SendScreen) + if (_tokens.isNotEmpty) + Card( + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + Icons.token, + color: Theme.of(context).iconTheme.color ?? Theme.of(context).primaryColor, + size: 22, + ), + const SizedBox(width: 8), + const Text( + 'Select Token', + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), + ), + ], + ), + const SizedBox(height: 12), + Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: Theme.of(context).cardColor.withOpacity(0.03), + border: Border.all(color: Theme.of(context).dividerColor), + borderRadius: BorderRadius.circular(10), + ), + child: DropdownButtonHideUnderline( + child: DropdownButton( + value: _selectedToken, + isExpanded: true, + hint: const Text('Select a token'), + onChanged: (CustomTokenModel? newToken) async { + if (newToken != null) { + setState(() => _selectedToken = newToken); + await _updateSelectedTokenBalance(); + } + }, + items: _tokens.map((token) { + return DropdownMenuItem( + value: token, + child: Row( + children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: token.isNative + ? Theme.of(context).primaryColor.withOpacity(0.15) + : Theme.of(context).cardColor.withOpacity(0.12), + borderRadius: BorderRadius.circular(18), + border: Border.all( + color: Theme.of(context).primaryColor.withOpacity(0.18), + width: 1, + ), + ), + child: Center( + child: Text( + token.symbol.toUpperCase(), + style: TextStyle( + color: Theme.of(context).iconTheme.color ?? Theme.of(context).primaryColor, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + token.name, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle(fontSize: 14), + ), + const SizedBox(height: 2), + Text( + token.symbol, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle(color: Theme.of(context).textTheme.bodySmall?.color, fontSize: 12), + ), + ], + ), + ), + const SizedBox(width: 8), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Theme.of(context).primaryColor.withOpacity(0.06), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + token.balance.toStringAsFixed(6), + style: TextStyle(fontSize: 12, color: Theme.of(context).textTheme.bodyMedium?.color), + ), + ), + ], + ), + ); + }).toList(), + ), + ), + ), + ], + ), ), - items: _tokens.map((t) => DropdownMenuItem(value: t, child: Text('${t.symbol} ${t.isNative ? '' : ''}'))).toList(), - onChanged: (t) async { - setState(() => _selectedToken = t); - await _updateSelectedTokenBalance(); - }, ), - const SizedBox(height: 12), - ], + const SizedBox(height: 20), + + // Destination address TextField( controller: _addressController, - decoration: const InputDecoration( + decoration: InputDecoration( labelText: 'Destination Address', - border: OutlineInputBorder(), - prefixIcon: Icon(Icons.location_on), + border: const OutlineInputBorder(), + prefixIcon: const Icon(Icons.location_on), + suffixIcon: IconButton( + icon: const Icon(Icons.qr_code_scanner), + onPressed: _scanQRCode, + ), ), ), const SizedBox(height: 16), + // Amount field with MAX TextField( controller: _amountController, decoration: InputDecoration( @@ -333,9 +487,34 @@ class _SendSuiScreenState extends State { const SizedBox(height: 16), - // Gas budget removed from SUI UI by request + // Warning + Card( + color: Theme.of(context).brightness == Brightness.dark + ? AppTheme.warningColor.withOpacity(0.12) + : AppTheme.warningColor.withOpacity(0.08), + child: Padding( + padding: const EdgeInsets.all(12), + child: Row( + children: [ + Icon(Icons.warning, color: AppTheme.warningColor, size: 20), + const SizedBox(width: 8), + Expanded( + child: Text( + 'Please check the address and amount carefully. Once sent, it cannot be undone.', + style: TextStyle( + fontSize: 12, + color: Theme.of(context).brightness == Brightness.dark + ? AppTheme.darkTextPrimary + : AppTheme.lightTextPrimary, + ), + ), + ), + ], + ), + ), + ), - const SizedBox(height: 24), + const SizedBox(height: 20), ElevatedButton.icon( onPressed: _isLoading ? null : _sendSui, @@ -350,4 +529,17 @@ class _SendSuiScreenState extends State { ), ); } + + Widget _buildQRScanner() { + return Scaffold( + appBar: AppBar( + title: const Text('Scan QR Code'), + leading: IconButton( + icon: const Icon(Icons.close), + onPressed: () => setState(() => _showScanner = false), + ), + ), + body: MobileScanner(onDetect: _onQRCodeDetected), + ); + } } diff --git a/lib/screenpage/SettingsScreen.dart b/lib/screenpage/SettingsScreen.dart index 41da7f1..b4f66fe 100644 --- a/lib/screenpage/SettingsScreen.dart +++ b/lib/screenpage/SettingsScreen.dart @@ -82,7 +82,7 @@ class _SettingsScreenState extends State { return Scaffold( appBar: AppBar( title: const Text('Settings'), - backgroundColor: Colors.transparent, + backgroundColor: AppTheme.transparent, elevation: 0, ), body: SingleChildScrollView( @@ -131,13 +131,7 @@ class _SettingsScreenState extends State { decoration: BoxDecoration( color: Theme.of(context).cardColor, borderRadius: BorderRadius.circular(AppTheme.borderRadiusMedium), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.05), - blurRadius: 10, - offset: const Offset(0, 4), - ), - ], + boxShadow: AppTheme.cardShadow, ), child: ListTile( leading: Container( @@ -183,13 +177,7 @@ class _SettingsScreenState extends State { decoration: BoxDecoration( color: Theme.of(context).cardColor, borderRadius: BorderRadius.circular(AppTheme.borderRadiusMedium), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.05), - blurRadius: 10, - offset: const Offset(0, 4), - ), - ], + boxShadow: AppTheme.cardShadow, ), child: Column( children: [ @@ -258,13 +246,7 @@ class _SettingsScreenState extends State { decoration: BoxDecoration( color: Theme.of(context).cardColor, borderRadius: BorderRadius.circular(AppTheme.borderRadiusMedium), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.05), - blurRadius: 10, - offset: const Offset(0, 4), - ), - ], + boxShadow: AppTheme.cardShadow, ), child: Column( children: [ diff --git a/lib/screenpage/ShowMnemonicScreen.dart b/lib/screenpage/ShowMnemonicScreen.dart index 385c2f5..9ca2d2c 100644 --- a/lib/screenpage/ShowMnemonicScreen.dart +++ b/lib/screenpage/ShowMnemonicScreen.dart @@ -125,7 +125,7 @@ class ShowMnemonicScreen extends StatelessWidget { width: 24, height: 24, decoration: BoxDecoration( - color: Theme.of(context).primaryColor, + color: Theme.of(context).iconTheme.color ?? Theme.of(context).primaryColor, shape: BoxShape.circle, ), child: Center( diff --git a/lib/screenpage/TransactionDetailScreen.dart b/lib/screenpage/TransactionDetailScreen.dart index ce05599..6dbed74 100644 --- a/lib/screenpage/TransactionDetailScreen.dart +++ b/lib/screenpage/TransactionDetailScreen.dart @@ -122,7 +122,7 @@ class _TransactionDetailScreenState extends State { backgroundColor: AppTheme.surfaceColor, appBar: AppBar( title: const Text('Transaction Details'), - backgroundColor: Colors.transparent, + backgroundColor: AppTheme.transparent, elevation: 0, leading: IconButton( icon: Icon(Icons.arrow_back, color: AppTheme.textPrimary), diff --git a/lib/screenpage/TransactionHistoryScreen.dart b/lib/screenpage/TransactionHistoryScreen.dart index 679e701..f1d1dfb 100644 --- a/lib/screenpage/TransactionHistoryScreen.dart +++ b/lib/screenpage/TransactionHistoryScreen.dart @@ -119,7 +119,7 @@ class _TransactionHistoryScreenState extends State { backgroundColor: AppTheme.surfaceColor, appBar: AppBar( title: const Text('Transaction History'), - backgroundColor: Colors.transparent, + backgroundColor: AppTheme.transparent, elevation: 0, leading: IconButton( icon: Icon(Icons.arrow_back, color: AppTheme.textPrimary), diff --git a/lib/screenpage/WalletListScreen.dart b/lib/screenpage/WalletListScreen.dart index e2bd874..6b41bb9 100644 --- a/lib/screenpage/WalletListScreen.dart +++ b/lib/screenpage/WalletListScreen.dart @@ -191,7 +191,7 @@ class _WalletListScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: AppTheme.surfaceColor, + backgroundColor: Theme.of(context).scaffoldBackgroundColor, appBar: AppBar( title: Column( crossAxisAlignment: CrossAxisAlignment.center, @@ -199,15 +199,10 @@ class _WalletListScreenState extends State { children: [const Text('My Wallets')], ), elevation: 0, - backgroundColor: Colors.transparent, + backgroundColor: AppTheme.transparent, ), - body: _isLoading - ? Center( - child: CircularProgressIndicator( - strokeWidth: 2, - valueColor: AlwaysStoppedAnimation(AppTheme.primaryColor), - ), - ) + body: _isLoading + ? Center(child: CircularProgressIndicator(strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Theme.of(context).colorScheme.primary))) : (_wallets.isEmpty ? _buildEmptyState() : _buildWalletList()), ); } @@ -250,18 +245,17 @@ class _WalletListScreenState extends State { final wallet = _wallets[index]; final isActive = _activeWallet?.address == wallet.address; + // Add a subtle border so cards are visible in dark mode return Card( margin: const EdgeInsets.only(bottom: 8), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + side: BorderSide(color: Theme.of(context).dividerColor.withOpacity(0.06)), + ), child: ListTile( leading: CircleAvatar( - backgroundColor: isActive - ? AppTheme.primaryColor - : Theme.of(context).disabledColor, - child: Icon( - Icons.account_balance_wallet, - color: Colors.white, - size: 20, - ), + backgroundColor: isActive ? AppTheme.primaryColor : Theme.of(context).disabledColor, + child: Icon(Icons.account_balance_wallet, color: Theme.of(context).colorScheme.onPrimary, size: 20), ), title: Row( children: [ @@ -272,14 +266,8 @@ class _WalletListScreenState extends State { horizontal: 8, vertical: 2, ), - decoration: BoxDecoration( - color: AppTheme.primaryColor, - borderRadius: BorderRadius.circular(12), - ), - child: const Text( - 'In Use', - style: TextStyle(color: Colors.white, fontSize: 10), - ), + decoration: BoxDecoration(color: AppTheme.primaryColor, borderRadius: BorderRadius.circular(12)), + child: Text('In Use', style: TextStyle(color: Theme.of(context).colorScheme.onPrimary, fontSize: 10)), ), ], ), diff --git a/lib/screenpage/WalletScreen.dart b/lib/screenpage/WalletScreen.dart index c81e078..bcd8e02 100644 --- a/lib/screenpage/WalletScreen.dart +++ b/lib/screenpage/WalletScreen.dart @@ -44,7 +44,6 @@ class WalletScreenState extends State { _loadWallet(); } - @override void dispose() { _walletService.dispose(); @@ -53,18 +52,20 @@ class WalletScreenState extends State { Future _loadWallet() async { setState(() => _isLoading = true); - + try { print('=== Loading wallet data ==='); final wallet = await _walletService.getActiveWallet(); final network = await _walletService.getCurrentNetwork(); - + print('Current network: ${network.name} (${network.id})'); print('Chain ID: ${network.chainId}'); print('RPC URL: ${network.rpcUrl}'); - + if (wallet != null) { - print('Loading balance for wallet: ${wallet.address} on network: ${network.name}'); + print( + 'Loading balance for wallet: ${wallet.address} on network: ${network.name}', + ); double balance = 0.0; if (network.id.toLowerCase().contains('sui')) { balance = await _walletService.getSuiBalance(wallet.address); @@ -76,10 +77,10 @@ class WalletScreenState extends State { balance = await _walletService.getEthBalance(wallet.address); setState(() => _ethBalance = balance); } - + // Load tokens for this wallet and network final tokens = await _tokenService.getTokenBalances(wallet, network.id); - + print('Balance loaded: $balance ${network.currencySymbol}'); print('Tokens loaded: ${tokens.length} tokens'); setState(() { @@ -89,14 +90,16 @@ class WalletScreenState extends State { _tokens = tokens; _tokens = tokens; }); - + // Calculate total balance with real-time prices try { final totalBalance = await _calculateTotalBalance(); setState(() { _totalBalance = totalBalance; }); - print('Total balance calculated: \$${_totalBalance.toStringAsFixed(2)}'); + print( + 'Total balance calculated: \$${_totalBalance.toStringAsFixed(2)}', + ); } catch (e) { print('Error calculating total balance: $e'); setState(() { @@ -123,24 +126,26 @@ class WalletScreenState extends State { Future _calculateTotalBalance() async { // Use current network's currency price (real-time prices) double nativeTokenPrice = await _getNativeTokenPrice(); - // Use SUI balance for Sui networks, otherwise use ETH/EVM native balance - final isSui = _currentNetwork != null && _currentNetwork!.id.toLowerCase().contains('sui'); - final nativeBalance = isSui ? _suiBalance : _ethBalance; - double total = nativeBalance * nativeTokenPrice; - + // Use SUI balance for Sui networks, otherwise use ETH/EVM native balance + final isSui = + _currentNetwork != null && + _currentNetwork!.id.toLowerCase().contains('sui'); + final nativeBalance = isSui ? _suiBalance : _ethBalance; + double total = nativeBalance * nativeTokenPrice; + // Add custom token values for (final token in _tokens) { if (!token.isNative) { total += token.usdValue; } } - + return total; } Future _getNativeTokenPrice() async { if (_currentNetwork == null) return 0.0; // Return 0 if no network - + try { final symbol = _currentNetwork!.currencySymbol; final price = await _priceService.getTokenPrice(symbol); @@ -158,7 +163,7 @@ class WalletScreenState extends State { context, MaterialPageRoute(builder: (context) => const NetworkSelectionScreen()), ); - + if (result == true) { // Force refresh network connection and reload wallet data setState(() { @@ -167,17 +172,17 @@ class WalletScreenState extends State { _ethBalance = 0.0; _tokens = []; }); - + try { // Get the new network and switch to it final newNetwork = await _walletService.getCurrentNetwork(); print('Switched to network: ${newNetwork.name}'); - + // Update UI with new network info immediately setState(() { _currentNetwork = newNetwork; }); - + // Reload all wallet data with new network await _loadWallet(); } catch (e) { @@ -192,7 +197,7 @@ class WalletScreenState extends State { context, MaterialPageRoute(builder: (context) => const CreateWalletScreen()), ); - + if (result != null) { await _loadWallet(); } @@ -203,7 +208,7 @@ class WalletScreenState extends State { context, MaterialPageRoute(builder: (context) => const WalletListScreen()), ); - + if (result != null) { await _loadWallet(); } @@ -232,42 +237,18 @@ class WalletScreenState extends State { } } - Color _getNetworkColor(NetworkModel network) { - if (network.isCustom) return AppTheme.primaryColor; - if (network.isTestnet) return Colors.orange; - - switch (network.id) { - case 'ethereum': - case 'sepolia': - return AppTheme.primaryColor; - case 'bsc': - case 'bsc-testnet': - return const Color(0xFFF3BA2F); - case 'polygon': - case 'mumbai': - return AppTheme.primaryColor; - case 'avalanche': - case 'fuji': - return const Color(0xFFE84142); - case 'fantom': - case 'fantom-testnet': - return AppTheme.primaryColor; - case 'alpen-testnet': - return const Color(0xFFF7931A); // Bitcoin orange - default: - return AppTheme.primaryColor; - } - } + IconData _getNetworkIcon(NetworkModel network) { if (network.isCustom) return Icons.lan; if (network.isTestnet) return Icons.code; - + switch (network.id) { case 'sui-devnet': case 'sui-testnet': case 'sui-mainnet': - return Icons.language; // placeholder for Sui - replace with custom icon if available + return Icons + .language; // placeholder for Sui - replace with custom icon if available case 'ethereum': case 'sepolia': return Icons.currency_bitcoin; // Use as Ethereum placeholder @@ -296,7 +277,6 @@ class WalletScreenState extends State { return Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( - color: _getNetworkColor(network), shape: BoxShape.circle, ), child: ClipOval( @@ -309,7 +289,7 @@ class WalletScreenState extends State { // Fallback to default icon if URL fails return Icon( _getNetworkIcon(network), - color: Colors.white, + color: Theme.of(context).colorScheme.onPrimary, size: 20, ); }, @@ -320,7 +300,9 @@ class WalletScreenState extends State { height: 20, child: CircularProgressIndicator( strokeWidth: 1, - valueColor: AlwaysStoppedAnimation(Colors.white), + valueColor: AlwaysStoppedAnimation( + Theme.of(context).colorScheme.onPrimary, + ), ), ); }, @@ -328,17 +310,16 @@ class WalletScreenState extends State { ), ); } - + // Default icon return Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( - color: _getNetworkColor(network), shape: BoxShape.circle, ), child: Icon( _getNetworkIcon(network), - color: Colors.white, + color: Theme.of(context).colorScheme.onPrimary, size: 20, ), ); @@ -347,10 +328,10 @@ class WalletScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: AppTheme.surfaceColor, + backgroundColor: Theme.of(context).scaffoldBackgroundColor, appBar: AppBar( title: const Text('Kanari Wallet'), - backgroundColor: Colors.transparent, + backgroundColor: AppTheme.transparent, elevation: 0, leading: _currentNetwork != null ? IconButton( @@ -389,7 +370,10 @@ class WalletScreenState extends State { value: 'create', child: Row( children: [ - Icon(Icons.add_circle_outline, color: AppTheme.primaryColor), + Icon( + Icons.add_circle_outline, + color: AppTheme.primaryColor, + ), const SizedBox(width: 8), const Text('Create Wallet'), ], @@ -398,32 +382,33 @@ class WalletScreenState extends State { ], ), IconButton( - icon: Icon( - Icons.settings_outlined, - color: AppTheme.textPrimary, - ), + icon: Icon(Icons.settings_outlined, color: AppTheme.textPrimary), onPressed: _openSettings, tooltip: 'Settings', ), + const SizedBox(width: AppTheme.spacingS), ], ), body: _isLoading - ? const Center( + ? Center( child: CircularProgressIndicator( - valueColor: AlwaysStoppedAnimation(AppTheme.primaryColor), + valueColor: AlwaysStoppedAnimation( + Theme.of(context).colorScheme.primary, + ), ), ) : _currentWallet == null - ? _buildNoWalletState() - : _buildWalletContent(), + ? _buildNoWalletState() + : _buildWalletContent(), ); } Widget _buildNoWalletState() { return EmptyState( title: 'Welcome to Kanari Wallet', - subtitle: 'Create your first wallet to get started with secure crypto transactions', + subtitle: + 'Create your first wallet to get started with secure crypto transactions', icon: Icons.account_balance_wallet_outlined, buttonText: 'Create New Wallet', onButtonPressed: _openCreateWallet, @@ -442,33 +427,43 @@ class WalletScreenState extends State { children: [ // Balance Card BalanceCard( - totalBalance: (_currentNetwork != null && _currentNetwork!.id.toLowerCase().contains('sui') ? _suiBalance : _totalBalance).toStringAsFixed(2), - currency: (_currentNetwork != null && _currentNetwork!.id.toLowerCase().contains('sui')) ? 'SUI' : 'USD', + totalBalance: + (_currentNetwork != null && + _currentNetwork!.id.toLowerCase().contains('sui') + ? _suiBalance + : _totalBalance) + .toStringAsFixed(2), + currency: + (_currentNetwork != null && + _currentNetwork!.id.toLowerCase().contains('sui')) + ? 'SUI' + : 'USD', walletName: _currentWallet?.name, walletAddress: _currentWallet?.address, networkName: _currentNetwork?.name, - networkIcon: _currentNetwork != null ? _getNetworkIcon(_currentNetwork!) : null, - networkColor: _currentNetwork != null ? _getNetworkColor(_currentNetwork!) : null, + networkIcon: _currentNetwork != null + ? _getNetworkIcon(_currentNetwork!) + : null, onCopyAddress: _copyAddress, onNetworkTap: _openNetworkSelection, ), - + const SizedBox(height: AppTheme.spacingL), - + // Action Buttons _buildActionButtons(), - + const SizedBox(height: AppTheme.spacingXL), - + // Assets Section Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'My Assets', - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: AppTheme.textPrimary, - ), + style: Theme.of( + context, + ).textTheme.titleLarge?.copyWith(color: AppTheme.textPrimary), ), TextButton.icon( onPressed: _openAddTokenScreen, @@ -480,9 +475,9 @@ class WalletScreenState extends State { ), ], ), - + const SizedBox(height: AppTheme.spacingM), - + // Asset List _buildAssetList(), ], @@ -498,7 +493,7 @@ class WalletScreenState extends State { label: 'Send', icon: Icons.arrow_upward_rounded, onPressed: _currentWallet != null ? _openSendScreen : null, - backgroundColor: Colors.white, + backgroundColor: Theme.of(context).cardColor, iconColor: AppTheme.primaryColor, ), const SizedBox(width: AppTheme.spacingM), @@ -506,7 +501,7 @@ class WalletScreenState extends State { label: 'Receive', icon: Icons.arrow_downward_rounded, onPressed: _currentWallet != null ? _openReceiveScreen : null, - backgroundColor: Colors.white, + backgroundColor: Theme.of(context).cardColor, iconColor: AppTheme.secondaryColor, ), const SizedBox(width: AppTheme.spacingM), @@ -514,17 +509,9 @@ class WalletScreenState extends State { label: 'History', icon: Icons.history, onPressed: _currentWallet != null ? _openTransactionHistory : null, - backgroundColor: Colors.white, - iconColor: Colors.purple, + backgroundColor: Theme.of(context).cardColor, + iconColor: Theme.of(context).colorScheme.secondary, ), - // const SizedBox(width: AppTheme.spacingM), - // ActionButton( - // label: 'Buy', - // icon: Icons.add_circle_outline, - // onPressed: null, // TODO: Implement buy functionality - // backgroundColor: Colors.white, - // iconColor: AppTheme.primaryColor, - // ), ], ); } @@ -532,7 +519,9 @@ class WalletScreenState extends State { void _openSendScreen() { if (_currentWallet != null) { // Choose SUI UI for Sui networks, otherwise use EVM send UI - final isSui = _currentNetwork != null && _currentNetwork!.id.toLowerCase().contains('sui'); + final isSui = + _currentNetwork != null && + _currentNetwork!.id.toLowerCase().contains('sui'); Navigator.push( context, @@ -561,7 +550,8 @@ class WalletScreenState extends State { Navigator.push( context, MaterialPageRoute( - builder: (context) => TransactionHistoryScreen(wallet: _currentWallet!), + builder: (context) => + TransactionHistoryScreen(wallet: _currentWallet!), ), ); } @@ -575,7 +565,7 @@ class WalletScreenState extends State { builder: (context) => AddTokenScreen(network: _currentNetwork!), ), ); - + if (result != null) { // Reload wallet to show the new token await _loadWallet(); @@ -600,8 +590,8 @@ class WalletScreenState extends State { amount: token.balance, usdValue: token.usdValue, iconUrl: token.iconUrl, - icon: token.isNative && _currentNetwork != null - ? _getNetworkIcon(_currentNetwork!) + icon: token.isNative && _currentNetwork != null + ? _getNetworkIcon(_currentNetwork!) : Icons.token, onTap: () { // TODO: Show token details @@ -615,7 +605,7 @@ class WalletScreenState extends State { void _showTokenDetails(CustomTokenModel token) { showModalBottomSheet( context: context, - backgroundColor: Colors.transparent, + backgroundColor: AppTheme.transparent, builder: (context) => Container( decoration: BoxDecoration( color: Theme.of(context).scaffoldBackgroundColor, @@ -638,7 +628,7 @@ class WalletScreenState extends State { ), ), const SizedBox(height: AppTheme.spacingL), - + // Token info Row( children: [ @@ -647,11 +637,15 @@ class WalletScreenState extends State { height: 50, decoration: BoxDecoration( color: AppTheme.surfaceColor, - borderRadius: BorderRadius.circular(AppTheme.borderRadiusSmall), + borderRadius: BorderRadius.circular( + AppTheme.borderRadiusSmall, + ), ), child: token.iconUrl != null ? ClipRRect( - borderRadius: BorderRadius.circular(AppTheme.borderRadiusSmall), + borderRadius: BorderRadius.circular( + AppTheme.borderRadiusSmall, + ), child: Image.network( token.iconUrl!, fit: BoxFit.cover, @@ -660,9 +654,9 @@ class WalletScreenState extends State { ), ) : Icon( - token.isNative && _currentNetwork != null - ? _getNetworkIcon(_currentNetwork!) - : Icons.token, + token.isNative && _currentNetwork != null + ? _getNetworkIcon(_currentNetwork!) + : Icons.token, color: AppTheme.primaryColor, ), ), @@ -686,16 +680,18 @@ class WalletScreenState extends State { ), ], ), - + const SizedBox(height: AppTheme.spacingL), - + // Balance info Container( width: double.infinity, padding: const EdgeInsets.all(AppTheme.spacingL), decoration: BoxDecoration( color: AppTheme.surfaceColor, - borderRadius: BorderRadius.circular(AppTheme.borderRadiusMedium), + borderRadius: BorderRadius.circular( + AppTheme.borderRadiusMedium, + ), ), child: Column( children: [ @@ -713,9 +709,9 @@ class WalletScreenState extends State { ], ), ), - + const SizedBox(height: AppTheme.spacingL), - + // Actions if (!token.isNative) ...[ Row( @@ -749,7 +745,9 @@ class WalletScreenState extends State { context: context, builder: (context) => AlertDialog( title: const Text('Remove Token'), - content: Text('Are you sure you want to remove ${token.symbol} from your wallet?'), + content: Text( + 'Are you sure you want to remove ${token.symbol} from your wallet?', + ), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), @@ -766,10 +764,10 @@ class WalletScreenState extends State { if (confirmed == true) { final success = await _tokenService.removeCustomToken( - token.contractAddress, + token.contractAddress, token.networkId, ); - + if (success) { await _loadWallet(); // Reload to update the list if (mounted) { diff --git a/lib/screenpage/WelcomeScreen.dart b/lib/screenpage/WelcomeScreen.dart index 9c204ff..8bb0f4c 100644 --- a/lib/screenpage/WelcomeScreen.dart +++ b/lib/screenpage/WelcomeScreen.dart @@ -58,8 +58,8 @@ class _WelcomeScreenState extends State @override Widget build(BuildContext context) { final bool isDark = Theme.of(context).brightness == Brightness.dark; - final Decoration backgroundDecoration = isDark - ? const BoxDecoration(gradient: AppTheme.darkBackgroundGradient) + final Decoration backgroundDecoration = isDark + ? const BoxDecoration(gradient: AppTheme.darkBackgroundGradient) : BoxDecoration( color: AppTheme.lightBackgroundColor, gradient: LinearGradient( @@ -95,7 +95,9 @@ class _WelcomeScreenState extends State width: 120, height: 120, decoration: BoxDecoration( - gradient: isDark ? AppTheme.darkBackgroundGradient : null, + gradient: isDark + ? AppTheme.darkBackgroundGradient + : null, color: isDark ? null : AppTheme.primaryColor.withOpacity(0.12), @@ -115,7 +117,7 @@ class _WelcomeScreenState extends State Icons.account_balance_wallet_outlined, size: 60, color: isDark - ? Colors.white + ? AppTheme.white : AppTheme.primaryColor, ), ), @@ -128,7 +130,9 @@ class _WelcomeScreenState extends State style: TextStyle( fontSize: 32, fontWeight: FontWeight.w800, - color: isDark ? Colors.white : AppTheme.textPrimary, + color: isDark + ? AppTheme.white + : AppTheme.textPrimary, letterSpacing: 0.5, shadows: const [ Shadow( @@ -192,17 +196,17 @@ class _WelcomeScreenState extends State decoration: BoxDecoration( color: isDark ? AppTheme.darkCardColor.withOpacity(0.12) - : Colors.black.withOpacity(0.02), + : AppTheme.textMuted.withOpacity(0.02), borderRadius: BorderRadius.circular(24), border: Border.all( color: isDark ? AppTheme.darkCardColor.withOpacity(0.18) - : Colors.black.withOpacity(0.04), + : AppTheme.textMuted.withOpacity(0.04), width: 1, ), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity( + color: AppTheme.textMuted.withOpacity( isDark ? 0.08 : 0.04, ), blurRadius: 20, @@ -295,7 +299,9 @@ class _WelcomeScreenState extends State width: 48, height: 48, decoration: BoxDecoration( - gradient: isDark ? AppTheme.darkBackgroundGradient : AppTheme.primaryGradient, + gradient: isDark + ? AppTheme.darkBackgroundGradient + : AppTheme.primaryGradient, borderRadius: BorderRadius.circular(14), boxShadow: [ BoxShadow( diff --git a/lib/services/network_service.dart b/lib/services/network_service.dart index 3ecbc66..c83d2cf 100644 --- a/lib/services/network_service.dart +++ b/lib/services/network_service.dart @@ -17,7 +17,7 @@ class NetworkService { rpcUrl: sui.SuiUrls.devnet, chainId: 0, currencySymbol: 'SUI', - blockExplorerUrl: null, + blockExplorerUrl: 'https://devnet.suivision.xyz/', isTestnet: true, iconPath: 'assets/icons/sui.png', ), @@ -27,7 +27,7 @@ class NetworkService { rpcUrl: sui.SuiUrls.testnet, chainId: 0, currencySymbol: 'SUI', - blockExplorerUrl: null, + blockExplorerUrl: 'https://testnet.suivision.xyz', isTestnet: true, iconPath: 'assets/icons/sui.png', ), @@ -37,7 +37,7 @@ class NetworkService { rpcUrl: sui.SuiUrls.mainnet, chainId: 0, currencySymbol: 'SUI', - blockExplorerUrl: null, + blockExplorerUrl: 'https://suivision.xyz', isTestnet: false, iconPath: 'assets/icons/sui.png', ), diff --git a/lib/utils/app_theme.dart b/lib/utils/app_theme.dart index 5a44b36..678f651 100644 --- a/lib/utils/app_theme.dart +++ b/lib/utils/app_theme.dart @@ -1,40 +1,44 @@ import 'package:flutter/material.dart'; class AppTheme { - // Color palette - // Brand primary set to orange - static const Color primaryColor = Color(0xFFF97316); // Orange (tailwind orange-500) - static const Color primaryVariant = Color(0xFFEA580C); // Orange-600 - static const Color secondaryColor = Color(0xFF10B981); // Success green - static const Color successColor = Color(0xFF10B981); // Success green (alias) + // Elegant, minimal palette: deep navy + soft gold accent + static const Color primaryColor = Color(0xFF0B3D91); // deep navy + static const Color primaryAccent = Color(0xFFC9A24A); // soft gold + // kept for backwards compatibility with existing screens + static const Color primaryVariant = Color(0xFF133C8F); + static const Color secondaryColor = Color(0xFF00BFA6); // teal accent for success + static const Color successColor = secondaryColor; static const Color errorColor = Color(0xFFEF4444); static const Color warningColor = Color(0xFFF59E0B); - + // Transparent alias to avoid direct Colors.transparent usage + static const Color transparent = Color(0x00000000); + // Common white alias to avoid direct Colors.white usage in widgets + static const Color white = Color(0xFFFFFFFF); + // Light Theme Colors - static const Color lightSurfaceColor = Color(0xFFF8FAFC); + static const Color lightSurfaceColor = Color(0xFFF6F7FB); static const Color lightBackgroundColor = Color(0xFFFFFFFF); static const Color lightCardColor = Color(0xFFFFFFFF); - static const Color lightTextPrimary = Color(0xFF1F2937); - static const Color lightTextSecondary = Color(0xFF6B7280); - static const Color lightTextMuted = Color(0xFF9CA3AF); - + static const Color lightTextPrimary = Color(0xFF0F1724); + static const Color lightTextSecondary = Color(0xFF51607A); + static const Color lightTextMuted = Color(0xFF94A3B8); + // Dark Theme Colors - static const Color darkSurfaceColor = Color(0xFF111827); - static const Color darkBackgroundColor = Color(0xFF1F2937); - static const Color darkCardColor = Color(0xFF374151); - static const Color darkTextPrimary = Color(0xFFF9FAFB); - static const Color darkTextSecondary = Color(0xFFE5E7EB); // เพิ่มความชัด - static const Color darkTextMuted = Color(0xFFD1D5DB); // เพิ่มความชัด - - // Dynamic colors based on theme mode + static const Color darkSurfaceColor = Color(0xFF0B1220); + static const Color darkBackgroundColor = Color(0xFF071026); + static const Color darkCardColor = Color(0xFF0F1724); + static const Color darkTextPrimary = Color(0xFFF7FAFC); + static const Color darkTextSecondary = Color(0xFFCBD5E1); + static const Color darkTextMuted = Color(0xFF94A3B8); + + // Dynamic colors based on theme mode (updated at runtime) static Color surfaceColor = lightSurfaceColor; static Color backgroundColor = lightBackgroundColor; static Color cardColor = lightCardColor; static Color textPrimary = lightTextPrimary; static Color textSecondary = lightTextSecondary; static Color textMuted = lightTextMuted; - - // Method to update theme colors based on brightness + static void updateThemeColors(bool isDark) { if (isDark) { surfaceColor = darkSurfaceColor; @@ -52,60 +56,51 @@ class AppTheme { textMuted = lightTextMuted; } } - - // Gradient colors + + // Subtle gradients for cards static const LinearGradient primaryGradient = LinearGradient( - colors: [primaryColor, primaryVariant], + colors: [Color(0xFF0F3A8C), Color(0xFF133C8F)], begin: Alignment.topLeft, end: Alignment.bottomRight, ); - // Background gradient for dark mode (neutral, non-orange) static const LinearGradient darkBackgroundGradient = LinearGradient( colors: [darkSurfaceColor, darkBackgroundColor], begin: Alignment.topLeft, end: Alignment.bottomRight, ); - - static const LinearGradient successGradient = LinearGradient( - colors: [Color(0xFF10B981), Color(0xFF059669)], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ); - // Border radius - static const double borderRadiusSmall = 8.0; - static const double borderRadiusMedium = 12.0; - static const double borderRadiusLarge = 16.0; - static const double borderRadiusXLarge = 24.0; + // Radii & spacing tuned for a refined look + static const double borderRadiusSmall = 10.0; + static const double borderRadiusMedium = 14.0; + static const double borderRadiusLarge = 18.0; + static const double borderRadiusXLarge = 26.0; - // Spacing - static const double spacingXS = 4.0; - static const double spacingS = 8.0; + static const double spacingXS = 6.0; + static const double spacingS = 10.0; static const double spacingM = 16.0; - static const double spacingL = 24.0; + static const double spacingL = 22.0; static const double spacingXL = 32.0; - static const double spacingXXL = 48.0; - // Shadows + // Softer shadows for depth static List get cardShadow => [ - BoxShadow( - color: Colors.black.withOpacity(0.05), - blurRadius: 10, - offset: const Offset(0, 4), - ), - ]; + BoxShadow( + color: Colors.black.withOpacity(0.06), + blurRadius: 14, + offset: const Offset(0, 6), + ), + ]; static List get elevatedShadow => [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 20, - offset: const Offset(0, 8), - ), - ]; + BoxShadow( + color: Colors.black.withOpacity(0.08), + blurRadius: 22, + offset: const Offset(0, 10), + ), + ]; - // Theme data - static ThemeData lightTheme = ThemeData( + // Light theme + static final ThemeData lightTheme = ThemeData( useMaterial3: true, colorScheme: ColorScheme.fromSeed( seedColor: primaryColor, @@ -115,18 +110,20 @@ class AppTheme { error: errorColor, surface: lightSurfaceColor, background: lightBackgroundColor, + onPrimary: Colors.white, + onSurface: lightTextPrimary, ), scaffoldBackgroundColor: lightSurfaceColor, cardTheme: CardThemeData( color: lightCardColor, - elevation: 0, + elevation: 2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(borderRadiusMedium), ), - shadowColor: Colors.black.withOpacity(0.05), + shadowColor: Colors.black.withOpacity(0.06), ), appBarTheme: AppBarTheme( - backgroundColor: lightBackgroundColor, + backgroundColor: Colors.transparent, foregroundColor: lightTextPrimary, elevation: 0, centerTitle: true, @@ -134,35 +131,30 @@ class AppTheme { color: lightTextPrimary, fontSize: 18, fontWeight: FontWeight.w600, + letterSpacing: 0.2, ), ), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( backgroundColor: primaryColor, foregroundColor: Colors.white, - elevation: 0, + elevation: 4, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(borderRadiusMedium), ), - padding: const EdgeInsets.symmetric(horizontal: spacingL, vertical: spacingM), - textStyle: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - ), + padding: const EdgeInsets.symmetric(horizontal: spacingL, vertical: spacingS), + textStyle: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600), ), ), outlinedButtonTheme: OutlinedButtonThemeData( style: OutlinedButton.styleFrom( foregroundColor: primaryColor, - side: const BorderSide(color: primaryColor), + side: BorderSide(color: primaryColor.withOpacity(0.14)), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(borderRadiusMedium), ), - padding: const EdgeInsets.symmetric(horizontal: spacingL, vertical: spacingM), - textStyle: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - ), + padding: const EdgeInsets.symmetric(horizontal: spacingL, vertical: spacingS), + textStyle: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600), ), ), inputDecorationTheme: InputDecorationTheme( @@ -170,77 +162,36 @@ class AppTheme { fillColor: lightBackgroundColor, border: OutlineInputBorder( borderRadius: BorderRadius.circular(borderRadiusMedium), - borderSide: BorderSide(color: Colors.grey.shade300), + borderSide: BorderSide(color: Colors.grey.shade200), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(borderRadiusMedium), - borderSide: BorderSide(color: Colors.grey.shade300), + borderSide: BorderSide(color: Colors.grey.shade200), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(borderRadiusMedium), - borderSide: const BorderSide(color: primaryColor, width: 2), + borderSide: BorderSide(color: primaryColor.withOpacity(0.9), width: 2), ), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(borderRadiusMedium), - borderSide: const BorderSide(color: errorColor), + borderSide: BorderSide(color: errorColor), ), contentPadding: const EdgeInsets.symmetric(horizontal: spacingM, vertical: spacingM), ), textTheme: TextTheme( - headlineLarge: TextStyle( - color: lightTextPrimary, - fontSize: 32, - fontWeight: FontWeight.bold, - ), - headlineMedium: TextStyle( - color: lightTextPrimary, - fontSize: 28, - fontWeight: FontWeight.bold, - ), - headlineSmall: TextStyle( - color: lightTextPrimary, - fontSize: 24, - fontWeight: FontWeight.w600, - ), - titleLarge: TextStyle( - color: lightTextPrimary, - fontSize: 20, - fontWeight: FontWeight.w600, - ), - titleMedium: TextStyle( - color: lightTextPrimary, - fontSize: 18, - fontWeight: FontWeight.w600, - ), - titleSmall: TextStyle( - color: lightTextPrimary, - fontSize: 16, - fontWeight: FontWeight.w600, - ), - bodyLarge: TextStyle( - color: lightTextPrimary, - fontSize: 16, - fontWeight: FontWeight.normal, - ), - bodyMedium: TextStyle( - color: lightTextSecondary, - fontSize: 14, - fontWeight: FontWeight.normal, - ), - bodySmall: TextStyle( - color: lightTextMuted, - fontSize: 12, - fontWeight: FontWeight.normal, - ), - labelLarge: TextStyle( - color: lightTextPrimary, - fontSize: 14, - fontWeight: FontWeight.w600, - ), + headlineLarge: TextStyle(color: lightTextPrimary, fontSize: 30, fontWeight: FontWeight.w700), + headlineMedium: TextStyle(color: lightTextPrimary, fontSize: 26, fontWeight: FontWeight.w700), + headlineSmall: TextStyle(color: lightTextPrimary, fontSize: 22, fontWeight: FontWeight.w600), + titleLarge: TextStyle(color: lightTextPrimary, fontSize: 18, fontWeight: FontWeight.w600), + titleMedium: TextStyle(color: lightTextSecondary, fontSize: 16, fontWeight: FontWeight.w600), + bodyLarge: TextStyle(color: lightTextPrimary, fontSize: 15), + bodyMedium: TextStyle(color: lightTextSecondary, fontSize: 14), + bodySmall: TextStyle(color: lightTextMuted, fontSize: 12), + labelLarge: TextStyle(color: lightTextPrimary, fontSize: 14, fontWeight: FontWeight.w600), ), ); - static ThemeData darkTheme = ThemeData( + static final ThemeData darkTheme = ThemeData( useMaterial3: true, colorScheme: ColorScheme.fromSeed( seedColor: primaryColor, @@ -250,128 +201,63 @@ class AppTheme { error: errorColor, surface: darkSurfaceColor, background: darkBackgroundColor, + onPrimary: Colors.white, + onSurface: darkTextPrimary, ), scaffoldBackgroundColor: darkSurfaceColor, cardTheme: CardThemeData( color: darkCardColor, - elevation: 0, + elevation: 2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(borderRadiusMedium), ), - shadowColor: Colors.black.withOpacity(0.3), + shadowColor: Colors.black.withOpacity(0.18), ), appBarTheme: AppBarTheme( - backgroundColor: darkBackgroundColor, + backgroundColor: Colors.transparent, foregroundColor: darkTextPrimary, elevation: 0, centerTitle: true, - titleTextStyle: TextStyle( - color: darkTextPrimary, - fontSize: 18, - fontWeight: FontWeight.w600, - ), + titleTextStyle: TextStyle(color: darkTextPrimary, fontSize: 18, fontWeight: FontWeight.w600), ), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( backgroundColor: primaryColor, foregroundColor: Colors.white, - elevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(borderRadiusMedium), - ), - padding: const EdgeInsets.symmetric(horizontal: spacingL, vertical: spacingM), - textStyle: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - ), + elevation: 4, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(borderRadiusMedium)), + padding: const EdgeInsets.symmetric(horizontal: spacingL, vertical: spacingS), + textStyle: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600), ), ), outlinedButtonTheme: OutlinedButtonThemeData( style: OutlinedButton.styleFrom( foregroundColor: primaryColor, - side: const BorderSide(color: primaryColor), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(borderRadiusMedium), - ), - padding: const EdgeInsets.symmetric(horizontal: spacingL, vertical: spacingM), - textStyle: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - ), + side: BorderSide(color: Colors.white.withOpacity(0.08)), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(borderRadiusMedium)), + padding: const EdgeInsets.symmetric(horizontal: spacingL, vertical: spacingS), + textStyle: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600), ), ), inputDecorationTheme: InputDecorationTheme( filled: true, - fillColor: darkBackgroundColor, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(borderRadiusMedium), - borderSide: BorderSide(color: Colors.grey.shade600), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(borderRadiusMedium), - borderSide: BorderSide(color: Colors.grey.shade600), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(borderRadiusMedium), - borderSide: const BorderSide(color: primaryColor, width: 2), - ), - errorBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(borderRadiusMedium), - borderSide: const BorderSide(color: errorColor), - ), + fillColor: darkCardColor, + border: OutlineInputBorder(borderRadius: BorderRadius.circular(borderRadiusMedium), borderSide: BorderSide(color: Colors.grey.shade800)), + enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(borderRadiusMedium), borderSide: BorderSide(color: Colors.grey.shade800)), + focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(borderRadiusMedium), borderSide: BorderSide(color: primaryColor.withOpacity(0.9), width: 2)), + errorBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(borderRadiusMedium), borderSide: BorderSide(color: errorColor)), contentPadding: const EdgeInsets.symmetric(horizontal: spacingM, vertical: spacingM), ), textTheme: TextTheme( - headlineLarge: TextStyle( - color: darkTextPrimary, - fontSize: 32, - fontWeight: FontWeight.bold, - ), - headlineMedium: TextStyle( - color: darkTextPrimary, - fontSize: 28, - fontWeight: FontWeight.bold, - ), - headlineSmall: TextStyle( - color: darkTextPrimary, - fontSize: 24, - fontWeight: FontWeight.w600, - ), - titleLarge: TextStyle( - color: darkTextPrimary, - fontSize: 20, - fontWeight: FontWeight.w600, - ), - titleMedium: TextStyle( - color: darkTextPrimary, - fontSize: 18, - fontWeight: FontWeight.w600, - ), - titleSmall: TextStyle( - color: darkTextPrimary, - fontSize: 16, - fontWeight: FontWeight.w600, - ), - bodyLarge: TextStyle( - color: darkTextPrimary, - fontSize: 16, - fontWeight: FontWeight.normal, - ), - bodyMedium: TextStyle( - color: darkTextSecondary, - fontSize: 14, - fontWeight: FontWeight.normal, - ), - bodySmall: TextStyle( - color: darkTextMuted, - fontSize: 12, - fontWeight: FontWeight.normal, - ), - labelLarge: TextStyle( - color: darkTextPrimary, - fontSize: 14, - fontWeight: FontWeight.w600, - ), + headlineLarge: TextStyle(color: darkTextPrimary, fontSize: 30, fontWeight: FontWeight.w700), + headlineMedium: TextStyle(color: darkTextPrimary, fontSize: 26, fontWeight: FontWeight.w700), + headlineSmall: TextStyle(color: darkTextPrimary, fontSize: 22, fontWeight: FontWeight.w600), + titleLarge: TextStyle(color: darkTextPrimary, fontSize: 18, fontWeight: FontWeight.w600), + titleMedium: TextStyle(color: darkTextSecondary, fontSize: 16, fontWeight: FontWeight.w600), + bodyLarge: TextStyle(color: darkTextPrimary, fontSize: 15), + bodyMedium: TextStyle(color: darkTextSecondary, fontSize: 14), + bodySmall: TextStyle(color: darkTextMuted, fontSize: 12), + labelLarge: TextStyle(color: darkTextPrimary, fontSize: 14, fontWeight: FontWeight.w600), ), ); } @@ -379,16 +265,10 @@ class AppTheme { // Custom widget extensions extension AppWidgets on Widget { Widget withPadding([EdgeInsets? padding]) { - return Padding( - padding: padding ?? const EdgeInsets.all(AppTheme.spacingM), - child: this, - ); + return Padding(padding: padding ?? const EdgeInsets.all(AppTheme.spacingM), child: this); } Widget withCard([EdgeInsets? margin]) { - return Card( - margin: margin ?? const EdgeInsets.all(AppTheme.spacingS), - child: this, - ); + return Card(margin: margin ?? const EdgeInsets.all(AppTheme.spacingS), child: this); } } diff --git a/lib/utils/custom_widgets.dart b/lib/utils/custom_widgets.dart index 05fd146..9306541 100644 --- a/lib/utils/custom_widgets.dart +++ b/lib/utils/custom_widgets.dart @@ -27,52 +27,37 @@ class CustomButton extends StatelessWidget { Widget build(BuildContext context) { Widget button; + final theme = Theme.of(context); + final foreground = textColor ?? (isOutlined ? theme.colorScheme.primary : Colors.white); + if (isOutlined) { button = OutlinedButton.icon( onPressed: isLoading ? null : onPressed, icon: isLoading - ? SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator( - strokeWidth: 2, - valueColor: AlwaysStoppedAnimation(AppTheme.primaryColor), - ), - ) - : (icon != null ? Icon(icon) : const SizedBox.shrink()), - label: Text(text), + ? SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2, valueColor: AlwaysStoppedAnimation(theme.colorScheme.primary))) + : (icon != null ? Icon(icon, color: foreground) : const SizedBox.shrink()), + label: Text(text, style: TextStyle(color: foreground)), style: OutlinedButton.styleFrom( - foregroundColor: textColor ?? AppTheme.primaryColor, - side: BorderSide(color: backgroundColor ?? AppTheme.primaryColor), - padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(AppTheme.borderRadiusMedium), - ), + foregroundColor: foreground, + side: BorderSide(color: (backgroundColor ?? theme.colorScheme.primary).withOpacity(0.14)), + padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 20), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(AppTheme.borderRadiusMedium)), ), ); } else { button = ElevatedButton.icon( onPressed: isLoading ? null : onPressed, icon: isLoading - ? SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator( - strokeWidth: 2, - valueColor: AlwaysStoppedAnimation(Colors.white), - ), - ) - : (icon != null ? Icon(icon) : const SizedBox.shrink()), - label: Text(text), + ? SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Colors.white))) + : (icon != null ? Icon(icon, color: foreground) : const SizedBox.shrink()), + label: Text(text, style: TextStyle(color: foreground)), style: ElevatedButton.styleFrom( - backgroundColor: backgroundColor ?? AppTheme.primaryColor, - foregroundColor: textColor ?? Colors.white, - padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(AppTheme.borderRadiusMedium), - ), - elevation: onPressed != null ? 2 : 0, - shadowColor: AppTheme.primaryColor.withOpacity(0.3), + backgroundColor: backgroundColor ?? theme.colorScheme.primary, + foregroundColor: foreground, + padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 20), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(AppTheme.borderRadiusMedium)), + elevation: onPressed != null ? 6 : 0, + shadowColor: theme.colorScheme.primary.withOpacity(0.14), ), ); } @@ -116,11 +101,10 @@ class BalanceCard extends StatelessWidget { return Container( width: double.infinity, decoration: BoxDecoration( - gradient: Theme.of(context).brightness == Brightness.dark - ? AppTheme.darkBackgroundGradient - : AppTheme.primaryGradient, + color: Theme.of(context).cardColor, borderRadius: BorderRadius.circular(AppTheme.borderRadiusLarge), boxShadow: AppTheme.elevatedShadow, + border: Border.all(color: Theme.of(context).dividerColor.withOpacity(0.06)), ), child: Padding( padding: EdgeInsets.all( @@ -133,14 +117,7 @@ class BalanceCard extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text( - 'Total Balance', - style: TextStyle( - color: Colors.white70, - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), + Text('Total Balance', style: Theme.of(context).textTheme.titleMedium?.copyWith(color: Theme.of(context).textTheme.bodySmall?.color)), Flexible( child: Row( mainAxisAlignment: MainAxisAlignment.end, @@ -156,13 +133,11 @@ class BalanceCard extends StatelessWidget { vertical: AppTheme.spacingXS, ), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), - borderRadius: BorderRadius.circular( - AppTheme.borderRadiusSmall, - ), - border: Border.all( - color: Colors.white.withOpacity(0.3), - ), + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white.withOpacity(0.06) + : Theme.of(context).cardColor.withOpacity(0.6), + borderRadius: BorderRadius.circular(AppTheme.borderRadiusSmall), + border: Border.all(color: Theme.of(context).dividerColor.withOpacity(0.06)), ), child: Row( mainAxisSize: MainAxisSize.min, @@ -178,11 +153,7 @@ class BalanceCard extends StatelessWidget { ), Text( networkName!, - style: TextStyle( - color: Colors.white, - fontSize: isSmall ? 9 : 11, - fontWeight: FontWeight.w600, - ), + style: TextStyle(color: Theme.of(context).textTheme.bodySmall?.color, fontSize: isSmall ? 9 : 11, fontWeight: FontWeight.w600), overflow: TextOverflow.ellipsis, ), ], @@ -192,15 +163,7 @@ class BalanceCard extends StatelessWidget { SizedBox(width: isSmall ? 4 : AppTheme.spacingS), ], if (walletName != null) - Text( - walletName!, - style: TextStyle( - color: Colors.white, - fontSize: isSmall ? 12 : 14, - fontWeight: FontWeight.w600, - ), - overflow: TextOverflow.ellipsis, - ), + Text(walletName!, style: Theme.of(context).textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w700), overflow: TextOverflow.ellipsis), ], ), ), @@ -211,22 +174,11 @@ class BalanceCard extends StatelessWidget { ), // Balance amount - FittedBox( + FittedBox( fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, - child: Text( - currency == 'USD' ? '\$$totalBalance' : '$totalBalance $currency', - style: TextStyle( - color: Colors.white, - fontSize: isSmall - ? 24 - : isMedium - ? 28 - : 36, - fontWeight: FontWeight.bold, - letterSpacing: -0.5, - ), - ), + child: Text(currency == 'USD' ? '\$$totalBalance' : '$totalBalance $currency', + style: Theme.of(context).textTheme.headlineSmall?.copyWith(fontSize: isSmall ? 22 : (isMedium ? 28 : 34), fontWeight: FontWeight.w800, color: Theme.of(context).textTheme.bodyLarge?.color)), ), SizedBox( @@ -244,23 +196,14 @@ class BalanceCard extends StatelessWidget { : AppTheme.spacingM, vertical: AppTheme.spacingS, ), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.1), - borderRadius: BorderRadius.circular( - AppTheme.borderRadiusSmall, - ), - ), + decoration: BoxDecoration(color: Theme.of(context).brightness == Brightness.dark ? Colors.white.withOpacity(0.03) : Theme.of(context).cardColor.withOpacity(0.6), borderRadius: BorderRadius.circular(AppTheme.borderRadiusSmall)), child: Row( mainAxisSize: MainAxisSize.min, children: [ Flexible( child: Text( '${walletAddress!.substring(0, 6)}...${walletAddress!.substring(walletAddress!.length - 4)}', - style: TextStyle( - color: Colors.white, - fontFamily: 'monospace', - fontSize: isSmall ? 12 : 14, - ), + style: TextStyle(color: Theme.of(context).textTheme.bodyMedium?.color, fontFamily: 'monospace', fontSize: isSmall ? 12 : 14), overflow: TextOverflow.ellipsis, ), ), @@ -387,32 +330,40 @@ class ActionButton extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final bool isDark = theme.brightness == Brightness.dark; + final Color bgColor = backgroundColor ?? theme.cardColor; + final Color effectiveIconColor = iconColor ?? theme.colorScheme.primary; + final Color textColor = iconColor ?? theme.colorScheme.onSurface; + return Expanded( child: Container( height: 60, decoration: BoxDecoration( - color: backgroundColor ?? Theme.of(context).cardColor, + color: bgColor, borderRadius: BorderRadius.circular(AppTheme.borderRadiusMedium), - boxShadow: AppTheme.cardShadow, + boxShadow: isDark + ? [ + BoxShadow( + color: Colors.black.withOpacity(0.06), + blurRadius: 8, + offset: const Offset(0, 3), + ), + ] + : AppTheme.cardShadow, + border: Border.all(color: theme.dividerColor.withOpacity(isDark ? 0.14 : 0.06), width: 1), ), child: Material( - color: Colors.transparent, + color: AppTheme.transparent, child: InkWell( onTap: onPressed, borderRadius: BorderRadius.circular(AppTheme.borderRadiusMedium), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(icon, color: iconColor ?? AppTheme.primaryColor, size: 24), + Icon(icon, color: effectiveIconColor, size: 24), const SizedBox(height: AppTheme.spacingXS), - Text( - label, - style: TextStyle( - color: iconColor ?? AppTheme.primaryColor, - fontWeight: FontWeight.w600, - fontSize: 12, - ), - ), + Text(label, style: TextStyle(color: textColor, fontWeight: FontWeight.w700, fontSize: 13, letterSpacing: 0.2)), ], ), ),