Fluxy
E-Commerce Example

User Profile Modules

Account management, addresses, and user settings.

User Profile Modules

The Profile section is a clean, multi-page module that isolates individual account settings files for exceptional organization and reusability.

Main Profile Component (lib/features/profile/profile.view.dart)

import 'package:flutter/material.dart';
import 'package:fluxy/fluxy.dart';
import '../../features/home/home.controller.dart';

class ProfileView extends StatelessWidget {
  final HomeController controller;
  const ProfileView({super.key, required this.controller});

  @override
  Widget build(BuildContext context) {
    return Fx.scaffold(
      backgroundColor: Colors.transparent,
      appBar: Fx.appBar(
        title: 'Profile',
        backgroundColor: Colors.transparent,
        foregroundColor: controller.text,
        elevation: 0,
        centerTitle: false,
      ),
      body: Builder(
        builder: (context) {
          return Fx.col(
            children: [
              // Profile Header
              Fx(
                () => Fx.col(
                  alignItems: CrossAxisAlignment.center,
                  children: [
                    Fx.stack(
                      children: [
                        Fx.image(
                          controller.userAvatar.value,
                          width: 100,
                          height: 100,
                          fit: BoxFit.cover,
                          radius: 50,
                        ),
                        Positioned(
                          bottom: 0,
                          right: 0,
                          child: Fx.box()
                              .w(32)
                              .h(32)
                              .bg
                              .black
                              .circle()
                              .border(color: Colors.white, width: 3)
                              .center()
                              .pointer()
                              .child(
                                const Icon(
                                  Icons.edit,
                                  color: Colors.white,
                                  size: 16,
                                ),
                              ),
                        ),
                      ],
                    ).tw('mb-4'),
                    Fx.text(
                      controller.userName.value,
                    ).font.xl2().bold().color(controller.text),
                    Fx.text(controller.userEmail.value).font.md().muted(),
                  ],
                ),
              ).tw('w-full pt-8 pb-6').my(24),

              // Quick Stats
              Fx(
                () => Fx.row(
                  children: [
                    _buildStatCard(
                      'Orders',
                      '${controller.ordersCount.value}',
                    ).expanded(),
                    Fx.gap(12),
                    _buildStatCard(
                      'Wishlist',
                      '${controller.wishlist.value.length}',
                    ).expanded(),
                    Fx.gap(12),
                    _buildStatCard(
                      'Points',
                      '${controller.points.value}',
                    ).expanded(),
                  ],
                ).px(24).pb(32),
              ),

              // Settings Groups
              Fx.col(
                alignItems: CrossAxisAlignment.start,
                children: [
                  Fx.text(
                    'Account Settings'.toUpperCase(),
                  ).font.xs().bold().color(Colors.grey.shade500).px(24).pb(8),
                  Fx.col(
                        children: [
                          _buildGroupTile(
                            Icons.person_outline,
                            'Personal Information',
                            onTap: () => Fluxy.to(
                              '/personal-info',
                              arguments: {'controller': controller},
                            ),
                          ),
                          _buildDivider(),
                          _buildGroupTile(
                            Icons.location_on_outlined,
                            'Shipping Addresses',
                            onTap: () => Fluxy.to('/shipping', arguments: {'controller': controller}),
                          ),
                          _buildDivider(),
                          _buildGroupTile(
                            Icons.payment_outlined,
                            'Payment Methods',
                            onTap: () => Fluxy.to('/payment', arguments: {'controller': controller}),
                          ),
                        ],
                      )
                      .bg(controller.surface)
                      .rounded(20)
                      .shadowSmall()
                      .mx(24)
                      .mb(24),

                  Fx.text(
                    'Preferences'.toUpperCase(),
                  ).font.xs().bold().color(Colors.grey.shade500).px(24).pb(8),
                  Fx(
                        () => Fx.col(
                          children: [
                            _buildGroupTile(
                              Icons.notifications_outlined,
                              'Notifications',
                              switchValue:
                                  controller.notificationsEnabled.value,
                              onSwitchChanged: (v) =>
                                  controller.notificationsEnabled.value = v,
                            ),
                            _buildDivider(),
                            _buildGroupTile(
                              Icons.language_outlined,
                              'Language',
                              onTap: () {},
                              trailingText: controller.language.value,
                            ),
                            _buildDivider(),
                            _buildGroupTile(
                              Icons.dark_mode_outlined,
                              'Dark Mode',
                              switchValue: controller.isDarkMode.value,
                              onSwitchChanged: (v) {
                                controller.toggleTheme(v);
                              },
                            ),
                          ],
                        ),
                      )
                      .bg(controller.surface)
                      .rounded(20)
                      .shadowSmall()
                      .mx(24)
                      .mb(32),

                  // Logout Button (Mixed Tailwind & DSL)
                  Fx.box()
                      .tw('w-full py-4 rounded-xl shadow-md')
                      .bg(controller.surface)
                      .shadowSmall()
                      .pointer()
                      .center()
                      .child(
                        Fx.text(
                          'Log Out',
                        ).tw('text-lg font-bold').color(Colors.redAccent),
                      )
                      .onTap(() {})
                      .tw('w-full mx-6 mb-10')
                      .mx(24)
                      .mb(32),
                ],
              ),
            ],
          ).scrollable();
        },
      ),
    );
  }

  Widget _buildStatCard(String title, String value) {
    return Fx.col(
      alignItems: CrossAxisAlignment.center,
      children: [
        Fx.text(value).tw('text-2xl font-bold').color(controller.text),
        Fx.gap(4),
        Fx.text(title).tw('text-sm').color(controller.textMuted),
      ],
    ).tw('p-4 rounded-xl shadow-md').bg(controller.surface);
  }

  Widget _buildGroupTile(
    IconData icon,
    String title, {
    VoidCallback? onTap,
    String? trailingText,
    bool? switchValue,
    ValueChanged<bool>? onSwitchChanged,
  }) {
    return Fx.row(
      children: [
        Fx.box()
            .tw('p-2.5 rounded-xl')
            .bg(
              controller.isDarkMode.value
                  ? Colors.grey.shade800
                  : Colors.grey.shade100,
            )
            .child(Icon(icon, color: controller.text, size: 20)),
        Fx.gap(16),
        Fx.text(
          title,
        ).tw('text-lg font-medium').color(controller.text).expanded(),
        if (trailingText != null)
          Fx.text(
            trailingText,
          ).tw('text-base pr-2').color(controller.textMuted),
        if (switchValue != null)
          Switch(
            value: switchValue,
            onChanged: onSwitchChanged,
            materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
          )
        else
          Icon(Icons.chevron_right, color: Colors.grey.shade400, size: 24),
      ],
    ).tw('px-4 py-3 pointer').onTap(() => onTap?.call());
  }

  Widget _buildDivider() {
    return Fx.box()
        .tw('w-full mx-4')
        .h(1)
        .bg(
          controller.isDarkMode.value
              ? Colors.grey.shade800
              : Colors.grey.shade100,
        );
  }
}

Personal Information Edit (lib/features/profile/personal_info.view.dart)

import 'package:flutter/material.dart';
import 'package:fluxy/fluxy.dart';
import '../../features/home/home.controller.dart';

class PersonalInfoView extends StatelessWidget {
  final HomeController controller;
  const PersonalInfoView({super.key, required this.controller});

