public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Shan Shaji <s.shaji@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH pve_flutter_frontend 2/4] fix: breaking changes due to the upgrade of font_awesome_flutter to v11
Date: Fri, 10 Apr 2026 17:09:30 +0200	[thread overview]
Message-ID: <20260410150935.25870-3-s.shaji@proxmox.com> (raw)
In-Reply-To: <20260410150935.25870-1-s.shaji@proxmox.com>

Previously, `FaIconData` extended `IconData`, but starting from
`v11.0.0`, `FaIconData` no longer implements `IconData` to prevent
rendering issues and to anticipate `IconData` being marked `final`
in an upcoming Flutter release, which will prevent subtyping outside
of its library.

Due to this change, the `Icon` widget can no longer directly accept
`FontAwesomeIcons`. To fix this, replace the `Icon` widget with
`FaIcon` wherever `FontAwesomeIcons` is used.

Signed-off-by: Shan Shaji <s.shaji@proxmox.com>
---
 lib/pages/main_layout_slim.dart               |  4 +-
 lib/utils/renderers.dart                      |  5 +--
 lib/widgets/pve_action_card_widget.dart       | 17 +++++++++
 lib/widgets/pve_file_selector_widget.dart     |  6 +--
 lib/widgets/pve_guest_backup_widget.dart      |  2 +-
 lib/widgets/pve_guest_migrate_widget.dart     |  4 +-
 lib/widgets/pve_guest_os_selector_widget.dart |  4 +-
 lib/widgets/pve_guest_overview_header.dart    |  4 +-
 lib/widgets/pve_lxc_overview.dart             | 32 +++++-----------
 lib/widgets/pve_node_overview.dart            |  6 +--
 lib/widgets/pve_qemu_overview.dart            | 38 +++++++------------
 .../pve_qemu_power_settings_widget.dart       |  4 +-
 pubspec.lock                                  |  4 +-
 pubspec.yaml                                  |  2 +-
 14 files changed, 63 insertions(+), 69 deletions(-)

diff --git a/lib/pages/main_layout_slim.dart b/lib/pages/main_layout_slim.dart
index 4229ef4..c8ab607 100644
--- a/lib/pages/main_layout_slim.dart
+++ b/lib/pages/main_layout_slim.dart
@@ -861,11 +861,11 @@ class AppBarFilterIconButton extends StatelessWidget {
         builder: (context, state) {
           return IconButton(
             icon: rBloc.isFiltered
-                ? const Icon(
+                ? const FaIcon(
                     FontAwesomeIcons.filter,
                     color: Colors.black,
                   )
-                : const Icon(
+                : const FaIcon(
                     FontAwesomeIcons.filter,
                     color: Colors.grey,
                   ),
diff --git a/lib/utils/renderers.dart b/lib/utils/renderers.dart
index b139a87..5c73539 100644
--- a/lib/utils/renderers.dart
+++ b/lib/utils/renderers.dart
@@ -35,13 +35,12 @@ class Renderers {
         return Icons.storage;
       case "qemu":
         return Icons.desktop_windows;
-
       case "lxc":
-        return FontAwesomeIcons.cube;
+        return FontAwesomeIcons.cube.data;
       case "storage":
         return (shared ?? false)
             ? Icons.folder_shared
-            : FontAwesomeIcons.database;
+            : FontAwesomeIcons.database.data;
       case "pool":
         return Icons.label;
       default:
diff --git a/lib/widgets/pve_action_card_widget.dart b/lib/widgets/pve_action_card_widget.dart
index 019a584..9fca232 100644
--- a/lib/widgets/pve_action_card_widget.dart
+++ b/lib/widgets/pve_action_card_widget.dart
@@ -1,4 +1,5 @@
 import 'package:flutter/material.dart';
+import 'package:font_awesome_flutter/font_awesome_flutter.dart';
 
 class ActionCard extends StatelessWidget {
   final Function? onTap;
@@ -14,6 +15,22 @@ class ActionCard extends StatelessWidget {
     this.color,
   });
 
+  factory ActionCard.withIcon(String title, IconData icon, Function onTap) {
+    return ActionCard(
+      title: title,
+      icon: Icon(icon, size: 55, color: Colors.white24),
+      onTap: onTap,
+    );
+  }
+
+  factory ActionCard.withFaIcon(String title, FaIconData icon, Function onTap) {
+    return ActionCard(
+      title: title,
+      icon: FaIcon(icon, size: 55, color: Colors.white24),
+      onTap: onTap,
+    );
+  }
+
   @override
   Widget build(BuildContext context) {
     return Card(
diff --git a/lib/widgets/pve_file_selector_widget.dart b/lib/widgets/pve_file_selector_widget.dart
index 59964dd..1722e50 100644
--- a/lib/widgets/pve_file_selector_widget.dart
+++ b/lib/widgets/pve_file_selector_widget.dart
@@ -373,7 +373,7 @@ class FileSelectorContentView extends StatelessWidget {
                 child: Column(
                   mainAxisAlignment: MainAxisAlignment.center,
                   children: <Widget>[
-                    Icon(
+                    FaIcon(
                       getContentIcon(content![index].content),
                       color: Theme.of(context).colorScheme.secondary,
                     ),
@@ -401,7 +401,7 @@ class FileSelectorContentView extends StatelessWidget {
       itemCount: content!.length,
       itemBuilder: (context, index) => Card(
         child: ListTile(
-          leading: Icon(
+          leading: FaIcon(
             getContentIcon(content![index].content),
             color: Theme.of(context).colorScheme.secondary,
           ),
@@ -418,7 +418,7 @@ class FileSelectorContentView extends StatelessWidget {
     );
   }
 
-  IconData getContentIcon(PveStorageContentType? content) {
+  FaIconData getContentIcon(PveStorageContentType? content) {
     switch (content) {
       case PveStorageContentType.iso:
         return FontAwesomeIcons.compactDisc;
diff --git a/lib/widgets/pve_guest_backup_widget.dart b/lib/widgets/pve_guest_backup_widget.dart
index 4966e9d..119348b 100644
--- a/lib/widgets/pve_guest_backup_widget.dart
+++ b/lib/widgets/pve_guest_backup_widget.dart
@@ -205,7 +205,7 @@ class PveGuestBackupContent extends StatelessWidget {
       itemCount: content!.length,
       itemBuilder: (context, index) => Card(
         child: ListTile(
-          leading: Icon(
+          leading: FaIcon(
             FontAwesomeIcons.floppyDisk,
             color: Theme.of(context).colorScheme.onSurface,
           ),
diff --git a/lib/widgets/pve_guest_migrate_widget.dart b/lib/widgets/pve_guest_migrate_widget.dart
index cb6cd38..c65f6fe 100644
--- a/lib/widgets/pve_guest_migrate_widget.dart
+++ b/lib/widgets/pve_guest_migrate_widget.dart
@@ -55,7 +55,7 @@ class PveGuestMigrate extends StatelessWidget {
                       crossAxisAlignment: CrossAxisAlignment.start,
                       children: [
                         ListTile(
-                          leading: const Icon(FontAwesomeIcons.globe),
+                          leading: const FaIcon(FontAwesomeIcons.globe),
                           title: Text(
                             'Mode',
                             style: TextStyle(color: colorScheme.onPrimary),
@@ -68,7 +68,7 @@ class PveGuestMigrate extends StatelessWidget {
                           ),
                         ),
                         ListTile(
-                          leading: const Icon(FontAwesomeIcons.mapPin),
+                          leading: const FaIcon(FontAwesomeIcons.mapPin),
                           title: Text(
                             'Source',
                             style: TextStyle(color: colorScheme.onPrimary),
diff --git a/lib/widgets/pve_guest_os_selector_widget.dart b/lib/widgets/pve_guest_os_selector_widget.dart
index 2b3bd82..3a89143 100644
--- a/lib/widgets/pve_guest_os_selector_widget.dart
+++ b/lib/widgets/pve_guest_os_selector_widget.dart
@@ -40,11 +40,11 @@ class PveGuestOsSelector extends StatelessWidget {
 
   Widget getIcon(String? osGroup) {
     if (osGroup == "Microsoft Windows") {
-      return const Icon(FontAwesomeIcons.windows);
+      return const FaIcon(FontAwesomeIcons.windows);
     }
 
     if (osGroup == "Linux") {
-      return const Icon(FontAwesomeIcons.linux);
+      return const FaIcon(FontAwesomeIcons.linux);
     }
 
     return Text(osGroup!);
diff --git a/lib/widgets/pve_guest_overview_header.dart b/lib/widgets/pve_guest_overview_header.dart
index e4b5756..addb888 100644
--- a/lib/widgets/pve_guest_overview_header.dart
+++ b/lib/widgets/pve_guest_overview_header.dart
@@ -122,7 +122,7 @@ class PveGuestOverviewHeader extends StatelessWidget {
                     padding: const EdgeInsets.symmetric(horizontal: 20.0),
                     child: Row(
                       children: <Widget>[
-                        Icon(
+                        FaIcon(
                           FontAwesomeIcons.heartPulse,
                           color: haError ? Colors.red : Colors.green[400],
                         ),
@@ -219,7 +219,7 @@ class _PveGuestHeaderRRDPageViewState extends State<PveGuestHeaderRRDPageView> {
                     subtitle: Renderers.formatSize(rrdData.last.mem ?? 0),
                     data: rrdData.where((e) => e.mem != null).map(
                         (e) => Point(e.time!.millisecondsSinceEpoch, e.mem!)),
-                    icon: Icon(FontAwesomeIcons.memory, color: fgColor),
+                    icon: FaIcon(FontAwesomeIcons.memory, color: fgColor),
                     bottomRight: pageIndicator,
                     dataRenderer: (data) => Renderers.formatSize(data),
                   ),
diff --git a/lib/widgets/pve_lxc_overview.dart b/lib/widgets/pve_lxc_overview.dart
index 78ad853..7caa5cf 100644
--- a/lib/widgets/pve_lxc_overview.dart
+++ b/lib/widgets/pve_lxc_overview.dart
@@ -38,18 +38,6 @@ class PveLxcOverview extends StatelessWidget {
   static final routeName = RegExp(r"\/nodes\/(\S+)\/lxc\/(\d+)");
   final String guestID;
 
-  ActionCard createActionCard(String title, IconData icon, Function onTap) {
-    return ActionCard(
-      icon: Icon(
-        icon,
-        size: 55,
-        color: Colors.white24,
-      ),
-      title: title,
-      onTap: onTap,
-    );
-  }
-
   const PveLxcOverview({super.key, required this.guestID});
   @override
   Widget build(BuildContext context) {
@@ -139,13 +127,13 @@ class PveLxcOverview extends StatelessWidget {
                             mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                             children: <Widget>[
                               if (!(status?.template ?? false))
-                                createActionCard(
+                                ActionCard.withIcon(
                                     'Power Settings',
                                     Icons.power_settings_new,
                                     () => showPowerMenuBottomSheet(
                                         context, lxcBloc)),
                               if (!(status?.template ?? false))
-                                createActionCard(
+                                ActionCard.withIcon(
                                     'Console',
                                     Icons.queue_play_next,
                                     () => showConsoleMenuBottomSheet(
@@ -154,7 +142,7 @@ class PveLxcOverview extends StatelessWidget {
                                         guestID,
                                         state.nodeID,
                                         'lxc')),
-                              createActionCard(
+                              ActionCard.withIcon(
                                   'Options',
                                   Icons.settings,
                                   () => Navigator.of(context)
@@ -164,7 +152,7 @@ class PveLxcOverview extends StatelessWidget {
                                               ),
                                           fullscreenDialog: true))),
                               if (!resourceBloc.latestState.isStandalone)
-                                createActionCard(
+                                ActionCard.withFaIcon(
                                     'Migrate',
                                     FontAwesomeIcons.paperPlane,
                                     () => Navigator.of(context).push(
@@ -172,7 +160,7 @@ class PveLxcOverview extends StatelessWidget {
                                             guestID,
                                             state.nodeID,
                                             resourceBloc.apiClient!))),
-                              createActionCard(
+                              ActionCard.withFaIcon(
                                   'Backup',
                                   FontAwesomeIcons.floppyDisk,
                                   () => Navigator.of(context).push(
@@ -193,7 +181,7 @@ class PveLxcOverview extends StatelessWidget {
                           ),
                           children: <Widget>[
                             ListTile(
-                              leading: const Icon(FontAwesomeIcons.memory),
+                              leading: const FaIcon(FontAwesomeIcons.memory),
                               title: Text('${config.memory}'),
                               subtitle: const Text('Memory'),
                               dense: true,
@@ -211,7 +199,7 @@ class PveLxcOverview extends StatelessWidget {
                               dense: true,
                             ),
                             ListTile(
-                              leading: const Icon(FontAwesomeIcons.hardDrive),
+                              leading: const FaIcon(FontAwesomeIcons.hardDrive),
                               dense: true,
                               title: Text('${config.rootfs}'),
                             ),
@@ -229,7 +217,7 @@ class PveLxcOverview extends StatelessWidget {
                               for (var net in config.net!)
                                 ListTile(
                                   leading:
-                                      const Icon(FontAwesomeIcons.ethernet),
+                                      const FaIcon(FontAwesomeIcons.ethernet),
                                   dense: true,
                                   title: Text(net),
                                 ),
@@ -244,14 +232,14 @@ class PveLxcOverview extends StatelessWidget {
                             ),
                             children: <Widget>[
                               ListTile(
-                                leading: const Icon(FontAwesomeIcons.globe),
+                                leading: const FaIcon(FontAwesomeIcons.globe),
                                 dense: true,
                                 title: Text(
                                     config.searchdomain ?? 'Use host settings'),
                                 subtitle: const Text('DNS Domain'),
                               ),
                               ListTile(
-                                leading: const Icon(
+                                leading: const FaIcon(
                                     FontAwesomeIcons.magnifyingGlass),
                                 dense: true,
                                 title: Text(
diff --git a/lib/widgets/pve_node_overview.dart b/lib/widgets/pve_node_overview.dart
index 7236cf4..092bb54 100644
--- a/lib/widgets/pve_node_overview.dart
+++ b/lib/widgets/pve_node_overview.dart
@@ -115,7 +115,7 @@ class PveNodeOverview extends StatelessWidget {
                                       data: rrd.map((e) => Point(
                                           e.time!.millisecondsSinceEpoch,
                                           e.memused ?? 0)),
-                                      icon: Icon(FontAwesomeIcons.memory,
+                                      icon: FaIcon(FontAwesomeIcons.memory,
                                           color: fgColor),
                                       bottomRight: pageIndicator,
                                       dataRenderer: (data) =>
@@ -259,7 +259,7 @@ class PveNodeOverview extends StatelessWidget {
                           child: ListTile(
                             title: const Text('HD space (root)'),
                             subtitle: ProxmoxCapacityIndicator(
-                              icon: Icon(
+                              icon: FaIcon(
                                 FontAwesomeIcons.solidHardDrive,
                                 color: Colors.blueGrey[300],
                               ),
@@ -385,7 +385,7 @@ class PveNodeOverview extends StatelessWidget {
                         .map(
                           (d) => ListTile(
                             dense: true,
-                            leading: Icon(FontAwesomeIcons.solidHardDrive,
+                            leading: FaIcon(FontAwesomeIcons.solidHardDrive,
                                 color: state.isDiskHealthy(d)
                                     ? Colors.grey
                                     : Colors.red),
diff --git a/lib/widgets/pve_qemu_overview.dart b/lib/widgets/pve_qemu_overview.dart
index 3fb25ba..12e3b30 100644
--- a/lib/widgets/pve_qemu_overview.dart
+++ b/lib/widgets/pve_qemu_overview.dart
@@ -40,18 +40,6 @@ class PveQemuOverview extends StatelessWidget {
 
   const PveQemuOverview({super.key, required this.guestID});
 
-  ActionCard createActionCard(String title, IconData icon, Function onTap) {
-    return ActionCard(
-      icon: Icon(
-        icon,
-        size: 55,
-        color: Colors.white24,
-      ),
-      title: title,
-      onTap: onTap,
-    );
-  }
-
   @override
   Widget build(BuildContext context) {
     final bloc = Provider.of<PveQemuOverviewBloc>(context);
@@ -140,13 +128,13 @@ class PveQemuOverview extends StatelessWidget {
                           mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                           children: <Widget>[
                             if (!(status?.template ?? false))
-                              createActionCard(
+                              ActionCard.withIcon(
                                   'Power Settings',
                                   Icons.power_settings_new,
                                   () =>
                                       showPowerMenuBottomSheet(context, bloc)),
                             if (!(status?.template ?? false))
-                              createActionCard(
+                              ActionCard.withIcon(
                                   'Console',
                                   Icons.queue_play_next,
                                   () => showConsoleMenuBottomSheet(
@@ -157,19 +145,19 @@ class PveQemuOverview extends StatelessWidget {
                                         'qemu',
                                         allowSpice: status?.spice ?? false,
                                       )),
-                            createActionCard(
+                            ActionCard.withIcon(
                                 'Options',
                                 Icons.settings,
                                 () => Navigator.of(context)
                                     .push(_createOptionsRoute(bloc))),
                             if (!rBloc.latestState.isStandalone)
-                              createActionCard(
+                              ActionCard.withFaIcon(
                                   'Migrate',
                                   FontAwesomeIcons.paperPlane,
                                   () => Navigator.of(context).push(
                                       _createMigrationRoute(guestID,
                                           state.nodeID, bloc.apiClient))),
-                            createActionCard(
+                            ActionCard.withFaIcon(
                                 'Backup',
                                 FontAwesomeIcons.floppyDisk,
                                 () => Navigator.of(context).push(
@@ -191,7 +179,7 @@ class PveQemuOverview extends StatelessWidget {
                           ),
                           children: [
                             ListTile(
-                              leading: const Icon(FontAwesomeIcons.memory),
+                              leading: const FaIcon(FontAwesomeIcons.memory),
                               title: Text('${config.memory}'),
                               subtitle: const Text('Memory'),
                               dense: true,
@@ -204,20 +192,20 @@ class PveQemuOverview extends StatelessWidget {
                               dense: true,
                             ),
                             ListTile(
-                              leading: const Icon(FontAwesomeIcons.microchip),
+                              leading: const FaIcon(FontAwesomeIcons.microchip),
                               title: Text(
                                   config.bios?.name ?? 'Default (SeaBIOS)'),
                               subtitle: const Text('BIOS'),
                               dense: true,
                             ),
                             ListTile(
-                              leading: const Icon(FontAwesomeIcons.gears),
+                              leading: const FaIcon(FontAwesomeIcons.gears),
                               dense: true,
                               title: Text(config.machine ?? 'Default (i440fx)'),
                               subtitle: const Text('Machine Type'),
                             ),
                             ListTile(
-                              leading: const Icon(FontAwesomeIcons.database),
+                              leading: const FaIcon(FontAwesomeIcons.database),
                               title: Text(
                                   config.scsihw?.name ?? 'Default (i440fx)'),
                               subtitle: const Text('SCSI Controller'),
@@ -226,21 +214,23 @@ class PveQemuOverview extends StatelessWidget {
                             for (var ide in config.ide!)
                               ListTile(
                                 leading:
-                                    const Icon(FontAwesomeIcons.compactDisc),
+                                    const FaIcon(FontAwesomeIcons.compactDisc),
                                 title: Text(ide),
                                 subtitle: const Text('CD/DVD Drive'),
                                 dense: true,
                               ),
                             for (var scsi in config.scsi!)
                               ListTile(
-                                leading: const Icon(FontAwesomeIcons.hardDrive),
+                                leading:
+                                    const FaIcon(FontAwesomeIcons.hardDrive),
                                 title: Text(scsi),
                                 subtitle: const Text('Hard Disk'),
                                 dense: true,
                               ),
                             for (var net in config.net!)
                               ListTile(
-                                leading: const Icon(FontAwesomeIcons.ethernet),
+                                leading:
+                                    const FaIcon(FontAwesomeIcons.ethernet),
                                 dense: true,
                                 subtitle: const Text('Network Device'),
                                 title: Text(net),
diff --git a/lib/widgets/pve_qemu_power_settings_widget.dart b/lib/widgets/pve_qemu_power_settings_widget.dart
index b4c3c0a..84c3b83 100644
--- a/lib/widgets/pve_qemu_power_settings_widget.dart
+++ b/lib/widgets/pve_qemu_power_settings_widget.dart
@@ -86,7 +86,7 @@ class PveQemuPowerSettings extends StatelessWidget {
                             context, PveClusterResourceAction.suspend, bloc),
                       ),
                       ListTile(
-                        leading: const Icon(FontAwesomeIcons.download),
+                        leading: const FaIcon(FontAwesomeIcons.download),
                         title: const Text(
                           "Hibernate",
                           style: TextStyle(fontWeight: FontWeight.bold),
@@ -106,7 +106,7 @@ class PveQemuPowerSettings extends StatelessWidget {
                             context, PveClusterResourceAction.stop, bloc),
                       ),
                       ListTile(
-                        leading: const Icon(FontAwesomeIcons.bolt),
+                        leading: const FaIcon(FontAwesomeIcons.bolt),
                         title: const Text(
                           "Reset",
                           style: TextStyle(fontWeight: FontWeight.bold),
diff --git a/pubspec.lock b/pubspec.lock
index ecaab97..def499d 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -324,10 +324,10 @@ packages:
     dependency: "direct main"
     description:
       name: font_awesome_flutter
-      sha256: b9011df3a1fa02993630b8fb83526368cf2206a711259830325bab2f1d2a4eb0
+      sha256: "09dcde8ab90ffae1a7d65ff2ef96fc62a17ad9d0ce7c127b317ded676b0d5935"
       url: "https://pub.dev"
     source: hosted
-    version: "10.12.0"
+    version: "11.0.0"
   glob:
     dependency: transitive
     description:
diff --git a/pubspec.yaml b/pubspec.yaml
index e15d57b..04c56a4 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -30,7 +30,7 @@ dependencies:
   path: ^1.8.0
   provider: ^6.0.1
   meta: ^1.1.7
-  font_awesome_flutter: ^10.0.0
+  font_awesome_flutter: ^11.0.0
   url_launcher: ^6.0.17
   intl: ^0.20.1
   path_provider: ^2.0.8
-- 
2.50.1





  parent reply	other threads:[~2026-04-10 15:09 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-10 15:09 [PATCH proxmox{_login_manager,_dart_api_client}/pve_flutter_frontend 0/7] upgrade dependencies based on flutter v3.41 and migrate deprecated members Shan Shaji
2026-04-10 15:09 ` [PATCH pve_flutter_frontend 1/4] chore: upgrade dependencies based on flutter v3.41 Shan Shaji
2026-04-10 15:09 ` Shan Shaji [this message]
2026-04-10 15:09 ` [PATCH pve_flutter_frontend 3/4] fix: migrate to UIScene lifecycle for iOS 26+ compatibility Shan Shaji
2026-04-10 15:09 ` [PATCH pve_flutter_frontend 4/4] chore: use latest ndkVersion from flutter Shan Shaji
2026-04-10 15:09 ` [PATCH proxmox_login_manager 1/1] chore: upgrade dependencies Shan Shaji
2026-04-10 15:09 ` [PATCH proxmox_dart_api_client 1/2] " Shan Shaji
2026-04-10 15:09 ` [PATCH proxmox_dart_api_client 2/2] deps: add objective_c dependency to access NSError's code property Shan Shaji

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260410150935.25870-3-s.shaji@proxmox.com \
    --to=s.shaji@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal