Initial setup: Flutter blockchain explorer app
- Clean architecture with feature-based organization - Riverpod state management, Dio HTTP, GoRouter navigation - Dashboard, blocks, wallet, transactions, mining screens - Dark crypto theme with JetBrains Mono font
This commit is contained in:
commit
5a8300c5ea
30
.gitignore
vendored
Normal file
30
.gitignore
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
*.class
|
||||
*.lock
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.build/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.packages
|
||||
.pub-cache/
|
||||
.pub/
|
||||
build/
|
||||
flutter_*.png
|
||||
linked_*.ds
|
||||
unlinked.ds
|
||||
unlinked_spec.ds
|
||||
.idea/
|
||||
.vagrant/
|
||||
.sconsign.dblite
|
||||
.svn/
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.env
|
||||
100
README.md
Normal file
100
README.md
Normal file
@ -0,0 +1,100 @@
|
||||
# blockchain-flutter
|
||||
|
||||
Flutter app for blockchain explorer, wallet management, and mining. Connects to the blockchain node via REST API.
|
||||
|
||||
## Architecture
|
||||
|
||||
Clean Architecture with feature-based organization:
|
||||
|
||||
```
|
||||
lib/
|
||||
├── main.dart
|
||||
├── core/
|
||||
│ ├── network/
|
||||
│ │ └── api_client.dart # Dio HTTP client setup
|
||||
│ ├── theme/
|
||||
│ │ └── app_theme.dart # Dark theme, crypto aesthetic
|
||||
│ └── utils/
|
||||
│ └── formatters.dart # Amount, hash, date formatting
|
||||
├── features/
|
||||
│ ├── dashboard/ # Home: chain stats, recent blocks
|
||||
│ │ ├── dashboard_screen.dart
|
||||
│ │ └── dashboard_provider.dart
|
||||
│ ├── blocks/ # Block explorer (list + detail)
|
||||
│ │ ├── blocks_screen.dart
|
||||
│ │ ├── block_detail_screen.dart
|
||||
│ │ └── blocks_provider.dart
|
||||
│ ├── wallet/ # Create, view balance, list wallets
|
||||
│ │ ├── wallet_screen.dart
|
||||
│ │ └── wallet_provider.dart
|
||||
│ ├── transactions/ # Send tx, pending list
|
||||
│ │ ├── transactions_screen.dart
|
||||
│ │ ├── send_tx_screen.dart
|
||||
│ │ └── transactions_provider.dart
|
||||
│ └── mining/ # Mine button, difficulty info
|
||||
│ ├── mining_screen.dart
|
||||
│ └── mining_provider.dart
|
||||
└── routing/
|
||||
└── app_router.dart # GoRouter navigation
|
||||
```
|
||||
|
||||
## Tech Stack
|
||||
|
||||
| Component | Choice | Rationale |
|
||||
|-----------|--------|-----------|
|
||||
| State management | Riverpod | Less boilerplate than BLoC, familiar |
|
||||
| HTTP client | Dio | Interceptors, retry support |
|
||||
| Navigation | GoRouter | Declarative routing |
|
||||
| Models | Freezed + json_serializable | Immutable data classes |
|
||||
| Secure storage | flutter_secure_storage | Wallet private keys |
|
||||
| Charts | fl_chart | Chain visualization |
|
||||
| Real-time | Polling (5-10s) | Simple, no WebSocket needed |
|
||||
|
||||
## Features
|
||||
|
||||
- **Dashboard**: chain height, difficulty, block reward, recent blocks
|
||||
- **Block Explorer**: browse blocks, view transactions per block
|
||||
- **Wallet**: generate keypairs, check balances, manage multiple wallets
|
||||
- **Transactions**: send signed transactions, view pending pool
|
||||
- **Mining**: one-tap mining, difficulty display, reward info
|
||||
|
||||
## Screens
|
||||
|
||||
1. **Dashboard** - overview with chain stats and recent activity
|
||||
2. **Blocks** - paginated block list with expandable details
|
||||
3. **Block Detail** - full block info with transaction list
|
||||
4. **Wallets** - wallet list with balances, create new
|
||||
5. **Send Transaction** - form: from, to, amount, sign & submit
|
||||
6. **Pending Transactions** - current mempool
|
||||
7. **Mining** - mine button with result display
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
flutter pub get
|
||||
|
||||
# Generate code (Freezed models)
|
||||
dart run build_runner build --delete-conflicting-outputs
|
||||
|
||||
# Run (blockchain-node must be running on localhost:3000)
|
||||
flutter run
|
||||
|
||||
# Run on web
|
||||
flutter run -d chrome
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Node URL is configured in `lib/core/network/api_client.dart`. Defaults to `http://localhost:3000`.
|
||||
|
||||
For Android emulator, use `http://10.0.2.2:3000` to reach host localhost.
|
||||
|
||||
## Related Repos
|
||||
|
||||
- [blockchain-core](../blockchain-core) - Core library + REST API node
|
||||
- [blockchain-cli](../blockchain-cli) - Rust CLI tool
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
105
lib/core/network/api_client.dart
Normal file
105
lib/core/network/api_client.dart
Normal file
@ -0,0 +1,105 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class ApiClient {
|
||||
static const String _defaultBaseUrl = 'http://localhost:3000';
|
||||
// Use 10.0.2.2 for Android emulator to reach host localhost
|
||||
static const String _androidEmulatorUrl = 'http://10.0.2.2:3000';
|
||||
|
||||
late final Dio dio;
|
||||
|
||||
ApiClient({String? baseUrl}) {
|
||||
final url = baseUrl ?? _resolveBaseUrl();
|
||||
|
||||
dio = Dio(BaseOptions(
|
||||
baseUrl: url,
|
||||
connectTimeout: const Duration(seconds: 5),
|
||||
receiveTimeout: const Duration(seconds: 10),
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
));
|
||||
|
||||
dio.interceptors.add(LogInterceptor(
|
||||
requestBody: true,
|
||||
responseBody: true,
|
||||
logPrint: (log) => debugPrint(log.toString()),
|
||||
));
|
||||
}
|
||||
|
||||
static String _resolveBaseUrl() {
|
||||
// Android emulator uses 10.0.2.2 to reach host machine
|
||||
if (defaultTargetPlatform == TargetPlatform.android) {
|
||||
return _androidEmulatorUrl;
|
||||
}
|
||||
return _defaultBaseUrl;
|
||||
}
|
||||
|
||||
// Chain
|
||||
Future<Map<String, dynamic>> getChainInfo() async {
|
||||
final response = await dio.get('/api/chain/info');
|
||||
return response.data;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> validateChain() async {
|
||||
final response = await dio.post('/api/chain/validate');
|
||||
return response.data;
|
||||
}
|
||||
|
||||
// Blocks
|
||||
Future<Map<String, dynamic>> getBlocks({int offset = 0, int limit = 10}) async {
|
||||
final response = await dio.get('/api/blocks', queryParameters: {
|
||||
'offset': offset,
|
||||
'limit': limit,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getBlock(String hash) async {
|
||||
final response = await dio.get('/api/blocks/$hash');
|
||||
return response.data;
|
||||
}
|
||||
|
||||
// Wallets
|
||||
Future<Map<String, dynamic>> createWallet() async {
|
||||
final response = await dio.post('/api/wallets');
|
||||
return response.data;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getBalance(String address) async {
|
||||
final response = await dio.get('/api/wallets/$address/balance');
|
||||
return response.data;
|
||||
}
|
||||
|
||||
// Transactions
|
||||
Future<Map<String, dynamic>> submitTransaction(Map<String, dynamic> tx) async {
|
||||
final response = await dio.post('/api/transactions', data: tx);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
Future<List<dynamic>> getPendingTransactions() async {
|
||||
final response = await dio.get('/api/transactions/pending');
|
||||
return response.data;
|
||||
}
|
||||
|
||||
// Mining
|
||||
Future<Map<String, dynamic>> mine(String minerAddress) async {
|
||||
final response = await dio.post('/api/mine', data: {
|
||||
'miner_address': minerAddress,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getMiningStatus() async {
|
||||
final response = await dio.get('/api/mining/status');
|
||||
return response.data;
|
||||
}
|
||||
|
||||
// Health
|
||||
Future<bool> healthCheck() async {
|
||||
try {
|
||||
await dio.get('/api/health');
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
49
lib/core/theme/app_theme.dart
Normal file
49
lib/core/theme/app_theme.dart
Normal file
@ -0,0 +1,49 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
class AppTheme {
|
||||
static const _primaryColor = Color(0xFF6C63FF);
|
||||
static const _accentColor = Color(0xFF00D9FF);
|
||||
static const _backgroundColor = Color(0xFF0D1117);
|
||||
static const _surfaceColor = Color(0xFF161B22);
|
||||
static const _cardColor = Color(0xFF21262D);
|
||||
|
||||
static ThemeData get darkTheme {
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark,
|
||||
colorScheme: const ColorScheme.dark(
|
||||
primary: _primaryColor,
|
||||
secondary: _accentColor,
|
||||
surface: _surfaceColor,
|
||||
),
|
||||
scaffoldBackgroundColor: _backgroundColor,
|
||||
cardColor: _cardColor,
|
||||
textTheme: GoogleFonts.jetBrainsMonoTextTheme(
|
||||
ThemeData.dark().textTheme,
|
||||
),
|
||||
appBarTheme: const AppBarTheme(
|
||||
backgroundColor: _surfaceColor,
|
||||
elevation: 0,
|
||||
),
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _primaryColor,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
),
|
||||
cardTheme: CardTheme(
|
||||
color: _cardColor,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
side: BorderSide(color: Colors.white.withAlpha(25)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
31
lib/core/utils/formatters.dart
Normal file
31
lib/core/utils/formatters.dart
Normal file
@ -0,0 +1,31 @@
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class Formatters {
|
||||
/// Format a hash for display (first 8 + ... + last 8).
|
||||
static String truncateHash(String hash, {int chars = 8}) {
|
||||
if (hash.length <= chars * 2) return hash;
|
||||
return '${hash.substring(0, chars)}...${hash.substring(hash.length - chars)}';
|
||||
}
|
||||
|
||||
/// Format amount from smallest unit to display unit.
|
||||
/// 50_000_000 -> "50.000000"
|
||||
static String formatAmount(int amount, {int decimals = 6}) {
|
||||
final value = amount / 1000000;
|
||||
return value.toStringAsFixed(decimals);
|
||||
}
|
||||
|
||||
/// Format a timestamp string to readable date.
|
||||
static String formatTimestamp(String timestamp) {
|
||||
try {
|
||||
final dt = DateTime.parse(timestamp);
|
||||
return DateFormat('yyyy-MM-dd HH:mm:ss').format(dt.toLocal());
|
||||
} catch (_) {
|
||||
return timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
/// Format large numbers with comma separators.
|
||||
static String formatNumber(int number) {
|
||||
return NumberFormat('#,###').format(number);
|
||||
}
|
||||
}
|
||||
21
lib/features/blocks/block_detail_screen.dart
Normal file
21
lib/features/blocks/block_detail_screen.dart
Normal file
@ -0,0 +1,21 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class BlockDetailScreen extends ConsumerWidget {
|
||||
final String hash;
|
||||
|
||||
const BlockDetailScreen({super.key, required this.hash});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Block Detail')),
|
||||
body: Center(
|
||||
child: Text(
|
||||
'Block Detail\n\nHash: $hash',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
19
lib/features/blocks/blocks_screen.dart
Normal file
19
lib/features/blocks/blocks_screen.dart
Normal file
@ -0,0 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class BlocksScreen extends ConsumerWidget {
|
||||
const BlocksScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Block Explorer')),
|
||||
body: const Center(
|
||||
child: Text(
|
||||
'Block Explorer\n\nBrowse blocks with pagination.',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
19
lib/features/dashboard/dashboard_screen.dart
Normal file
19
lib/features/dashboard/dashboard_screen.dart
Normal file
@ -0,0 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class DashboardScreen extends ConsumerWidget {
|
||||
const DashboardScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Dashboard')),
|
||||
body: const Center(
|
||||
child: Text(
|
||||
'Blockchain Dashboard\n\nChain stats, recent blocks, and network overview.',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
19
lib/features/mining/mining_screen.dart
Normal file
19
lib/features/mining/mining_screen.dart
Normal file
@ -0,0 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class MiningScreen extends ConsumerWidget {
|
||||
const MiningScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Mining')),
|
||||
body: const Center(
|
||||
child: Text(
|
||||
'Mining\n\nMine blocks, view difficulty and rewards.',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
19
lib/features/transactions/send_tx_screen.dart
Normal file
19
lib/features/transactions/send_tx_screen.dart
Normal file
@ -0,0 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class SendTxScreen extends ConsumerWidget {
|
||||
const SendTxScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Send Transaction')),
|
||||
body: const Center(
|
||||
child: Text(
|
||||
'Send Transaction\n\nForm: from, to, amount.',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
19
lib/features/transactions/transactions_screen.dart
Normal file
19
lib/features/transactions/transactions_screen.dart
Normal file
@ -0,0 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class TransactionsScreen extends ConsumerWidget {
|
||||
const TransactionsScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Transactions')),
|
||||
body: const Center(
|
||||
child: Text(
|
||||
'Pending Transactions\n\nView mempool.',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
19
lib/features/wallet/wallet_screen.dart
Normal file
19
lib/features/wallet/wallet_screen.dart
Normal file
@ -0,0 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class WalletScreen extends ConsumerWidget {
|
||||
const WalletScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Wallets')),
|
||||
body: const Center(
|
||||
child: Text(
|
||||
'Wallet Management\n\nCreate wallets, view balances.',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
25
lib/main.dart
Normal file
25
lib/main.dart
Normal file
@ -0,0 +1,25 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'core/theme/app_theme.dart';
|
||||
import 'routing/app_router.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const ProviderScope(child: BlockchainApp()));
|
||||
}
|
||||
|
||||
class BlockchainApp extends ConsumerWidget {
|
||||
const BlockchainApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final router = ref.watch(appRouterProvider);
|
||||
|
||||
return MaterialApp.router(
|
||||
title: 'Blockchain Explorer',
|
||||
theme: AppTheme.darkTheme,
|
||||
routerConfig: router,
|
||||
debugShowCheckedModeBanner: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
95
lib/routing/app_router.dart
Normal file
95
lib/routing/app_router.dart
Normal file
@ -0,0 +1,95 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../features/dashboard/dashboard_screen.dart';
|
||||
import '../features/blocks/blocks_screen.dart';
|
||||
import '../features/blocks/block_detail_screen.dart';
|
||||
import '../features/wallet/wallet_screen.dart';
|
||||
import '../features/transactions/transactions_screen.dart';
|
||||
import '../features/transactions/send_tx_screen.dart';
|
||||
import '../features/mining/mining_screen.dart';
|
||||
|
||||
final appRouterProvider = Provider<GoRouter>((ref) {
|
||||
return GoRouter(
|
||||
initialLocation: '/',
|
||||
routes: [
|
||||
ShellRoute(
|
||||
builder: (context, state, child) => ScaffoldWithNav(child: child),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (context, state) => const DashboardScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/blocks',
|
||||
builder: (context, state) => const BlocksScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/blocks/:hash',
|
||||
builder: (context, state) => BlockDetailScreen(
|
||||
hash: state.pathParameters['hash']!,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/wallets',
|
||||
builder: (context, state) => const WalletScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/transactions',
|
||||
builder: (context, state) => const TransactionsScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/transactions/send',
|
||||
builder: (context, state) => const SendTxScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/mining',
|
||||
builder: (context, state) => const MiningScreen(),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
class ScaffoldWithNav extends StatelessWidget {
|
||||
final Widget child;
|
||||
const ScaffoldWithNav({super.key, required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: child,
|
||||
bottomNavigationBar: NavigationBar(
|
||||
selectedIndex: _calculateIndex(GoRouterState.of(context).uri.path),
|
||||
onDestinationSelected: (index) => _onTap(context, index),
|
||||
destinations: const [
|
||||
NavigationDestination(icon: Icon(Icons.dashboard), label: 'Dashboard'),
|
||||
NavigationDestination(icon: Icon(Icons.view_in_ar), label: 'Blocks'),
|
||||
NavigationDestination(icon: Icon(Icons.account_balance_wallet), label: 'Wallets'),
|
||||
NavigationDestination(icon: Icon(Icons.swap_horiz), label: 'Transactions'),
|
||||
NavigationDestination(icon: Icon(Icons.hardware), label: 'Mining'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
int _calculateIndex(String path) {
|
||||
if (path.startsWith('/blocks')) return 1;
|
||||
if (path.startsWith('/wallets')) return 2;
|
||||
if (path.startsWith('/transactions')) return 3;
|
||||
if (path.startsWith('/mining')) return 4;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void _onTap(BuildContext context, int index) {
|
||||
switch (index) {
|
||||
case 0: context.go('/');
|
||||
case 1: context.go('/blocks');
|
||||
case 2: context.go('/wallets');
|
||||
case 3: context.go('/transactions');
|
||||
case 4: context.go('/mining');
|
||||
}
|
||||
}
|
||||
}
|
||||
34
pubspec.yaml
Normal file
34
pubspec.yaml
Normal file
@ -0,0 +1,34 @@
|
||||
name: blockchain_flutter
|
||||
description: Flutter app for blockchain explorer, wallet management, and mining.
|
||||
publish_to: 'none'
|
||||
version: 0.1.0
|
||||
|
||||
environment:
|
||||
sdk: ^3.6.0
|
||||
flutter: ">=3.27.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_riverpod: ^2.6.1
|
||||
riverpod_annotation: ^2.6.1
|
||||
dio: ^5.7.0
|
||||
go_router: ^14.8.1
|
||||
freezed_annotation: ^2.4.4
|
||||
json_annotation: ^4.9.0
|
||||
flutter_secure_storage: ^9.2.4
|
||||
fl_chart: ^0.70.2
|
||||
intl: ^0.19.0
|
||||
google_fonts: ^6.2.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^5.0.0
|
||||
build_runner: ^2.4.14
|
||||
freezed: ^2.5.7
|
||||
json_serializable: ^6.9.4
|
||||
riverpod_generator: ^2.6.3
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
Loading…
Reference in New Issue
Block a user