fluxy 0.1.4 copy "fluxy: ^0.1.4" to clipboard
fluxy: ^0.1.4 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(
  MaterialApp(
    navigatorKey: FluxyRouter.navigatorKey,
    debugShowCheckedModeBanner: false,
    home: const 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("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().sizeLg().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("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]!),
        ],
      ),
    ).pointer();
  }

  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)],
        ),
      ),
    );
  }
}
1
likes
0
points
867
downloads

Publisher

unverified uploader

Weekly Downloads

A complete Flutter platform unifying reactive state, declarative UI, styling, animation, routing, and tooling. Build production-grade apps faster and at scale.

Repository (GitHub)
View/report issues

Documentation

Documentation

License

unknown (license)

Dependencies

args, flutter, flutter_secure_storage, path, path_provider, shared_preferences

More

Packages that depend on fluxy