Skip to content

Easy Stepper Not Respecting Fixed Width and Exceeding Container Boundaries on Android #56

@gabrielMorais21

Description

@gabrielMorais21

Hello, how are you? I'm facing an issue on Android with the Easy Stepper component. In some cases, the text is exceeding the container's boundaries. I created a container with 16 padding, and all other elements are respecting this configuration. However, even when setting a fixed width for the Easy Stepper, it doesn't comply and ends up taking 100% of the screen width.

code: `import 'package:flutter/material.dart';
import 'package:micro_app_wagon_locator/app/wagon_home/domain/entities/tran_entity.dart';
import 'package:micro_app_wagon_locator/app/wagon_home/presentation/widget/timeLine.dart';
import 'package:shared_dependencies/shared_dependencies.dart';
// ignore: depend_on_referenced_packages
import 'package:intl/intl.dart';

class TrainModal extends StatelessWidget {
final PinType pinType;
final VoidCallback onPressedDetails;
final VoidCallback onPressedCancel;
final DraggableScrollableController draggableScrollableController;
final TrainEntity? trainEntity;

const TrainModal({
super.key,
required this.pinType,
required this.onPressedCancel,
required this.trainEntity,
required this.onPressedDetails,
required this.draggableScrollableController,
});

@OverRide
Widget build(BuildContext context) {
return Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: TrainContent(
pinType: pinType,
onPressedDetails: onPressedDetails,
onPressedCancel: onPressedCancel,
trainEntity: trainEntity,
),
),
],
);
}
}

class TrainContent extends StatelessWidget {
final PinType pinType;
final VoidCallback onPressedDetails;
final VoidCallback onPressedCancel;
final TrainEntity? trainEntity;

const TrainContent({
super.key,
required this.pinType,
required this.onPressedDetails,
required this.onPressedCancel,
required this.trainEntity,
});

@OverRide
Widget build(BuildContext context) {
return Container(
height: pinType == PinType.normal ? 541 : 659,
width: double.infinity,
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(
topRight: Radius.circular(24),
topLeft: Radius.circular(24),
),
color: DsColors.greyBlue25,
boxShadow: [
BoxShadow(
color: const Color(0xFF1D29394D).withOpacity(0.3),
offset: const Offset(-3, -4),
blurRadius: 24,
),
],
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 16),
const DragIndicator(),
if (pinType != PinType.normal)
AlertSection(pinType: pinType, trainEntity: trainEntity),
LocationInfo(trainEntity: trainEntity),
const SizedBox(height: 16),
LastUpdateInfo(trainEntity: trainEntity),
const SizedBox(height: 16),
TrainDetails(trainEntity: trainEntity),
const SizedBox(height: 12),
TrainAttributes(trainEntity: trainEntity),
TimelineWidget(trainEntity: trainEntity!),
const SizedBox(height: 16),
ActionButtons(
onPressedDetails: onPressedDetails,
onPressedCancel: onPressedCancel,
),
],
),
),
);
}
}

class DragIndicator extends StatelessWidget {
const DragIndicator({super.key});

@OverRide
Widget build(BuildContext context) {
return Center(
child: Container(
width: 36,
height: 5,
decoration: BoxDecoration(
color: const Color(0XFFBEBFC0),
borderRadius: BorderRadius.circular(4),
),
),
);
}
}

class AlertSection extends StatelessWidget {
final PinType pinType;
final TrainEntity? trainEntity;

const AlertSection({
super.key,
required this.pinType,
required this.trainEntity,
});

@OverRide
Widget build(BuildContext context) {
final alertColor = pinType == PinType.broken
? const Color(0XFFC01048)
: DsColors.warning400;
final alertIcon = pinType == PinType.broken
? DsIconSource.alertIcon.iconPath
: DsIconSource.alertTriangle.iconPath;

return Padding(
  padding: const EdgeInsets.symmetric(vertical: 20),
  child: Container(
    padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 18),
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(8),
      border: Border(left: BorderSide(color: alertColor, width: 4)),
    ),
    child: Column(
      children: [
        Row(
          children: [
            SvgPicture.asset(
              'assets/images/$alertIcon',
              package: 'core_design_system',
              height: 12,
            ),
            const SizedBox(width: 6),
            Text(
              pinType == PinType.broken
                  ? trainEntity?.vehicleCondition ?? ''
                  : 'Ocorrência ferroviária',
              style: const TextStyle(
                fontFamily: 'Mission Gothic',
                fontWeight: FontWeight.w600,
                fontSize: 12,
                color: DsColors.greyBlue800,
              ),
            ),
          ],
        ),
        const SizedBox(height: 6),
        Text(
          pinType == PinType.occurrence
              ? trainEntity?.occurrence ?? 'Valor padrão'
              : trainEntity?.vehicleConditionDetail ?? 'Valor padrão',
          maxLines: 3,
          overflow: TextOverflow.ellipsis,
          style: const TextStyle(
            fontFamily: 'Roboto',
            fontWeight: FontWeight.w400,
            fontSize: 12,
            color: DsColors.greyBlue500,
          ),
        ),
      ],
    ),
  ),
);

}
}

class LocationInfo extends StatelessWidget {
final TrainEntity? trainEntity;

const LocationInfo({super.key, required this.trainEntity});

@OverRide
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Localização',
style: TextStyle(
fontFamily: 'Mission Gothic',
fontWeight: FontWeight.w600,
fontSize: 16,
color: DsColors.greyBlue900,
),
),
Container(
height: 22,
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: DsColors.primary50,
borderRadius: BorderRadius.circular(16),
),
child: IntrinsicWidth(
child: Center(
child: Text(
trainEntity?.transportStageName ?? '',
textAlign: TextAlign.center,
style: const TextStyle(
color: Color(0xFF1849A9),
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
),
),
),
],
);
}
}

class LastUpdateInfo extends StatelessWidget {
final TrainEntity? trainEntity;

const LastUpdateInfo({super.key, required this.trainEntity});

@OverRide
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
const Text(
'Última atualização realizada em:',
style: TextStyle(
fontFamily: 'Roboto',
fontWeight: FontWeight.w400,
fontStyle: FontStyle.italic,
fontSize: 9,
color: DsColors.greyBlue500,
),
),
Text(
trainEntity?.lastUpdateDate ?? "",
style: const TextStyle(
fontFamily: 'Roboto',
fontWeight: FontWeight.w400,
fontSize: 10,
color: DsColors.greyBlue700,
),
),
],
);
}
}

class TrainDetails extends StatelessWidget {
final TrainEntity? trainEntity;

const TrainDetails({super.key, required this.trainEntity});

@OverRide
Widget build(BuildContext context) {
return Row(
children: [
SvgPicture.asset(
'assets/images/${DsIconSource.train.iconPath}',
package: 'core_design_system',
height: 20,
),
const SizedBox(width: 4),
Text(
trainEntity?.locationName ?? "",
style: const TextStyle(
fontFamily: 'Roboto',
fontWeight: FontWeight.w500,
fontSize: 14,
color: DsColors.primary800,
),
),
],
);
}
}

class TrainAttributes extends StatelessWidget {
final TrainEntity? trainEntity;

const TrainAttributes({super.key, required this.trainEntity});

String _obterTextoPrevisaoEntrega(TrainEntity? trainEntity) {
String dataString = trainEntity?.estimatedDestinationEntryDate ?? '';
if (trainEntity?.transportStageName == "Fila terminal" &&
dataString != '') {
DateFormat dateFormat = DateFormat('dd/MM/yyyy HH:mm');

  DateTime dataConvertida = dateFormat.parse(dataString);

  DateTime dataAtual = DateTime.now();

  if (dataConvertida.isBefore(dataAtual)) {
    return 'Vagão em fila para entrega';
  }
}

return dataString;

}

@OverRide
Widget build(BuildContext context) {
final attributes = [
{'title': 'Cliente', 'subtitle': trainEntity?.clientName ?? ""},
{'title': 'Trem/Pátio', 'subtitle': trainEntity?.currentLocation ?? ""},
{
'title': 'Quantidade vagões',
'subtitle': trainEntity?.numberOfWagons.toString() ?? ""
},
{'title': 'Origem', 'subtitle': trainEntity?.origin ?? ""},
{'title': 'Destino', 'subtitle': trainEntity?.destination ?? ""},
{
'title': trainEntity?.transportStageName == 'Entregue'
? 'Hora da Entrega'
: 'Previsão chegada',
'subtitle': _obterTextoPrevisaoEntrega(trainEntity)
},
];

return GridView.count(
  padding: EdgeInsets.zero,
  crossAxisCount: 3,
  shrinkWrap: true,
  childAspectRatio: 3,
  physics: const NeverScrollableScrollPhysics(),
  children: attributes
      .map((attr) =>
          GridItem(title: attr['title']!, subtitle: attr['subtitle']!))
      .toList(),
);

}
}

class ActionButtons extends StatelessWidget {
final VoidCallback onPressedDetails;
final VoidCallback onPressedCancel;

const ActionButtons({
super.key,
required this.onPressedDetails,
required this.onPressedCancel,
});

@OverRide
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: DsColors.primary600,
minimumSize: const Size(double.infinity, 40),
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)),
),
onPressed: onPressedDetails,
child: const Text(
'Detalhar',
style: TextStyle(
fontFamily: 'Roboto',
fontWeight: FontWeight.w700,
fontSize: 14.0,
color: Colors.white,
),
),
),
const SizedBox(height: 16),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
minimumSize: const Size(double.infinity, 40),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
side: const BorderSide(color: DsColors.greyBlue300),
),
),
onPressed: onPressedCancel,
child: const Text(
'Fechar',
style: TextStyle(
fontFamily: 'Roboto',
fontWeight: FontWeight.w700,
fontSize: 14.0,
color: DsColors.greyBlue700,
),
),
),
],
);
}
}

class GridItem extends StatelessWidget {
final String title;
final String subtitle;

const GridItem({super.key, required this.title, required this.subtitle});

@OverRide
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontFamily: 'Roboto',
fontWeight: FontWeight.w500,
fontSize: 10,
color: DsColors.greyBlue700,
),
),
Text(
subtitle,
style: const TextStyle(
fontFamily: 'Roboto',
fontWeight: FontWeight.w400,
fontSize: 12,
color: DsColors.greyBlue900,
),
),
],
);
}
}
`

