Widgets

Modals

Introduzione

Nylo Website fornisce un sistema di modal basato su bottom sheet modal.

La classe BottomSheetModal offre un'API flessibile per visualizzare overlay di contenuto con azioni, header, pulsanti di chiusura e stili personalizzati.

I modal sono utili per:

  • Dialoghi di conferma (es. logout, eliminazione)
  • Form di input rapidi
  • Fogli di azioni con opzioni multiple
  • Overlay informativi

Creare un Modal

Puoi creare un nuovo modal usando la CLI Metro:

metro make:bottom_sheet_modal payment_options

Questo genera due cose:

  1. Un widget per il contenuto del modal in lib/resources/widgets/bottom_sheet_modals/modals/payment_options_modal.dart:
import 'package:flutter/material.dart';

/// Payment Options Modal
///
/// Used in BottomSheetModal.showPaymentOptions()
class PaymentOptionsModal extends StatelessWidget {
  const PaymentOptionsModal({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('PaymentOptionsModal').headingLarge(),
      ],
    );
  }
}
  1. Un metodo statico aggiunto alla tua classe BottomSheetModal in lib/resources/widgets/bottom_sheet_modals/bottom_sheet_modals.dart:
/// Show Payment Options modal
static Future<void> showPaymentOptions(BuildContext context) {
  return displayModal(
    context,
    isScrollControlled: false,
    child: const PaymentOptionsModal(),
  );
}

Puoi quindi visualizzare il modal da qualsiasi punto:

BottomSheetModal.showPaymentOptions(context);

Se un modal con lo stesso nome esiste gia', usa il flag --force per sovrascriverlo:

metro make:bottom_sheet_modal payment_options --force

Utilizzo Base

Mostra un modal usando BottomSheetModal:

BottomSheetModal.showLogout(context);

Creare un Modal

Il pattern consigliato e' creare una classe BottomSheetModal con metodi statici per ogni tipo di modal. Il boilerplate fornisce questa struttura:

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

class BottomSheetModal extends NyBaseModal {
  static ModalShowFunction get displayModal => displayModal;

  /// Show Logout modal
  static Future<void> showLogout(
    BuildContext context, {
    Function()? onLogoutPressed,
    Function()? onCancelPressed,
  }) {
    return displayModal(
      context,
      isScrollControlled: false,
      child: const LogoutModal(),
      actionsRow: [
        Button.secondary(
          text: "Logout",
          onPressed: onLogoutPressed ?? () => routeToInitial(),
        ),
        Button(
          text: "Cancel",
          onPressed: onCancelPressed ?? () => Navigator.pop(context),
        ),
      ],
    );
  }
}

Chiamalo da qualsiasi punto:

BottomSheetModal.showLogout(context);

// With custom callbacks
BottomSheetModal.showLogout(
  context,
  onLogoutPressed: () {
    // Custom logout logic
  },
  onCancelPressed: () {
    Navigator.pop(context);
  },
);

BottomSheetModal

displayModal<T>() e' il metodo principale per visualizzare i modal.

Parametri

Parametro Tipo Predefinito Descrizione
context BuildContext obbligatorio Build context per il modal
child Widget obbligatorio Widget del contenuto principale
actionsRow List<Widget> [] Widget azione visualizzati in una riga orizzontale
actionsColumn List<Widget> [] Widget azione visualizzati verticalmente
height double? null Altezza fissa per il modal
header Widget? null Widget header in cima
useSafeArea bool true Avvolge il contenuto in SafeArea
isScrollControlled bool false Permette al modal di essere scrollabile
showCloseButton bool false Mostra un pulsante X di chiusura
headerPadding EdgeInsets? null Padding quando l'header e' presente
backgroundColor Color? null Colore di sfondo del modal
showHandle bool true Mostra la maniglia di trascinamento in cima
closeButtonColor Color? null Colore di sfondo del pulsante di chiusura
closeButtonIconColor Color? null Colore dell'icona del pulsante di chiusura
modalDecoration BoxDecoration? null Decorazione personalizzata per il contenitore del modal
handleColor Color? null Colore della maniglia di trascinamento

Azioni

Le azioni sono pulsanti visualizzati in fondo al modal.

Le azioni in riga sono posizionate una accanto all'altra, ciascuna occupa lo stesso spazio:

displayModal(
  context,
  child: Text("Are you sure?"),
  actionsRow: [
    ElevatedButton(
      onPressed: () => Navigator.pop(context, true),
      child: Text("Yes"),
    ),
    TextButton(
      onPressed: () => Navigator.pop(context, false),
      child: Text("No"),
    ),
  ],
);

