shortcut_grid.dart 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. import 'dart:math' as math;
  2. import 'dart:typed_data';
  3. import 'package:flutter/material.dart';
  4. import 'shortcut_tile.dart';
  5. class ShortcutSpec {
  6. const ShortcutSpec({
  7. required this.title,
  8. required this.subtitle,
  9. required this.iconAsset,
  10. required this.assigned,
  11. required this.onTap,
  12. this.onLongPress,
  13. this.longPressHint,
  14. this.iconBytes,
  15. this.showEqualizer = false,
  16. });
  17. final String title;
  18. final String? subtitle;
  19. final String iconAsset;
  20. final bool assigned;
  21. final VoidCallback onTap;
  22. final VoidCallback? onLongPress;
  23. final String? longPressHint;
  24. final Uint8List? iconBytes;
  25. final bool showEqualizer;
  26. }
  27. /// Fits up to 16 home shortcuts. Columns and tile size are chosen together so
  28. /// the whole set stays visible and grows denser as apps are added.
  29. class ShortcutGrid extends StatelessWidget {
  30. const ShortcutGrid({
  31. super.key,
  32. required this.shortcuts,
  33. required this.compact,
  34. });
  35. final List<ShortcutSpec> shortcuts;
  36. final bool compact;
  37. @override
  38. Widget build(BuildContext context) {
  39. return LayoutBuilder(
  40. builder: (context, constraints) {
  41. if (!constraints.hasBoundedWidth || !constraints.hasBoundedHeight) {
  42. return const SizedBox.shrink();
  43. }
  44. if (shortcuts.isEmpty) return const SizedBox.shrink();
  45. final dense = shortcuts.length > 8;
  46. final horizontalGap = dense
  47. ? (compact ? 10.0 : 14.0)
  48. : (compact ? 18.0 : 24.0);
  49. final rowGap = dense
  50. ? (compact ? 12.0 : 16.0)
  51. : (compact ? 18.0 : 26.0);
  52. final labelAllowance = compact ? 46.0 : 50.0;
  53. final maxTileSize = compact ? 168.0 : 196.0;
  54. var bestSize = 0.0;
  55. for (var columns = 2; columns <= 6; columns++) {
  56. if (columns > shortcuts.length) break;
  57. final rows = (shortcuts.length / columns).ceil();
  58. final widthLimited =
  59. (constraints.maxWidth - horizontalGap * (columns - 1)) / columns;
  60. final heightLimited =
  61. (constraints.maxHeight - rowGap * (rows - 1)) / rows -
  62. labelAllowance;
  63. final candidate = math
  64. .min(widthLimited, heightLimited)
  65. .clamp(0.0, maxTileSize);
  66. if (candidate > bestSize) bestSize = candidate;
  67. }
  68. final size = bestSize.clamp(64.0, maxTileSize);
  69. return Center(
  70. child: Wrap(
  71. alignment: WrapAlignment.center,
  72. crossAxisAlignment: WrapCrossAlignment.start,
  73. spacing: horizontalGap,
  74. runSpacing: rowGap,
  75. children: [
  76. for (final shortcut in shortcuts)
  77. ShortcutTile(
  78. title: shortcut.title,
  79. subtitle: shortcut.subtitle,
  80. iconAsset: shortcut.iconAsset,
  81. size: size,
  82. assigned: shortcut.assigned,
  83. onTap: shortcut.onTap,
  84. onLongPress: shortcut.onLongPress,
  85. longPressHint: shortcut.longPressHint,
  86. iconBytes: shortcut.iconBytes,
  87. showEqualizer: shortcut.showEqualizer,
  88. ),
  89. ],
  90. ),
  91. );
  92. },
  93. );
  94. }
  95. }