shortcut_grid.dart 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  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. The grid is
  29. /// anchored to the bottom so a short row does not float mid-page.
  30. class ShortcutGrid extends StatelessWidget {
  31. const ShortcutGrid({
  32. super.key,
  33. required this.shortcuts,
  34. required this.compact,
  35. });
  36. final List<ShortcutSpec> shortcuts;
  37. final bool compact;
  38. @override
  39. Widget build(BuildContext context) {
  40. return LayoutBuilder(
  41. builder: (context, constraints) {
  42. if (!constraints.hasBoundedWidth || !constraints.hasBoundedHeight) {
  43. return const SizedBox.shrink();
  44. }
  45. if (shortcuts.isEmpty) return const SizedBox.shrink();
  46. final dense = shortcuts.length > 8;
  47. final horizontalGap = dense
  48. ? (compact ? 10.0 : 14.0)
  49. : (compact ? 18.0 : 24.0);
  50. final rowGap = dense
  51. ? (compact ? 12.0 : 16.0)
  52. : (compact ? 18.0 : 26.0);
  53. final labelAllowance = compact ? 46.0 : 50.0;
  54. final maxTileSize = compact ? 168.0 : 196.0;
  55. var bestSize = 0.0;
  56. for (var columns = 2; columns <= 6; columns++) {
  57. if (columns > shortcuts.length) break;
  58. final rows = (shortcuts.length / columns).ceil();
  59. final widthLimited =
  60. (constraints.maxWidth - horizontalGap * (columns - 1)) / columns;
  61. final heightLimited =
  62. (constraints.maxHeight - rowGap * (rows - 1)) / rows -
  63. labelAllowance;
  64. final candidate = math
  65. .min(widthLimited, heightLimited)
  66. .clamp(0.0, maxTileSize);
  67. if (candidate > bestSize) bestSize = candidate;
  68. }
  69. final size = bestSize.clamp(64.0, maxTileSize);
  70. return Align(
  71. alignment: Alignment.bottomCenter,
  72. child: Wrap(
  73. alignment: WrapAlignment.center,
  74. crossAxisAlignment: WrapCrossAlignment.start,
  75. spacing: horizontalGap,
  76. runSpacing: rowGap,
  77. children: [
  78. for (final shortcut in shortcuts)
  79. ShortcutTile(
  80. title: shortcut.title,
  81. subtitle: shortcut.subtitle,
  82. iconAsset: shortcut.iconAsset,
  83. size: size,
  84. assigned: shortcut.assigned,
  85. onTap: shortcut.onTap,
  86. onLongPress: shortcut.onLongPress,
  87. longPressHint: shortcut.longPressHint,
  88. iconBytes: shortcut.iconBytes,
  89. showEqualizer: shortcut.showEqualizer,
  90. ),
  91. ],
  92. ),
  93. );
  94. },
  95. );
  96. }
  97. }