Le azioni in colonna sono impilate verticalmente:

displayModal(
  context,
  child: Text("Choose an option"),
  actionsColumn: [
    ElevatedButton(
      onPressed: () => Navigator.pop(context, true),
      child: Text("Yes"),
    ),
    TextButton(
      onPressed: () => Navigator.pop(context, false),
      child: Text("No"),
    ),
  ],
);

Header

Aggiungi un header che si posiziona sopra il contenuto principale:

displayModal(
  context,
  header: Container(
    padding: EdgeInsets.all(16),
    color: Colors.blue,
    child: Text(
      "Modal Title",
      style: TextStyle(color: Colors.white, fontSize: 18),
    ),
  ),
  child: Text("Modal content goes here"),
);

Pulsante di Chiusura

Mostra un pulsante di chiusura nell'angolo in alto a destra:

displayModal(
  context,
  showCloseButton: true,
  closeButtonColor: Colors.grey.shade200,
  closeButtonIconColor: Colors.black,
  child: Padding(
    padding: EdgeInsets.all(24),
    child: Text("Content with close button"),
  ),
);

Decorazione Personalizzata

Personalizza l'aspetto del contenitore del modal:

displayModal(
  context,
  backgroundColor: Colors.transparent,
  modalDecoration: BoxDecoration(
    color: Colors.white,
    borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
    boxShadow: [
      BoxShadow(color: Colors.black26, blurRadius: 10),
    ],
  ),
  handleColor: Colors.grey.shade400,
  child: Text("Custom styled modal"),
);

BottomModalSheetStyle

BottomModalSheetStyle configura l'aspetto dei bottom sheet modal utilizzati dai picker dei form e da altri componenti:

BottomModalSheetStyle(
  backgroundColor: NyColor(light: Colors.white, dark: Colors.grey.shade900),
  barrierColor: NyColor(light: Colors.black54, dark: Colors.black87),
  useRootNavigator: false,
  titleStyle: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
  itemStyle: TextStyle(fontSize: 16),
  clearButtonStyle: TextStyle(color: Colors.red),
)
Proprieta' Tipo Descrizione
backgroundColor NyColor? Colore di sfondo del modal
barrierColor NyColor? Colore dell'overlay dietro il modal
useRootNavigator bool Usa il navigator root (predefinito: false)
routeSettings RouteSettings? Impostazioni della route per il modal
titleStyle TextStyle? Stile per il testo del titolo
itemStyle TextStyle? Stile per il testo degli elementi della lista
clearButtonStyle TextStyle? Stile per il testo del pulsante di cancellazione

Esempi

Modal di Conferma

static Future<bool?> showConfirm(
  BuildContext context, {
  required String message,
}) {
  return displayModal<bool>(
    context,
    child: Padding(
      padding: EdgeInsets.symmetric(vertical: 16),
      child: Text(message, textAlign: TextAlign.center),
    ),
    actionsRow: [
      ElevatedButton(
        onPressed: () => Navigator.pop(context, true),
        child: Text("Confirm"),
      ),
      TextButton(
        onPressed: () => Navigator.pop(context, false),
        child: Text("Cancel"),
      ),
    ],
  );
}

// Usage
bool? confirmed = await BottomSheetModal.showConfirm(
  context,
  message: "Delete this item?",
);
if (confirmed == true) {
  // delete the item
}

Modal con Contenuto Scrollabile

displayModal(
  context,
  isScrollControlled: true,
  height: MediaQuery.of(context).size.height * 0.8,
  showCloseButton: true,
  header: Padding(
    padding: EdgeInsets.all(16),
    child: Text("Terms of Service", style: TextStyle(fontSize: 20)),
  ),
  child: SingleChildScrollView(
    child: Text(longTermsText),
  ),
);

Foglio di Azioni

displayModal(
  context,
  showHandle: true,
  child: Text("Share via", style: TextStyle(fontSize: 18)),
  actionsColumn: [
    ListTile(
      leading: Icon(Icons.email),
      title: Text("Email"),
      onTap: () {
        Navigator.pop(context);
        shareViaEmail();
      },
    ),
    ListTile(
      leading: Icon(Icons.message),
      title: Text("Message"),
      onTap: () {
        Navigator.pop(context);
        shareViaMessage();
      },
    ),
    ListTile(
      leading: Icon(Icons.copy),
      title: Text("Copy Link"),
      onTap: () {
        Navigator.pop(context);
        copyLink();
      },
    ),
  ],
);