Dev #3

Merged
aefril merged 9 commits from dev into main 2026-10-01 06:09:11 +02:00
Showing only changes of commit 5a8b020256 - Show all commits
@@ -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),
),
],
),
);
},
);
},
);