`import 'package:easy_stepper/easy_stepper.dart';
import 'package:flutter/material.dart';
import 'package:micro_app_wagon_locator/app/wagon_home/domain/entities/tran_entity.dart';
import 'package:micro_app_wagon_locator/micro_app_wagon_locator.dart';
import 'package:shared_dependencies/shared_dependencies.dart' hide LineStyle;

// Widget principal da Timeline
class TimelineWidget extends StatelessWidget {
final TrainEntity trainEntity;

const TimelineWidget({Key? key, required this.trainEntity}) : super(key: key);

@OverRide
Widget build(BuildContext context) {
int activeStep = _determineActiveStep(trainEntity.transportStageName);

return EasyStepper(
  lineStyle: const LineStyle(
    lineSpace: 4,
    lineLength: 110,
    lineWidth: 20,
    lineType: LineType.normal,
    progress: 0.5,
    defaultLineColor: DsColors.greyBlue300,
    finishedLineColor: DsColors.primary700,
    progressColor: DsColors.primary700,
  ),
  activeStep: activeStep,
  activeStepTextColor: Colors.black87,
  finishedStepTextColor: Colors.black87,
  internalPadding: 0,
  showLoadingAnimation: false,
  stepRadius: 8,
  showStepBorder: false,
  activeStepBorderType: BorderType.normal,
  activeStepBackgroundColor: Colors.transparent,
  finishedStepBorderType: BorderType.normal,
  finishedStepBackgroundColor: Colors.transparent,
  unreachedStepBorderType: BorderType.normal,
  unreachedStepBackgroundColor: Colors.transparent,
  steps: [
    TimelineStep(
      isActive: activeStep == 0,
      checkStep: activeStep > 0,
      title: trainEntity.originEntryDate ?? "",
      subtitle: _formatSubTitle(
        trainEntity.origin,
        trainEntity.originCityName,
      ),
    ),
    TimelineStep(
      isActive: activeStep == 1,
      checkStep: activeStep > 1,
      title: trainEntity.currentLocationEntryDate ?? '',
      subtitle: trainEntity.currentCityName ?? "",
    ),
    TimelineStep(
      isActive: activeStep == 2,
      checkStep: activeStep >= 2,
      title: trainEntity.estimatedDestinationEntryDate ?? "",
      subtitle: _formatSubTitle(
        trainEntity.destination,
        trainEntity.destinationCityName,
      ),
      transportStageName: trainEntity.transportStageName,
    ),
  ],
);

}

int _determineActiveStep(String? stageName) {
switch (stageName) {
case "Aguardando circulação":
return 0;
case "Circulando":
return 1;
case "Fila terminal":
case "Entregue":
return 2;
default:
return 0;
}
}

String _formatSubTitle(String? terminal, String? cidade) {
if (terminal != null && terminal.isNotEmpty) {
return terminal;
}
if (cidade != null && cidade.isNotEmpty) {
return cidade;
}
return '';
}
}

class TimelineStep extends EasyStep {
final bool isActive;
final bool checkStep;
final String title;
final String subtitle;
final String? transportStageName;

TimelineStep({
required this.isActive,
required this.checkStep,
required this.title,
required this.subtitle,
this.transportStageName,
}) : super(
customStep: transportStageName != null
? CheckStage(transportStageName: transportStageName!)
: CircleWidget(activeStep: isActive, checkStep: checkStep),
customTitle: StepText(
isActive: isActive,
title: title,
subtitle: subtitle,
),
);
}

class CheckStage extends StatelessWidget {
final String transportStageName;

const CheckStage({Key? key, required this.transportStageName})
: super(key: key);

@OverRide
Widget build(BuildContext context) {
if (transportStageName == "Entregue") {
return CircleWidget(activeStep: true, checkStep: true);
}
if (transportStageName == "Fila terminal") {
return CircleWidget(activeStep: false, checkStep: true);
}
return CircleWidget(activeStep: false, checkStep: false);
}
}

class CircleWidget extends StatelessWidget {
final bool activeStep;
final bool checkStep;

const CircleWidget(
{Key? key, required this.activeStep, required this.checkStep})
: super(key: key);

@OverRide
Widget build(BuildContext context) {
return Container(
width: activeStep ? 24 : 12,
height: activeStep ? 24 : 12,
decoration: BoxDecoration(
color: activeStep
? const Color(0xFF175CD3)
: (checkStep ? const Color(0xFF175CD3) : DsColors.greyBlue300),
shape: BoxShape.circle,
border: activeStep
? Border.all(
color: const Color(0xFF1849A9),
width: 2,
)
: Border.all(
color: Colors.white,
width: 2,
),
),
child: activeStep
? SvgPicture.asset(
'assets/leading_icon.svg',
package: MicroAppWagonlocator.microAppName,
colorFilter: const ColorFilter.mode(
Colors.white,
BlendMode.srcIn,
),
)
: null,
);
}
}

class StepText extends StatelessWidget {
final String title;
final String subtitle;
final bool isActive;

const StepText({
Key? key,
required this.title,
required this.subtitle,
required this.isActive,
}) : super(key: key);

@OverRide
Widget build(BuildContext context) {
return Column(
children: [
Text(
isActive ? 'Localização atual' : title,
style: TextStyle(
fontFamily: 'Roboto',
fontWeight: isActive ? FontWeight.w700 : FontWeight.w500,
fontSize: 10,
color: isActive ? const Color(0xFF1570EF) : const Color(0xFF667085),
),
textAlign: TextAlign.center,
),
const SizedBox(height: 4),
Text(
subtitle,
style: TextStyle(
fontFamily: 'Roboto',
fontWeight: FontWeight.w400,
fontSize: 10,
color: isActive ? const Color(0xFF175CD3) : const Color(0xFF344054),
),
textAlign: TextAlign.center,
),
],
);
}
}
`

befcf64f-e94c-4809-9b71-9979958bdabe

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions