import 'package:flutter/widgets.dart'; /// 데스크톱 레이아웃으로 간주할 최소 너비(px). const double desktopBreakpoint = 1200; /// 태블릿 레이아웃을 구분하는 최소 너비(px). const double tabletBreakpoint = 960; /// 뷰포트 크기별 분기값. enum DeviceBreakpoint { mobile, tablet, desktop } /// 현재 화면 너비에 맞는 [DeviceBreakpoint]를 계산한다. DeviceBreakpoint breakpointForWidth(double width) { if (width >= desktopBreakpoint) { return DeviceBreakpoint.desktop; } if (width >= tabletBreakpoint) { return DeviceBreakpoint.tablet; } return DeviceBreakpoint.mobile; } /// 주어진 너비가 데스크톱 분기에 해당하는지 여부. bool isDesktop(double width) => width >= desktopBreakpoint; /// 주어진 너비가 태블릿 분기에 해당하는지 여부. bool isTablet(double width) => width >= tabletBreakpoint && width < desktopBreakpoint; /// 주어진 너비가 모바일 분기에 해당하는지 여부. bool isMobile(double width) => width < tabletBreakpoint; /// 컨텍스트 기반으로 데스크톱 범위인지 확인한다. bool isDesktopContext(BuildContext context) => isDesktop(MediaQuery.of(context).size.width); /// 컨텍스트 기반으로 태블릿 범위인지 확인한다. bool isTabletContext(BuildContext context) => isTablet(MediaQuery.of(context).size.width); /// 컨텍스트 기반으로 모바일 범위인지 확인한다. bool isMobileContext(BuildContext context) => isMobile(MediaQuery.of(context).size.width); /// 반응형 분기 정보를 담는 값 객체. class ResponsiveBreakpoints { ResponsiveBreakpoints._(this.width) : breakpoint = breakpointForWidth(width); /// 현재 뷰 가로 너비. final double width; /// 너비에서 계산된 분기값. final DeviceBreakpoint breakpoint; /// 모바일 범위인지 여부. bool get isMobile => breakpoint == DeviceBreakpoint.mobile; /// 태블릿 범위인지 여부. bool get isTablet => breakpoint == DeviceBreakpoint.tablet; /// 데스크톱 범위인지 여부. bool get isDesktop => breakpoint == DeviceBreakpoint.desktop; /// 현재 컨텍스트에서 [ResponsiveBreakpoints]를 생성한다. static ResponsiveBreakpoints of(BuildContext context) { final size = MediaQuery.of(context).size; return ResponsiveBreakpoints._(size.width); } } /// 기기 타입에 따라 위젯 빌더를 분기 실행하는 레이아웃 헬퍼. class ResponsiveLayoutBuilder extends StatelessWidget { const ResponsiveLayoutBuilder({ super.key, required this.mobile, this.tablet, required this.desktop, }); /// 모바일 뷰에서 사용할 빌더. final WidgetBuilder mobile; /// 태블릿 뷰에서 사용할 빌더. 제공되지 않으면 데스크톱 빌더를 재사용한다. final WidgetBuilder? tablet; /// 데스크톱 뷰에서 사용할 빌더. final WidgetBuilder desktop; @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { final breakpoint = breakpointForWidth(constraints.maxWidth); switch (breakpoint) { case DeviceBreakpoint.mobile: return mobile(context); case DeviceBreakpoint.tablet: final tabletBuilder = tablet ?? desktop; return tabletBuilder(context); case DeviceBreakpoint.desktop: return desktop(context); } }, ); } } /// 특정 분기에서만 child를 표시하는 헬퍼 위젯. class ResponsiveVisibility extends StatelessWidget { const ResponsiveVisibility({ super.key, required this.child, this.replacement = const SizedBox.shrink(), this.visibleOn = const { DeviceBreakpoint.mobile, DeviceBreakpoint.tablet, DeviceBreakpoint.desktop, }, }); /// 조건을 만족할 때 보여줄 실제 위젯. final Widget child; /// 조건을 만족하지 않을 때 대체로 렌더링할 위젯. final Widget replacement; /// 어떤 분기에서 child를 노출할지 정의한 집합. final Set visibleOn; @override Widget build(BuildContext context) { final breakpoint = ResponsiveBreakpoints.of(context).breakpoint; return visibleOn.contains(breakpoint) ? child : replacement; } }