Make LogicalKeyboardKey.keyLabel a getter and better (#79100)

diff --git a/dev/tools/gen_keycodes/data/keyboard_key.tmpl b/dev/tools/gen_keycodes/data/keyboard_key.tmpl
index b4fa838..6baa963 100644
--- a/dev/tools/gen_keycodes/data/keyboard_key.tmpl
+++ b/dev/tools/gen_keycodes/data/keyboard_key.tmpl
@@ -76,9 +76,9 @@
 ///       _message = 'Pressed the "Q" key!';
 ///     } else {
 ///       if (kReleaseMode) {
-///         _message = 'Not a Q: Key label is "${event.logicalKey.keyLabel}"';
+///         _message = 'Not a Q: Pressed 0x${event.logicalKey.keyId.toRadixString(16)}';
 ///       } else {
-///         // This will only print useful information in debug mode.
+///         // The debugName will only print useful information in debug mode.
 ///         _message = 'Not a Q: Pressed ${event.logicalKey.debugName}';
 ///       }
 ///     }
@@ -124,21 +124,18 @@
 ///    keyboard events.
 @immutable
 class LogicalKeyboardKey extends KeyboardKey {
-  /// Creates a LogicalKeyboardKey object with an optional key label and debug
-  /// name.
+  /// A LogicalKeyboardKey object for a key ID.
   ///
-  /// [keyId] must not be null.
-  ///
-  /// {@tool snippet}
-  /// To save executable size, it is recommended that the [debugName] be null in
-  /// release mode. You can do this by using the [kReleaseMode] constant.
-  ///
-  /// ```dart
-  /// const LogicalKeyboardKey(0x0010000000a, debugName: kReleaseMode ? null : 'Special Key')
-  /// ```
-  /// {@end-tool}
-  const LogicalKeyboardKey(this.keyId, {this.debugName, this.keyLabel = ''})
-      : assert(keyId != null);
+  /// This factory constructor ensures that the same `keyId` always results
+  /// in the identical instance. It looks up an object from the predefined values
+  /// if possible, or construct a new one and cache it.
+  factory LogicalKeyboardKey(int keyId) {
+    return findKeyByKeyId(keyId) ??
+      _additionalKeyPool.putIfAbsent(keyId, () => LogicalKeyboardKey._(keyId));
+  }
+
+  /// Creates a LogicalKeyboardKey object for a key ID.
+  const LogicalKeyboardKey._(this.keyId);
 
   /// A unique code representing this key.
   ///
@@ -146,37 +143,95 @@
   /// from it, as the representation of the code could change at any time.
   final int keyId;
 
-  /// The debug string to print for this keyboard key, which will be null in
-  /// release mode.
-  final String? debugName;
+  // Returns the bits that are not included in [valueMask], shifted to the
+  // right.
+  //
+  // For example, if the input is 0x12abcdabcd, then the result is 0x12.
+  //
+  // This is mostly equivalent to a right shift, resolving the problem that
+  // JavaScript only support 32-bit bitwise operation and needs to use division
+  // instead.
+  static int _nonValueBits(int n) {
+    // `n >> valueMaskWidth` is equivalent to `n / divisorForValueMask`.
+    const int divisorForValueMask = valueMask + 1;
+    const int valueMaskWidth = 32;
 
-  /// The Unicode string representing the character produced by a [RawKeyEvent].
+    // Equivalent to assert(divisorForValueMask == (1 << valueMaskWidth)).
+    const int _firstDivisorWidth = 28;
+    assert(divisorForValueMask ==
+      (1 << _firstDivisorWidth) * (1 << (valueMaskWidth - _firstDivisorWidth)));
+
+    // JS only supports up to 2^53 - 1, therefore non-value bits can only
+    // contain (maxSafeIntegerWidth - valueMaskWidth) bits.
+    const int maxSafeIntegerWidth = 52;
+    const int nonValueMask = (1 << (maxSafeIntegerWidth - valueMaskWidth)) - 1;
+
+    if (kIsWeb) {
+      return (n / divisorForValueMask).floor() & nonValueMask;
+    } else {
+      return (n >> valueMaskWidth) & nonValueMask;
+    }
+  }
+
+  static String? _unicodeKeyLabel(int keyId) {
+    if (_nonValueBits(keyId) == 0) {
+      return String.fromCharCode(keyId).toUpperCase();
+    }
+    return null;
+  }
+
+  /// A description representing the character produced by a [RawKeyEvent].
   ///
-  /// This value is useful for describing or matching mnemonic keyboard
-  /// shortcuts.
+  /// This value is useful for providing readable strings for keys or keyboard
+  /// shortcuts. Do not use this value to compare equality of keys; compare
+  /// [keyId] instead.
   ///
-  /// This value is an empty string if there's no key label data for a key.
+  /// For printable keys, this is usually the printable character in upper case
+  /// ignoring modifiers or combining keys, such as 'A', '1', or '/'. This
+  /// might also return accented letters (such as 'Ù') for keys labeled as so,
+  /// but not if such character is a result from preceding combining keys ('`̀'
+  /// followed by key U).
   ///
-  /// On most platforms this is a single code point, but it could contain any
-  /// Unicode string. The `keyLabel` differs from [RawKeyEvent.character]
-  /// because `keyLabel` only takes into account the key being pressed, not any
-  /// combining keys pressed before it, so, for example, an “o” that follows a
-  /// combining dieresis (“¨”, COMBINING DIAERESIS (U+0308)) would just return
-  /// “o” for [keyLabel], but would return “ö” for [RawKeyEvent.character].
+  /// For other keys, [keyLabel] looks up the full key name from a predefined
+  /// map, such as 'F1', 'Shift Left', or 'Media Down'. This value is an empty
+  /// string if there's no key label data for a key.
+  ///
+  /// For the printable representation that takes into consideration the
+  /// modifiers and combining keys, see [RawKeyEvent.character].
   ///
   /// {@macro flutter.services.RawKeyEventData.keyLabel}
-  final String keyLabel;
+  String get keyLabel {
+    return _unicodeKeyLabel(keyId)
+        ?? _keyLabels[keyId]
+        ?? '';
+  }
 
-  @override
-  int get hashCode => keyId.hashCode;
-
-  @override
-  bool operator ==(Object other) {
-    if (other.runtimeType != runtimeType) {
-      return false;
-    }
-    return other is LogicalKeyboardKey
-        && other.keyId == keyId;
+  /// The debug string to print for this keyboard key, which will be null in
+  /// release mode.
+  ///
+  /// For printable keys, this is usually a more descriptive name related to
+  /// [keyLabel], such as 'Key A', 'Digit 1', 'Backslash'. This might
+  /// also return accented letters (such as 'Key Ù') for keys labeled as so.
+  ///
+  /// For other keys, this looks up the full key name from a predefined map (the
+  /// same value as [keyLabel]), such as 'F1', 'Shift Left', or 'Media Down'. If
+  /// there's no key label data for a key, this returns a name that explains the
+  /// ID (such as 'Key with ID 0x00100012345').
+  String? get debugName {
+    String? result;
+    assert(() {
+      result = _keyLabels[keyId];
+      if (result == null) {
+        final String? unicodeKeyLabel = _unicodeKeyLabel(keyId);
+        if (unicodeKeyLabel != null) {
+          result = 'Key $unicodeKeyLabel';
+        } else {
+          result = 'Key with ID 0x${keyId.toRadixString(16).padLeft(11, '0')}';
+        }
+      }
+      return true;
+    }());
+    return result;
   }
 
   /// Returns the [LogicalKeyboardKey] constant that matches the given ID, or
@@ -261,10 +316,11 @@
   void debugFillProperties(DiagnosticPropertiesBuilder properties) {
     super.debugFillProperties(properties);
     properties.add(StringProperty('keyId', '0x${keyId.toRadixString(16).padLeft(8, '0')}', showName: true));
-    properties.add(StringProperty('keyLabel', keyLabel, showName: true));
     properties.add(StringProperty('debugName', debugName, showName: true, defaultValue: null));
   }
 
+  static final Map<int, LogicalKeyboardKey> _additionalKeyPool = <int, LogicalKeyboardKey>{};
+
   /// Mask for the 32-bit value portion of the key code.
   ///
   /// This is used by platform-specific code to generate Flutter key codes.
@@ -313,6 +369,9 @@
   // A map of keys to the pseudo-key synonym for that key. Used by getSynonyms.
   static final Map<LogicalKeyboardKey, LogicalKeyboardKey> _synonyms = <LogicalKeyboardKey, LogicalKeyboardKey>{
 @@@LOGICAL_KEY_SYNONYMS@@@  };
+
+  static const Map<int, String> _keyLabels = <int, String>{
+@@@LOGICAL_KEY_KEY_LABELS@@@    };
 }
 
 /// A class with static values that describe the keys that are returned from
@@ -387,7 +446,7 @@
 ///                 onTap: () {
 ///                   FocusScope.of(context).requestFocus(_focusNode);
 ///                 },
-///                 child: Text('Tap to focus'),
+///                 child: const Text('Tap to focus'),
 ///               );
 ///             }
 ///             return Text(_message ?? 'Press a key');
@@ -408,20 +467,18 @@
 ///    keyboard events.
 @immutable
 class PhysicalKeyboardKey extends KeyboardKey {
-  /// Creates a PhysicalKeyboardKey object with an optional debug name.
+  /// A PhysicalKeyboardKey object for a USB HID usage.
   ///
-  /// The [usbHidUsage] must not be null.
-  ///
-  /// {@tool snippet}
-  /// To save executable size, it is recommended that the [debugName] be null in
-  /// release mode. You can do this using the [kReleaseMode] constant.
-  ///
-  /// ```dart
-  /// const PhysicalKeyboardKey(0x0000ffff, debugName: kReleaseMode ? null : 'Special Key')
-  /// ```
-  /// {@end-tool}
-  const PhysicalKeyboardKey(this.usbHidUsage, {this.debugName})
-      : assert(usbHidUsage != null);
+  /// This factory constructor ensures that the same `usbHidUsage` always results
+  /// in the identical instance. It looks up an object from the predefined values
+  /// if possible, or construct a new one and cache it.
+  factory PhysicalKeyboardKey(int usbHidUsage) {
+    return findKeyByCode(usbHidUsage) ??
+      _additionalKeyPool.putIfAbsent(usbHidUsage, () => PhysicalKeyboardKey._(usbHidUsage));
+  }
+
+  /// Creates a PhysicalKeyboardKey object for a USB HID usage.
+  const PhysicalKeyboardKey._(this.usbHidUsage);
 
   /// The unique USB HID usage ID of this physical key on the keyboard.
   ///
@@ -435,31 +492,29 @@
 
   /// The debug string to print for this keyboard key, which will be null in
   /// release mode.
-  final String? debugName;
+  String? get debugName {
+    String? result;
+    assert(() {
+      result = _debugNames[usbHidUsage] ??
+        'Key with ID 0x${usbHidUsage.toRadixString(16).padLeft(8, '0')}';
+      return true;
+    }());
+    return result;
+  }
 
   /// Finds a known [PhysicalKeyboardKey] that matches the given USB HID usage
   /// code.
   static PhysicalKeyboardKey? findKeyByCode(int usageCode) => _knownPhysicalKeys[usageCode];
 
   @override
-  int get hashCode => usbHidUsage.hashCode;
-
-  @override
-  bool operator ==(Object other) {
-    if (other.runtimeType != runtimeType) {
-      return false;
-    }
-    return other is PhysicalKeyboardKey
-        && other.usbHidUsage == usbHidUsage;
-  }
-
-  @override
   void debugFillProperties(DiagnosticPropertiesBuilder properties) {
     super.debugFillProperties(properties);
     properties.add(StringProperty('usbHidUsage', '0x${usbHidUsage.toRadixString(16).padLeft(8, '0')}', showName: true));
     properties.add(StringProperty('debugName', debugName, showName: true, defaultValue: null));
   }
 
+  static final Map<int, PhysicalKeyboardKey> _additionalKeyPool = <int, PhysicalKeyboardKey>{};
+
   // Key constants for all keyboard keys in the USB HID specification at the
   // time Flutter was built.
 @@@PHYSICAL_KEY_DEFINITIONS@@@
@@ -468,4 +523,9 @@
   static const Map<int, PhysicalKeyboardKey> _knownPhysicalKeys = <int, PhysicalKeyboardKey>{
 @@@PHYSICAL_KEY_MAP@@@
   };
+
+  static const Map<int, String> _debugNames = kReleaseMode ?
+    <int, String>{} :
+    <int, String>{
+@@@PHYSICAL_KEY_DEBUG_NAMES@@@    };
 }