  @override
  Widget build(BuildContext context) {
    return Fx(() {
      final isDark = controller.isDarkMode.value;
      
      return Fx.scaffold(
        backgroundColor: isDark ? const Color(0xFF121212) : Colors.grey.shade50,
        appBar: Fx.appBar(
          title: 'Personal Info',
          backgroundColor: Colors.transparent,
          foregroundColor: controller.text,
          elevation: 0,
          centerTitle: false,
        ),
      body: Fx.col(
        alignItems: CrossAxisAlignment.start,
        children: [
          // Profile Avatar
          SizedBox(
            width: 104,
            height: 104,
            child: Stack(
              clipBehavior: Clip.none,
              alignment: Alignment.center,
              children: [
                Fx.image(
                  controller.userAvatar.value,
                  width: 104,
                  height: 104,
                  fit: BoxFit.cover,
                  radius: 52,
                  error: Icon(Icons.person, size: 50, color: controller.textMuted),
                ),
                Positioned.fill(
                  child: Container(
                    decoration: BoxDecoration(
                      shape: BoxShape.circle,
                      border: Border.all(color: Colors.blueAccent.withOpacity(0.3), width: 4),
                    ),
                  ),
                ),
                Positioned(
                  bottom: -2,
                  right: -2,
                  child: Fx.box()
                    .p(8)
                    .bg(Colors.blueAccent)
                    .circle()
                    .border(color: isDark ? const Color(0xFF121212) : Colors.grey.shade50, width: 3)
                    .child(const Icon(Icons.camera_alt, size: 14, color: Colors.white))
                    .pointer()
                ),
              ]
            )
          ).center().pb(32),

          Fx.text('Name').font.md().bold().color(controller.text).pb(8),
          Fx.input(
            signal: controller.userName,
            placeholder: 'Enter your name',
            icon: Icons.person_outline,
          ).bg(isDark ? const Color(0xFF1E1E1E) : Colors.white)
           .border(color: isDark ? Colors.grey.shade800 : Colors.grey.shade200).shadowSmall().rounded(16).mb(24),
          
          Fx.text('Email').font.md().bold().color(controller.text).pb(8),
          Fx.input(
            signal: controller.userEmail,
            placeholder: 'Enter your email',
            icon: Icons.email_outlined,
          ).bg(isDark ? const Color(0xFF1E1E1E) : Colors.white)
           .border(color: isDark ? Colors.grey.shade800 : Colors.grey.shade200).shadowSmall().rounded(16).mb(32),

          Fx.box()
            .wFull().py(18)
            .bg(Colors.blueAccent)
            .rounded(16)
            .pointer()
            .shadowSmall()
            .center()
            .child(Fx.text('Save Changes').font.lg().bold().color(Colors.white))
            .onTap(() {
              Fluxy.back();
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('Changes saved successfully!'), backgroundColor: Colors.green)
              );
            }),
        ],
      ).p(24).scrollable(),
      );
    });
  }
}

Payment Methods Setup (lib/features/profile/payment_method.view.dart)

import 'package:flutter/material.dart';
import 'package:fluxy/fluxy.dart';

import '../../features/home/home.controller.dart';

class PaymentMethodView extends StatelessWidget {
  final HomeController controller;
  const PaymentMethodView({super.key, required this.controller});

  @override
  Widget build(BuildContext context) {
    return Fx(() => Fx.scaffold(
      backgroundColor: controller.surface,
      appBar: Fx.appBar(
        title: 'Payment Methods',
        backgroundColor: Colors.transparent,
        foregroundColor: controller.text,
        elevation: 0,
      ),
      body: Fx.col(
        alignItems: CrossAxisAlignment.start,
        children: [
          ...controller.paymentCards.value.map((payment) {
            final isSelected = controller.selectedPaymentId.value == payment.id;
            return _buildCardInfo(payment, isSelected).mb(20);
          }),
          Fx.gap(16),
          Fx.box()
            .wFull().py(18)
            .bg.transparent
            .border(color: controller.text, width: 2)
            .rounded(16)
            .pointer()
            .center()
            .child(Fx.text('+ Add Payment Method').font.lg().bold().color(controller.text))
            .onTap(() {
               ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Add Card UI placeholder.')));
            }),
        ],
      ).p(24).scrollable(),
    ));
  }

  Widget _buildCardInfo(PaymentCard card, bool isSelected) {
    return Fx.col(
      alignItems: CrossAxisAlignment.start,
      children: [
        Fx.row(
          justify: MainAxisAlignment.spaceBetween,
          children: [
            Fx.row(
              children: [
                Icon(card.iconType == 'visa' ? Icons.credit_card : Icons.credit_card_outlined, color: controller.text),
                Fx.gap(12),
                Fx.text(card.bank).font.lg().bold().color(controller.text),
              ],
            ).expanded(),
            Fx.box().bg(isSelected ? Colors.blueAccent : Colors.transparent).rounded(8).px(12).py(6).border(color: isSelected ? Colors.transparent : controller.textMuted).pointer().onTap((){
               controller.selectedPaymentId.value = card.id;
               Fluxy.back();
            }).child(
              Fx.text(isSelected ? 'Selected' : 'Select').font.xs().bold().color(isSelected ? Colors.white : controller.textMuted)
            ),
          ],
        ).pb(12),
        Fx.text('**** **** **** ${card.last4}').font.xl().medium().color(controller.text).pb(4),
        Fx.text('Expires 12/26').font.sm().color(controller.textMuted),
      ],
    ).p(20).bg(isSelected ? Colors.blueAccent.withOpacity(0.05) : controller.surface).rounded(16).border(color: isSelected ? Colors.blueAccent : (controller.isDarkMode.value ? Colors.white12 : Colors.grey.shade200), width: isSelected ? 2 : 1).wFull().pointer().onTap((){
       controller.selectedPaymentId.value = card.id;
    });
  }
}

Shipping Address Configuration (lib/features/profile/shipping_address.view.dart)

import 'package:flutter/material.dart';
import 'package:fluxy/fluxy.dart';

import '../../features/home/home.controller.dart';

class ShippingAddressView extends StatelessWidget {
  final HomeController controller;
  const ShippingAddressView({super.key, required this.controller});

  @override
  Widget build(BuildContext context) {
    return Fx(
      () => Fx.scaffold(
        backgroundColor: controller.surface,
      appBar: Fx.appBar(
        title: 'Shipping Addresses',
        backgroundColor: Colors.transparent,
          foregroundColor: controller.text,
        elevation: 0,
      ),
      body: Fx.col(
        alignItems: CrossAxisAlignment.start,
        children: [
            ...controller.addresses.value.map((address) {
              final isSelected =
                  controller.selectedAddressId.value == address.id;
              return _buildAddressCard(address, isSelected).mb(20);
            }),
            Fx.gap(20),

          Fx.box()
            .wFull().py(18)
            .bg.transparent
                .border(color: controller.text, width: 2)
            .rounded(16)
            .pointer()
            .center()
                .child(
                  Fx.text(
                    '+ Add New Address',
                  ).font.lg().bold().color(controller.text),
                )
                .onTap(() {
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(
                      content: Text('Add Address UI placeholder.'),
                    ),
                  );
                }),
        ],
      ).p(24).scrollable(),
      ),
    );
  }

  Widget _buildAddressCard(Address address, bool isSelected) {
    return Fx.col(
      alignItems: CrossAxisAlignment.start,
      children: [
        Fx.row(
          justify: MainAxisAlignment.spaceBetween,
          children: [
                Fx.text(
                  address.label,
                ).font.lg().bold().color(controller.text).expanded(),
                if (address.isDefault)
              Fx.box().bg(Colors.green.shade100).rounded(8).px(8).py(4).child(
                Fx.text('Default').font.xs().bold().color(Colors.green.shade800)
              ),
          ],
        ).pb(8),
            Fx.text(
              address.fullAddress,
            ).font.md().color(controller.textMuted).pb(16),
        Fx.row(
          children: [
                Fx.text('Select').font
                    .sm()
                    .bold()
                    .color(isSelected ? Colors.green : Colors.blueAccent)
                    .pointer()
                    .onTap(() {
                      controller.selectedAddressId.value = address.id;
                      Fluxy.back();
                    }),
                Fx.gap(16),
                Fx.text('Edit').font
                    .sm()
                    .bold()
                    .color(controller.textMuted)
                    .pointer()
                    .onTap(() {}),
            Fx.gap(16),
            Fx.text('Delete').font.sm().bold().color(Colors.redAccent).pointer().onTap((){}),
          ],
        )
      ],
        )
        .p(16)
        .bg(
          isSelected ? Colors.blueAccent.withOpacity(0.05) : controller.surface,
        )
        .rounded(16)
        .border(
          color: isSelected
              ? Colors.blueAccent
              : (controller.isDarkMode.value
                    ? Colors.white12
                    : Colors.grey.shade200),
          width: isSelected ? 2 : 1,
        )
        .wFull()
        .pointer()
        .onTap(() {
          controller.selectedAddressId.value = address.id;
        });
  }
}

📱 Visual Examples

Profile MainProfile DarkPersonal InfoPersonal Info DarkShipping AddressesShipping Addresses DarkPayment MethodsPayment Methods Dark

On this page