fluxy 0.1.2
fluxy: ^0.1.2 copied to clipboard
A complete Flutter platform unifying reactive state, declarative UI, styling, animation, routing, and tooling. Build production-grade apps faster and at scale.
example/lib/main.dart
// ignore_for_file: prefer_const_constructors, prefer_const_literals_to_create_immutables
import 'package:flutter/material.dart';
import 'package:fluxy/fluxy.dart';
// --- Global Reactive State (Fintech Design Hub) ---
final balance = flux(42840.50);
final income = flux(15400.00);
final expense = flux(6200.00);
final transactions = flux(<Map<String, dynamic>>[
{
'title': 'Amazon India',
'subtitle': 'Electronics & Gadgets',
'amount': -12499.00,
'icon': Icons.shopping_bag_rounded,
'color': const Color(0xFFFF9900)
},
{
'title': 'HDFC Salary Deposit',
'subtitle': 'Monthly Payout',
'amount': 85000.00,
'icon': Icons.account_balance_wallet_rounded,
'color': const Color(0xFF10B981)
},
{
'title': 'Zomato UX',
'subtitle': 'Dining & Delivery',
'amount': -840.50,
'icon': Icons.fastfood_rounded,
'color': const Color(0xFFEF4444)
},
{
'title': 'Netflix Premium',
'subtitle': 'Entertainment',
'amount': -649.00,
'icon': Icons.play_circle_fill,
'color': const Color(0xFFE50914)
},
]);
final filter = flux("All");
void main() => runApp(const MaterialApp(
debugShowCheckedModeBanner: false,
home: FluxyFintechApp(),
));
class FluxyFintechApp extends StatefulWidget {
const FluxyFintechApp({super.key});
@override
State<FluxyFintechApp> createState() => _FluxyFintechAppState();
}
class _FluxyFintechAppState extends State<FluxyFintechApp> {
int _activePage = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF8FAFC),
body: _buildPage(),
bottomNavigationBar: _buildPremiumNav(),
);
}
Widget _buildPage() {
switch (_activePage) {
case 0:
return const WalletDashboard();
case 1:
return const PortfolioStats();
default:
return const WalletDashboard();
}
}
Widget _buildPremiumNav() {
return FxBottomBar(
currentIndex: _activePage,
onTap: (i) => setState(() => _activePage = i),
activeColor: const Color(0xFF6366F1),
baseColor: const Color(0xFF94A3B8),
items: const [
FxBottomBarItem(icon: Icons.grid_view_rounded, label: "Home"),
FxBottomBarItem(icon: Icons.auto_graph_rounded, label: "Stats"),
FxBottomBarItem(icon: Icons.account_balance_wallet_rounded, label: "Wallet"),
FxBottomBarItem(icon: Icons.person_rounded, label: "Profile"),
],
);
}
}
// --- SCREEN 1: WALLET DASHBOARD ---
class WalletDashboard extends StatelessWidget {
const WalletDashboard({super.key});
@override
Widget build(BuildContext context) {
return SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 20),
child: Column(
children: [
// Branding & Profile Header
Fx.row(
children: [
Fx.column(
gap: 4,
children: [
Fx.text("Welcome back,").fontSize(14).color(const Color(0xFF64748B)).weight(FontWeight.w500),
Fx.text("Alex Riviera").fontSize(26).bold().color(const Color(0xFF0F172A)),
],
).expand(),
Fx.avatar(
fallback: "AR",
size: FxAvatarSize.lg,
shape: FxAvatarShape.rounded,
onTap: () => Fx.toast(context, "Profile tapped"),
).shadow(
color: const Color(0xFF6366F1).withValues(alpha: 0.3),
blur: 20
),
],
),
const SizedBox(height: 36),
// Main Balance Gradient Card
Fx.box(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Fx.row(
children: [
const Text("AVAILABLE BALANCE", style: TextStyle(fontSize: 11, fontWeight: FontWeight.bold, letterSpacing: 1.5, color: Color(0xFF94A3B8))),
const Spacer(),
Fx.box(
child: const Icon(Icons.nfc_rounded, color: Colors.white, size: 20)
).size(36, 36).background(Colors.white.withValues(alpha: 0.1)).borderRadius(10).center(),
],
),
const SizedBox(height: 12),
Fx.text(() => "₹ ${balance.value.toStringAsFixed(2)}")
.fontSize(38) // Reduced from 44
.bold()
.color(Colors.white),
const SizedBox(height: 24),
Fx.row(
gap: 20, // Reduced from 24
children: [
_miniStat("Income", "₹ ${income.value.toInt()}", const Color(0xFF10B981)),
_miniStat("Expense", "₹ ${expense.value.toInt()}", const Color(0xFFEF4444)),
],
),
],
),
)
.background(const Color(0xFF1E293B))
.borderRadius(36)
.padding(24) // Reduced from 32
.shadow(color: const Color(0xFF000000).withValues(alpha: 0.15), blur: 40),
const SizedBox(height: 36),
// Quick Actions Grid-like row
Fx.row(
style: const FxStyle(justifyContent: MainAxisAlignment.spaceBetween),
children: [
_actionCircle(Icons.send_rounded, "Send", const Color(0xFF6366F1)),
_actionCircle(Icons.qr_code_scanner_rounded, "Scan", const Color(0xFFF59E0B)),
_actionCircle(Icons.account_balance_rounded, "Bank", const Color(0xFF10B981)),
_actionCircle(Icons.more_horiz_rounded, "More", const Color(0xFF64748B)),
],
),
const SizedBox(height: 24),
// Example Button as requested
Fx.button("Add New Card", onTap: () {})
.fullWidth()
.sizeLarge()
.shadowMedium(),
const SizedBox(height: 48),
// Transactions Header with Dropdown
Fx.row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Fx.text("Recent Transactions").fontSize(20).bold().color(const Color(0xFF0F172A)),
// Reactive Dropdown: Automatically updates 'filter' signal
Fx.dropdown<String>(
signal: filter, // Pass the signal directly
items: ["All", "Credit", "Debit"],
itemLabel: (s) => s,
).w(100),
],
),
const SizedBox(height: 24),
Fx.column(
gap: 16,
children: transactions.value.map((tx) =>
_transactionTile(tx['title'], tx['subtitle'], tx['amount'], tx['icon'], tx['color'])
).toList(),
),
const SizedBox(height: 48),
Fx.text("Quick Actions").fontSize(18).bold().color(Colors.grey[800]!),
const SizedBox(height: 16),
// Redesigned Action Row
Fx.row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_actionButton(
Icons.notifications_active_outlined,
"Toast",
() => Fx.toast(context, "Fluxy toast message!"),
badgeCount: 3
),
_actionButton(Icons.window_outlined, "Modal", () => Fx.modal(context,
child: Fx.box(
child: Fx.column(children: [
Fx.text("Fluxy Modal").bold().fontSize(18),
const SizedBox(height: 12),
Fx.text("Declarative & Simple.").textCenter().color(Colors.grey),
const SizedBox(height: 24),
Fx.button("Close", onTap: () => Navigator.pop(context)).fullWidth(),
]).pack()
).p(24).backgroundWhite().rounded(24).w(300)
)),
_actionButton(Icons.vertical_align_bottom_outlined, "Sheet", () => Fx.bottomSheet(context,
child: Fx.box(
child: Fx.column(children: [
Fx.box().w(40).h(4).bg(Colors.grey[300]!).roundedFull(),
const SizedBox(height: 24),
Fx.text("Bottom Sheet").bold().fontSize(20),
const SizedBox(height: 12),
Fx.text("Swipe down to close.").color(Colors.grey),
const SizedBox(height: 40),
Fx.button("Got it", onTap: () => Navigator.pop(context)).fullWidth(),
]).pack()
).p(24).h(300).wFull()
)),
],
).p(16).bg(Colors.white).rounded(20).shadowMedium(),
const SizedBox(height: 32),
Fx.button("View Full Layout Demo", onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const LayoutShowcase())))
.secondary().fullWidth(),
const SizedBox(height: 100), // Bottom padding
],
),
),
);
}
Widget _actionButton(IconData icon, String label, VoidCallback onTap, {int? badgeCount}) {
Widget iconWidget = Fx.icon(icon, size: 28, color: const Color(0xFF2563EB));
if (badgeCount != null && badgeCount > 0) {
iconWidget = Fx.badge(
child: iconWidget,
label: badgeCount.toString(),
color: Colors.red,
offset: const Offset(6, -6),
);
}
return Fx.box(
onTap: onTap,
child: Fx.column(children: [
iconWidget.p(12).bg(const Color(0xFFEFF6FF)).roundedFull(),
const SizedBox(height: 8),
Fx.text(label).fontSize(12).semiBold().color(Colors.grey[700]!)
]),
).onPressed((s) => s.op(0.7));
}
Widget _miniStat(String label, String value, Color col) {
return Fx.column(
gap: 6,
children: [
Fx.text(label).fontSize(11).color(const Color(0xFF94A3B8)).weight(FontWeight.w600),
Fx.text(value).fontSize(17).bold().color(col),
],
);
}
Widget _actionCircle(IconData icon, String label, Color color) {
return Fx.column(
gap: 10,
children: [
Fx.box(
child: Icon(icon, color: color, size: 28)
).size(68, 68).background(Colors.white).borderRadius(22).shadow(color: Colors.black.withValues(alpha: 0.03), blur: 15).center().pointer(),
Fx.text(label).fontSize(13).bold().color(const Color(0xFF64748B)),
],
).expand();
}
Widget _transactionTile(String title, String subtitle, double amount, IconData icon, Color col) {
final isNegative = amount < 0;
return Fx.box(
child: Fx.row(
children: [
Fx.box(
child: Icon(icon, color: col, size: 26)
).size(54, 54).background(col.withValues(alpha: 0.12)).borderRadius(18).center(),
const SizedBox(width: 18),
Fx.column(
gap: 4,
children: [
Fx.text(title).fontSize(16).bold().color(const Color(0xFF0F172A)),
Fx.text(subtitle).fontSize(12).color(const Color(0xFF94A3B8)).weight(FontWeight.w500),
],
).expand(),
Fx.text("${isNegative ? '' : '+'}${isNegative ? '-' : ''} ₹ ${amount.abs().toStringAsFixed(2)}")
.fontSize(16)
.bold()
.color(isNegative ? const Color(0xFF0F172A) : const Color(0xFF10B981)),
],
),
)
.background(Colors.white)
.borderRadius(28)
.padding(18)
.shadow(color: Colors.black.withValues(alpha: 0.02), blur: 10).mb(12);
}
}
class LayoutShowcase extends StatefulWidget {
const LayoutShowcase({super.key});
@override
State<LayoutShowcase> createState() => _LayoutShowcaseState();
}
class _LayoutShowcaseState extends State<LayoutShowcase> {
int _index = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: Fx.appBar(
title: "Fluxy Layouts",
centerTitle: true,
actions: [
IconButton(icon: const Icon(Icons.search), onPressed: () {}),
IconButton(icon: const Icon(Icons.more_vert), onPressed: () {}),
],
),
drawer: Fx.drawer(
width: 280,
child: Fx.column(children: [
Fx.box().h(200).bg(const Color(0xFF1E293B)).wFull().child(
Fx.column(mainAxisAlignment: MainAxisAlignment.center, children: [
Fx.icon(Icons.person, size: 64, color: Colors.white).p(16).bg(Colors.white24).roundedFull(),
const SizedBox(height: 16),
Fx.text("Fluxy User").color(Colors.white).bold().fontSize(18),
])
),
ListTile(leading: const Icon(Icons.home), title: const Text("Home"), onTap: () {}),
ListTile(leading: const Icon(Icons.settings), title: const Text("Settings"), onTap: () {}),
const Spacer(),
ListTile(leading: const Icon(Icons.logout), title: const Text("Logout"), onTap: () => Navigator.pop(context)),
const SizedBox(height: 24),
])
),
body: splitLayout(),
bottomNavigationBar: Fx.bottomNav(
currentIndex: _index,
onTap: (i) => setState(() => _index = i),
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home_outlined), activeIcon: Icon(Icons.home), label: "Home"),
BottomNavigationBarItem(icon: Icon(Icons.explore_outlined), activeIcon: Icon(Icons.explore), label: "Explore"),
BottomNavigationBarItem(icon: Icon(Icons.person_outline), activeIcon: Icon(Icons.person), label: "Profile"),
],
),
);
}
Widget splitLayout() {
return Fx.row(children: [
Fx.column(mainAxisAlignment: MainAxisAlignment.center, children: [
Fx.text("Page $_index Content").bold().fontSize(24),
const SizedBox(height: 16),
Fx.text("Try opening the sidebar!").color(Colors.grey),
]).expand(),
]);
}
}
// --- SCREEN 2: PORTFOLIO STATS ---
class PortfolioStats extends StatelessWidget {
const PortfolioStats({super.key});
@override
Widget build(BuildContext context) {
return SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
children: [
Fx.text("Market Insights").fontSize(30).bold().color(const Color(0xFF0F172A)),
const SizedBox(height: 48),
// Dynamic Chart Card
Fx.box(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("PORTFOLIO PERFORMANCE", style: TextStyle(fontSize: 10, fontWeight: FontWeight.bold, letterSpacing: 1.5, color: Color(0xFF94A3B8))),
const SizedBox(height: 12),
Fx.row(
children: [
Fx.text("+ ₹ 12,400.00").fontSize(28).bold().color(const Color(0xFF10B981)),
const SizedBox(width: 12),
Fx.box(
child: Fx.text("↑ 14.2%").color(const Color(0xFF10B981)).fontSize(12).bold()
).background(const Color(0xFF10B981).withValues(alpha: 0.1)).borderRadius(8).paddingX(8).paddingY(4),
],
),
const Spacer(),
Fx.row(
gap: 8, // Reduced from 12
children: [
_chartBar(60), _chartBar(90), _chartBar(70), _chartBar(130),
_chartBar(110), _chartBar(80), _chartBar(120), _chartBar(105),
],
).center(),
],
),
)
.height(280)
.background(Colors.white)
.borderRadius(36)
.padding(28)
.shadow(color: Colors.black.withValues(alpha: 0.04), blur: 30),
const SizedBox(height: 40),
// Reactive Signal Adjustment Control
Fx.box(
child: Column(
children: [
Fx.text("Live Budget Control").fontSize(18).bold().color(const Color(0xFF1E293B)),
const SizedBox(height: 8),
Fx.text("Simulate instant financial adjustments").fontSize(13).color(const Color(0xFF64748B)),
const SizedBox(height: 32),
Fx.row(
gap: 24, // Reduced from 40
children: [
Fx.box(
onTap: () => balance.value -= 500,
child: const Icon(Icons.remove_rounded, color: Colors.white, size: 28),
).background(const Color(0xFF1E293B)).size(56, 56).borderRadius(18).shadow(color: Colors.black.withValues(alpha: 0.2), blur: 15).pointer(),
Fx.text(() => "₹ ${balance.value.toInt()}").fontSize(28).bold().color(const Color(0xFF0F172A)),
Fx.box(
onTap: () => balance.value += 500,
child: const Icon(Icons.add_rounded, color: Colors.white, size: 28),
).background(const Color(0xFF6366F1)).size(56, 56).borderRadius(18).shadow(color: const Color(0xFF6366F1).withValues(alpha: 0.4), blur: 20).pointer(),
],
).center(),
],
),
)
.background(const Color(0xFFF1F5F9))
.borderRadius(36)
.padding(32),
const SizedBox(height: 40),
_fancyCard("Mutual Fund SIP", Icons.trending_up_rounded, "Active • ₹ 5,000/mo", const Color(0xFF6366F1)),
const SizedBox(height: 18),
_fancyCard("Crypto Assets", Icons.currency_bitcoin_rounded, "Balanced Portfolio", const Color(0xFFF59E0B)),
],
),
),
);
}
Widget _chartBar(double height) {
return Fx.box(
child: Align(
alignment: Alignment.bottomCenter,
child: AnimBar(height: height),
)
)
.height(height)
.background(const Color(0xFF6366F1).withValues(alpha: 0.1))
.borderRadius(8)
.expand();
}
Widget _fancyCard(String title, IconData icon, String detail, Color accent) {
return Fx.box(
child: Fx.row(
children: [
Fx.box(
child: Icon(icon, color: accent, size: 28)
).size(50, 50).background(accent.withValues(alpha: 0.1)).borderRadius(16).center(),
const SizedBox(width: 20),
Fx.column(
gap: 4,
children: [
Fx.text(title).bold().fontSize(17).color(const Color(0xFF0F172A)),
Fx.text(detail).fontSize(13).color(const Color(0xFF64748B)).weight(FontWeight.w500),
],
).expand(),
const Icon(Icons.chevron_right_rounded, color: Color(0xFFCBD5E1), size: 30),
],
),
)
.background(Colors.white)
.borderRadius(28)
.padding(24)
.shadow(color: Colors.black.withValues(alpha: 0.03), blur: 20);
}
}
class AnimBar extends StatefulWidget {
final double height;
const AnimBar({super.key, required this.height});
@override
State<AnimBar> createState() => _AnimBarState();
}
class _AnimBarState extends State<AnimBar> {
@override
Widget build(BuildContext context) {
return AnimatedContainer(
duration: const Duration(seconds: 1),
curve: Curves.elasticOut,
height: widget.height * 0.7,
width: 30,
decoration: BoxDecoration(
color: const Color(0xFF6366F1),
borderRadius: BorderRadius.circular(10),
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
const Color(0xFF818CF8),
const Color(0xFF6366F1),
],
),
),
);
}
}