From 5a8b020256bce924632cfdd469108512f19c0908 Mon Sep 17 00:00:00 2001 From: efrilm Date: Thu, 1 Oct 2026 10:29:43 +0700 Subject: [PATCH] Home: samakan ukuran font label menu layanan Co-Authored-By: Claude Opus 5.5 --- .../pages/home/widgets/service_section.dart | 106 +++++++++++++----- 1 file changed, 76 insertions(+), 30 deletions(-) diff --git a/lib/presentation/pages/main/pages/home/widgets/service_section.dart b/lib/presentation/pages/main/pages/home/widgets/service_section.dart index 77e1e36..cac3d6c 100644 --- a/lib/presentation/pages/main/pages/home/widgets/service_section.dart +++ b/lib/presentation/pages/main/pages/home/widgets/service_section.dart @@ -1,3 +1,5 @@ +import 'dart:math' as math; + import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -98,39 +100,83 @@ class HomeServiceSection extends StatelessWidget { context.router.push(route); } + static const double _horizontalPadding = 16; + static const int _crossAxisCount = 4; + static const double _crossAxisSpacing = 8; + + /// Semua label memakai ukuran font yang sama: kalau label terpanjang tidak + /// muat di lebar sel, semuanya diperkecil bersama supaya tidak ada yang + /// kecil sendiri. + TextStyle _labelStyle(BuildContext context, double maxWidth) { + final style = DefaultTextStyle.of( + context, + ).style.merge(AppStyle.xs.copyWith(fontWeight: FontWeight.w600)); + final textScaler = MediaQuery.textScalerOf(context); + final textDirection = Directionality.of(context); + + final widest = _menus + .map((menu) { + final painter = TextPainter( + text: TextSpan(text: menu.label, style: style), + textDirection: textDirection, + textScaler: textScaler, + maxLines: 1, + )..layout(); + final width = painter.width; + painter.dispose(); + return width; + }) + .reduce(math.max); + + if (widest <= maxWidth) return style; + return style.copyWith(fontSize: style.fontSize! * maxWidth / widest); + } + @override Widget build(BuildContext context) { - return GridView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - padding: const EdgeInsets.fromLTRB(16, 32, 16, 16), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 4, - mainAxisSpacing: 12, - crossAxisSpacing: 8, - mainAxisExtent: 76, - ), - itemCount: _menus.length, - itemBuilder: (context, index) { - final menu = _menus[index]; - return InkWell( - borderRadius: BorderRadius.circular(AppValue.borderRadius), - onTap: () => _open(context, menu), - child: Column( - children: [ - const SizedBox(height: 4), - menu.icon.image(width: 44, height: 44, fit: BoxFit.contain), - const SizedBox(height: 6), - FittedBox( - fit: BoxFit.scaleDown, - child: Text( - menu.label, - maxLines: 1, - style: AppStyle.xs.copyWith(fontWeight: FontWeight.w600), - ), - ), - ], + return LayoutBuilder( + builder: (context, constraints) { + final itemWidth = + (constraints.maxWidth - + _horizontalPadding * 2 - + _crossAxisSpacing * (_crossAxisCount - 1)) / + _crossAxisCount; + final labelStyle = _labelStyle(context, itemWidth); + + return GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.fromLTRB( + _horizontalPadding, + 32, + _horizontalPadding, + 16, ), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: _crossAxisCount, + mainAxisSpacing: 12, + crossAxisSpacing: _crossAxisSpacing, + mainAxisExtent: 76, + ), + itemCount: _menus.length, + itemBuilder: (context, index) { + final menu = _menus[index]; + return InkWell( + borderRadius: BorderRadius.circular(AppValue.borderRadius), + onTap: () => _open(context, menu), + child: Column( + children: [ + const SizedBox(height: 4), + menu.icon.image(width: 44, height: 44, fit: BoxFit.contain), + const SizedBox(height: 6), + FittedBox( + fit: BoxFit.scaleDown, + child: Text(menu.label, maxLines: 1, style: labelStyle), + ), + ], + ), + ); + }, ); }, );