shortcut_grid.dart 2.9 KB

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