From e4030d76b27d1f28cb1d3b2bf29d37b1755d8421 Mon Sep 17 00:00:00 2001 From: Pedro Paulo de Amorim Date: Fri, 6 Jun 2025 00:16:15 +0100 Subject: [PATCH] Restore example folder, add WASM compatibility, fix linter errors, improve code documentation --- .pubignore | 1 - lib/responsive_builder2.dart | 8 ++ lib/src/device_screen_type.dart | 47 ++++++++++++ lib/src/helpers/device_width.dart | 11 +++ lib/src/helpers/device_width_web.dart | 3 - lib/src/helpers/helpers.dart | 92 +++++++++++++++++++---- lib/src/responsive_sizing_config.dart | 81 ++++++++++++++++++-- lib/src/responsive_wrapper.dart | 69 +++++++++++++---- lib/src/scroll/scroll_transform_item.dart | 34 +++++++++ lib/src/scroll/scroll_transform_view.dart | 29 +++++++ lib/src/sizing_information.dart | 79 ++++++++++++++++--- lib/src/widget_builders.dart | 73 +++++++++++------- pubspec.yaml | 1 + 13 files changed, 453 insertions(+), 75 deletions(-) delete mode 100644 lib/src/helpers/device_width_web.dart diff --git a/.pubignore b/.pubignore index b922e0e..dc3d511 100644 --- a/.pubignore +++ b/.pubignore @@ -1,5 +1,4 @@ build/ -example/ coverage/ .github/ .vscode/ diff --git a/lib/responsive_builder2.dart b/lib/responsive_builder2.dart index 33f0a54..68ff227 100644 --- a/lib/responsive_builder2.dart +++ b/lib/responsive_builder2.dart @@ -1,3 +1,11 @@ +/// The responsive_builder2 library provides widgets and utilities for building +/// responsive UIs in Flutter. +/// +/// This package allows you to easily adapt your UI to different screen sizes +/// and device types (watch, phone, tablet, desktop). +/// It includes builder widgets such as [ResponsiveBuilder] and +/// [ScreenTypeLayout.builder2] that provide detailed sizing information, +/// as well as helpers for custom breakpoints and refined size categories. library responsive_builder2; export 'src/device_screen_type.dart'; diff --git a/lib/src/device_screen_type.dart b/lib/src/device_screen_type.dart index 365126e..fc8e3d9 100644 --- a/lib/src/device_screen_type.dart +++ b/lib/src/device_screen_type.dart @@ -1,45 +1,92 @@ +/// Represents the type of device screen, used for responsive layout decisions. +/// +/// The enum values are ordered by increasing screen size: +/// - [watch]: Smallest screens, such as smartwatches. +/// - [phone]: Phones and small mobile devices. +/// - [tablet]: Tablets and medium-sized devices. +/// - [desktop]: Large screens, such as desktops and laptops. +/// +/// Deprecated values (capitalized) are kept for backward compatibility. enum DeviceScreenType { + /// Deprecated: Use [watch] instead. @Deprecated('Use lowercase version') Watch(0), + + /// Deprecated: Use [phone] instead. @Deprecated('Use lowercase or phoneversion') Mobile(1), + + /// Deprecated: Use [tablet] instead. @Deprecated('Use lowercase version') Tablet(2), + + /// Deprecated: Use [desktop] instead. @Deprecated('Use lowercase version') Desktop(3), + /// Deprecated: Use [phone] instead. @Deprecated('Use phone version') mobile(1), + /// Smallest screens, such as smartwatches. watch(0), + + /// Phones and small mobile devices. phone(1), + + /// Tablets and medium-sized devices. tablet(2), + + /// Large screens, such as desktops and laptops. desktop(3); + /// The ordinal value representing the order of the device type. const DeviceScreenType(this.ordinal); final int ordinal; + /// Returns true if this device type is greater (larger) than [other]. bool operator >(DeviceScreenType other) => ordinal > other.ordinal; + /// Returns true if this device type is greater than or equal to [other]. bool operator >=(DeviceScreenType other) => ordinal >= other.ordinal; + /// Returns true if this device type is less (smaller) than [other]. bool operator <(DeviceScreenType other) => ordinal < other.ordinal; + /// Returns true if this device type is less than or equal to [other]. bool operator <=(DeviceScreenType other) => ordinal <= other.ordinal; } +/// Represents a more granular size classification for responsive layouts. +/// +/// The enum values are ordered by increasing size: +/// - [small]: Smallest refined size. +/// - [normal]: Default or typical size. +/// - [large]: Larger than normal. +/// - [extraLarge]: Largest refined size. enum RefinedSize { + /// Smallest refined size. small, + + /// Default or typical size. normal, + + /// Larger than normal. large, + + /// Largest refined size. extraLarge; + /// Returns true if this refined size is greater (larger) than [other]. bool operator >(RefinedSize other) => index > other.index; + /// Returns true if this refined size is greater than or equal to [other]. bool operator >=(RefinedSize other) => index >= other.index; + /// Returns true if this refined size is less (smaller) than [other]. bool operator <(RefinedSize other) => index < other.index; + /// Returns true if this refined size is less than or equal to [other]. bool operator <=(RefinedSize other) => index <= other.index; } diff --git a/lib/src/helpers/device_width.dart b/lib/src/helpers/device_width.dart index d6c9575..94244a2 100644 --- a/lib/src/helpers/device_width.dart +++ b/lib/src/helpers/device_width.dart @@ -1,4 +1,15 @@ import 'dart:ui'; +/// Calculates the effective device width based on the platform and screen size. +/// +/// For web or desktop platforms, returns the full width of the screen. +/// For mobile platforms, returns the shortest side of the screen +/// (width or height). +/// +/// Parameters: +/// * [size]: The physical size of the screen +/// * [isWebOrDesktop]: Whether the app is running on web or desktop platform +/// +/// Returns the effective width in logical pixels. double deviceWidth(Size size, bool isWebOrDesktop) => isWebOrDesktop ? size.width : size.shortestSide; diff --git a/lib/src/helpers/device_width_web.dart b/lib/src/helpers/device_width_web.dart deleted file mode 100644 index 98e578c..0000000 --- a/lib/src/helpers/device_width_web.dart +++ /dev/null @@ -1,3 +0,0 @@ -import 'dart:ui'; - -double deviceWidth(Size size) => size.width; diff --git a/lib/src/helpers/helpers.dart b/lib/src/helpers/helpers.dart index c538cb9..c9df8cc 100644 --- a/lib/src/helpers/helpers.dart +++ b/lib/src/helpers/helpers.dart @@ -1,17 +1,30 @@ -import 'dart:io'; +// Removed import 'dart:io' for WASM compatibility. import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:responsive_builder2/responsive_builder2.dart'; +import 'package:universal_platform/universal_platform.dart'; -// Author: https://github.com/fastogt/responsive_builder/tree/master -import 'device_width.dart' if (dart.library.js_interop) 'device_width_web.dart' - as width; +import 'device_width.dart' as width; -final _isWebOrDesktop = - kIsWeb || Platform.isWindows || Platform.isLinux || Platform.isMacOS; +/// Determines if the current platform is web or desktop (WASM compatible) +final _isWebOrDesktop = kIsWeb || + UniversalPlatform.isWindows || + UniversalPlatform.isLinux || + UniversalPlatform.isMacOS; /// Returns the [DeviceScreenType] that the application is currently running on +/// +/// This function determines the device type based on the screen size and +/// optional breakpoints. +/// +/// Parameters: +/// * [size] - The current screen size +/// * [breakpoint] - Optional custom breakpoints to override defaults +/// * [isWebOrDesktop] - Optional flag to force web/desktop behavior +/// +/// Returns [DeviceScreenType] representing the current device type (desktop, +/// tablet, phone, or watch) DeviceScreenType getDeviceType(Size size, [ScreenBreakpoints? breakpoint = null, bool? isWebOrDesktop]) { isWebOrDesktop = isWebOrDesktop ??= _isWebOrDesktop; @@ -39,14 +52,28 @@ DeviceScreenType getDeviceType(Size size, return DeviceScreenType.phone; } -// coverage:ignore-start +/// Helper function to determine if a large screen should be treated as desktop +/// or tablet +/// +/// Returns [DeviceScreenType.desktop] if [isWebOrDesktop] is true, otherwise +/// returns [DeviceScreenType.tablet] DeviceScreenType _desktopOrTablet(bool? isWebOrDesktop) => (isWebOrDesktop ?? _isWebOrDesktop) ? DeviceScreenType.desktop : DeviceScreenType.tablet; -// coverage:ignore-end -/// Returns the [RefindedSize] for each device that the application is currently running on +/// Returns the [RefinedSize] for the current device based on screen dimensions +/// +/// This function provides more granular size categories (extraLarge, large, +/// normal, small) +/// within each device type (desktop, tablet, phone, watch). +/// +/// Parameters: +/// * [size] - The current screen size +/// * [refinedBreakpoint] - Optional custom breakpoints for refined sizes +/// * [isWebOrDesktop] - Optional flag to force web/desktop behavior +/// +/// Returns [RefinedSize] representing the current refined size category RefinedSize getRefinedSize( Size size, { RefinedBreakpoints? refinedBreakpoint, @@ -160,7 +187,22 @@ RefinedSize getRefinedSize( return RefinedSize.small; } -/// Will return one of the values passed in for the device it's running on +/// Returns a value based on the current device screen type +/// +/// This function selects the appropriate value from the provided options based +/// on the current device type (desktop, tablet, phone, watch). It follows a +/// fallback pattern where if a value for the current device type isn't provided, +/// it will use the next best option. +/// +/// Parameters: +/// * [context] - The build context +/// * [isWebOrDesktop] - Optional flag to force web/desktop behavior +/// * [mobile] - Required value for mobile devices +/// * [tablet] - Optional value for tablet devices +/// * [desktop] - Optional value for desktop devices +/// * [watch] - Optional value for watch devices +/// +/// Returns the appropriate value for the current device type T getValueForScreenType({ required BuildContext context, bool? isWebOrDesktop, @@ -175,7 +217,8 @@ T getValueForScreenType({ if (deviceScreenType == DeviceScreenType.desktop) { // If we have supplied the desktop layout then display that if (desktop != null) return desktop; - // If no desktop layout is supplied we want to check if we have the size below it and display that + // If no desktop layout is supplied we want to check if we have the size + // below it and display that if (tablet != null) return tablet; } @@ -191,12 +234,28 @@ T getValueForScreenType({ if (mobile != null) return mobile; } - // If none of the layouts above are supplied we use the prefered layout based on the flag + // If none of the layouts above are supplied we use the prefered layout based + // on the flag final buildDesktopLayout = ResponsiveAppUtil.preferDesktop && desktop != null; return buildDesktopLayout ? desktop : mobile; } +/// Returns a value based on the current refined screen size +/// +/// This function selects the appropriate value from the provided options based +/// on the current refined size (extraLarge, large, normal, small). It follows a +/// fallback pattern where if a value for the current size isn't provided, it +/// will use the next best option. +/// +/// Parameters: +/// * [context] - The build context +/// * [small] - Optional value for small screens +/// * [normal] - Required value for normal screens +/// * [large] - Optional value for large screens +/// * [extraLarge] - Optional value for extra large screens +/// +/// Returns the appropriate value for the current refined size /// Will return one of the values passed in for the refined size T getValueForRefinedSize({ required BuildContext context, @@ -210,14 +269,16 @@ T getValueForRefinedSize({ if (refinedSize == RefinedSize.extraLarge) { // If we have supplied the extra large layout then display that if (extraLarge != null) return extraLarge; - // If no extra large layout is supplied we want to check if we have the size below it and display that + // If no extra large layout is supplied we want to check if we have the + // size below it and display that if (large != null) return large; } if (refinedSize == RefinedSize.large) { // If we have supplied the large layout then display that if (large != null) return large; - // If no large layout is supplied we want to check if we have the size below it and display that + // If no large layout is supplied we want to check if we have the size + // below it and display that if (normal != null) return normal; } @@ -232,7 +293,8 @@ T getValueForRefinedSize({ if (small != null) return small; } - // If none of the layouts above are supplied or we're on the normal size layout then we show the normal layout + // If none of the layouts above are supplied or we're on the normal size + // layout then we show the normal layout return normal; } diff --git a/lib/src/responsive_sizing_config.dart b/lib/src/responsive_sizing_config.dart index ca628fa..fddce84 100644 --- a/lib/src/responsive_sizing_config.dart +++ b/lib/src/responsive_sizing_config.dart @@ -1,25 +1,81 @@ import 'package:responsive_builder2/responsive_builder2.dart'; -/// Keeps the configuration that will determines the breakpoints for different device sizes +/// A singleton configuration class that manages breakpoints for responsive +/// layouts. +/// +/// This class provides a centralized way to configure and access breakpoints +/// used for determining device types (mobile, tablet, desktop) and refined size +/// categories (small, normal, large, extraLarge). It uses the singleton pattern +/// to ensure consistent breakpoint values throughout the app. +/// +/// Example: +/// ```dart +/// // Configure custom breakpoints +/// ResponsiveSizingConfig.instance.setCustomBreakpoints( +/// ScreenBreakpoints(small: 320, large: 768), +/// customRefinedBreakpoints: RefinedBreakpoints( +/// mobileSmall: 320, +/// mobileNormal: 375, +/// // ... other breakpoints +/// ), +/// ); +/// +/// // Access breakpoints +/// final breakpoints = ResponsiveSizingConfig.instance.breakpoints; +/// final rb = ResponsiveSizingConfig.instance.refinedBreakpoints; +/// ``` class ResponsiveSizingConfig { static ResponsiveSizingConfig? _instance; + + /// Returns the singleton instance of [ResponsiveSizingConfig]. + /// + /// Creates a new instance if one doesn't exist. static ResponsiveSizingConfig get instance { if (_instance == null) { _instance = ResponsiveSizingConfig(); } - return _instance!; } - static const ScreenBreakpoints _defaultBreakPoints = const ScreenBreakpoints( + /// Default breakpoints for determining device types. + /// + /// These values are used when no custom breakpoints are set: + /// * [large]: 600 logical pixels - devices above this width are considered + /// tablets/desktops + /// * [small]: 300 logical pixels - devices below this width are considered + /// mobile + static const ScreenBreakpoints _defaultBreakPoints = ScreenBreakpoints( large: 600, small: 300, ); + /// Custom breakpoints that override the defaults when set. ScreenBreakpoints? _customBreakPoints; + /// Default breakpoints for refined size categories. + /// + /// These values define the boundaries for different size categories across + /// device types. All values are in logical pixels: + /// + /// Desktop breakpoints: + /// * [desktopExtraLarge]: 4096px - 4K displays + /// * [desktopLarge]: 3840px - Large desktop displays + /// * [desktopNormal]: 1920px - Standard desktop displays + /// * [desktopSmall]: 950px - Small desktop displays + /// + /// Tablet breakpoints: + /// * [tabletExtraLarge]: 900px - Large tablets + /// * [tabletLarge]: 850px - Standard tablets + /// * [tabletNormal]: 768px - Small tablets + /// * [tabletSmall]: 600px - Mini tablets + /// + /// Mobile breakpoints: + /// * [mobileExtraLarge]: 480px - Large phones + /// * [mobileLarge]: 414px - Standard phones + /// * [mobileNormal]: 375px - Small phones + /// * [mobileSmall]: 320px - Mini phones static const RefinedBreakpoints _defaultRefinedBreakPoints = - const RefinedBreakpoints( + RefinedBreakpoints( // Desktop desktopExtraLarge: 4096, desktopLarge: 3840, @@ -37,9 +93,18 @@ class ResponsiveSizingConfig { mobileSmall: 320, ); + /// Custom refined breakpoints that override the defaults when set. RefinedBreakpoints? _customRefinedBreakPoints; - /// Set the breakPoints which will then be returned through the [breakpoints] + /// Sets custom breakpoints for device types and refined size categories. + /// + /// Parameters: + /// * [customBreakpoints]: Optional custom breakpoints for device types + /// * [customRefinedBreakpoints]: Optional custom breakpoints for refined + /// size categories + /// + /// If either parameter is null, the corresponding default breakpoints will + /// be used. void setCustomBreakpoints( ScreenBreakpoints? customBreakpoints, { RefinedBreakpoints? customRefinedBreakpoints, @@ -50,9 +115,15 @@ class ResponsiveSizingConfig { } } + /// Returns the current breakpoints for device types. + /// + /// Returns custom breakpoints if set, otherwise returns default breakpoints. ScreenBreakpoints get breakpoints => _customBreakPoints ?? _defaultBreakPoints; + /// Returns the current breakpoints for refined size categories. + /// + /// Returns custom breakpoints if set, otherwise returns default breakpoints. RefinedBreakpoints get refinedBreakpoints => _customRefinedBreakPoints ?? _defaultRefinedBreakPoints; } diff --git a/lib/src/responsive_wrapper.dart b/lib/src/responsive_wrapper.dart index e3d552d..8f563be 100644 --- a/lib/src/responsive_wrapper.dart +++ b/lib/src/responsive_wrapper.dart @@ -1,15 +1,32 @@ import 'package:flutter/widgets.dart'; -/// Wrap your app with this widget if you want to use the responsive sizing extension +/// A widget that wraps your app to enable responsive sizing functionality. +/// +/// This widget provides the foundation for responsive design by tracking +/// screen dimensions and orientation. It should be placed at the root of your +/// widget tree to enable the responsive sizing extensions throughout your app. +/// +/// Example: +/// ```dart +/// ResponsiveApp( +/// builder: (context) => MyApp(), +/// preferDesktop: false, +/// ) +/// ``` class ResponsiveApp extends StatelessWidget { + /// The builder function that creates the main widget tree final Widget Function(BuildContext) builder; - /// Tells ResponsiveApp if we prefer desktop or mobile when a layout is not supplied + /// Controls the default layout preference when a specific layout is not + /// provided. When true, desktop layouts will be preferred over mobile + /// layouts. final bool preferDesktop; - const ResponsiveApp( - {Key? key, required this.builder, this.preferDesktop = false}) - : super(key: key); + const ResponsiveApp({ + Key? key, + required this.builder, + this.preferDesktop = false, + }) : super(key: key); @override Widget build(BuildContext context) { @@ -23,29 +40,55 @@ class ResponsiveApp extends StatelessWidget { } } +/// Extension methods for numeric values to calculate responsive dimensions. +/// +/// These extensions provide convenient ways to calculate dimensions as +/// percentages of the screen size. They are useful for creating layouts that +/// scale proportionally with the screen size. extension ResponsiveAppExtensions on num { - /// Returns the pecentage of screen height based on the extended number - /// Example: 20.screenHeight = (20 / 100) * currentScreenHeight + /// Calculates a percentage of the screen height. + /// + /// Returns the value as a percentage of the current screen height. + /// For example, `20.screenHeight` returns 20% of the screen height. double get screenHeight => (this / 100) * ResponsiveAppUtil.height; - /// Returns the pecentage of screen width based on the extended number - /// Example: 20.screenHeight = (20 / 100) * currentScreenHeight + /// Calculates a percentage of the screen width. + /// + /// Returns the value as a percentage of the current screen width. + /// For example, `20.screenWidth` returns 20% of the screen width. double get screenWidth => (this / 100) * ResponsiveAppUtil.width; - /// Shorthand for [screenHeight] + /// Shorthand alias for [screenHeight] double get sh => screenHeight; - /// Shorthand for [screenWidth] + /// Shorthand alias for [screenWidth] double get sw => screenWidth; } -// TODO: Replace ResponsiveAppUtil with ValueNotifier +/// Utility class for managing responsive app dimensions and preferences. +/// +/// This class maintains the current screen dimensions and layout preferences +/// for use throughout the app. It is used internally by the responsive +/// extensions and should not be accessed directly in most cases. +/// +/// TODO: Replace with ValueNotifier for better state management class ResponsiveAppUtil { + /// The current screen height in logical pixels static late double height; + + /// The current screen width in logical pixels static late double width; + + /// Whether to prefer desktop layouts over mobile layouts static bool preferDesktop = false; - /// Saves the screenSzie for access through the extensions later + /// Updates the stored screen dimensions based on the current constraints + /// and orientation. + /// + /// This method should be called whenever the screen size or orientation + /// changes. + /// It handles the swapping of width and height values when in landscape + /// orientation. static void setScreenSize( BoxConstraints constraints, Orientation orientation, diff --git a/lib/src/scroll/scroll_transform_item.dart b/lib/src/scroll/scroll_transform_item.dart index c32112c..4c00d8a 100644 --- a/lib/src/scroll/scroll_transform_item.dart +++ b/lib/src/scroll/scroll_transform_item.dart @@ -1,11 +1,45 @@ import 'package:flutter/widgets.dart'; import 'package:provider/provider.dart'; +/// A widget that applies scroll-based transformations to its child. +/// +/// This widget allows you to create dynamic scroll effects by transforming +/// (scaling and/or translating) its child based on the scroll offset. It uses +/// a [ScrollController] from the widget tree to track scroll position. +/// +/// Example: +/// ```dart +/// ScrollTransformItem( +/// offsetBuilder: (offset) => Offset(0, offset * 0.5), +/// scaleBuilder: (offset) => 1.0 - (offset * 0.001), +/// builder: (offset) => MyWidget(), +/// ) +/// ``` class ScrollTransformItem extends StatelessWidget { + /// Optional function that calculates the translation offset based on scroll + /// position. + /// + /// If not provided, no translation will be applied. final Offset Function(double scrollOffset)? offsetBuilder; + + /// Optional function that calculates the scale factor based on scroll + /// position. + /// + /// If not provided, no scaling will be applied (scale = 1.0). final double Function(double scrollOffset)? scaleBuilder; + + /// Required function that builds the child widget based on scroll position. final Widget Function(double scrollOffset) builder; + + /// Whether to log the current scroll offset to the console. + /// + /// Useful for debugging scroll effects. final bool logOffset; + + /// Creates a new [ScrollTransformItem]. + /// + /// The [builder] parameter is required and must not be null. + /// [offsetBuilder] and [scaleBuilder] are optional. const ScrollTransformItem({ Key? key, required this.builder, diff --git a/lib/src/scroll/scroll_transform_view.dart b/lib/src/scroll/scroll_transform_view.dart index c7a4d70..6a51fee 100644 --- a/lib/src/scroll/scroll_transform_view.dart +++ b/lib/src/scroll/scroll_transform_view.dart @@ -2,8 +2,36 @@ import 'package:flutter/widgets.dart'; import 'package:provider/provider.dart'; import 'package:responsive_builder2/src/scroll/scroll_transform_item.dart'; +/// A widget that creates a scrollable view with transformable children. +/// +/// This widget provides a scrollable container that enables scroll-based +/// transformations on its children. It uses a [ScrollController] to track +/// scroll position and provides it to child [ScrollTransformItem] widgets +/// through a [ChangeNotifierProvider]. +/// +/// Example: +/// ```dart +/// ScrollTransformView( +/// children: [ +/// ScrollTransformItem( +/// offsetBuilder: (offset) => Offset(0, offset * 0.5), +/// builder: (offset) => MyWidget(), +/// ), +/// // More transform items... +/// ], +/// ) +/// ``` class ScrollTransformView extends StatefulWidget { + /// The list of [ScrollTransformItem] widgets to be displayed in the scroll + /// view. + /// + /// Each child will receive scroll position updates and can apply its own + /// transformations based on the scroll offset. final List children; + + /// Creates a new [ScrollTransformView]. + /// + /// The [children] parameter is required and must not be null. const ScrollTransformView({ Key? key, required this.children, @@ -14,6 +42,7 @@ class ScrollTransformView extends StatefulWidget { } class _ScrollTransformViewState extends State { + /// The controller that manages scroll position and notifies listeners. final scrollController = ScrollController(); @override diff --git a/lib/src/sizing_information.dart b/lib/src/sizing_information.dart index 3782558..6220bc8 100644 --- a/lib/src/sizing_information.dart +++ b/lib/src/sizing_information.dart @@ -1,38 +1,63 @@ import 'package:flutter/widgets.dart'; import 'device_screen_type.dart'; -/// Contains sizing information to make responsive choices for the current screen +/// Contains sizing information to make responsive choices for the current +/// screen. +/// +/// This class provides information about the current device's screen +/// characteristics including device type (watch, phone, tablet, desktop), +/// refined size categories, and both screen and local widget dimensions. +/// Use this information to make responsive layout decisions in your app. class SizingInformation { + /// The type of device screen (watch, phone, tablet, desktop) final DeviceScreenType deviceScreenType; + + /// The refined size category (small, normal, large, extraLarge) final RefinedSize refinedSize; + + /// The total screen dimensions final Size screenSize; + + /// The dimensions of the local widget's constraints final Size localWidgetSize; + /// Returns true if the device is a watch bool get isWatch => deviceScreenType == DeviceScreenType.watch; @Deprecated('Use isPhone instead') bool get isMobile => isPhone; + /// Returns true if the device is a phone bool get isPhone => - deviceScreenType == DeviceScreenType.phone - || + deviceScreenType == DeviceScreenType.phone || // ignore: deprecated_member_use_from_same_package deviceScreenType == DeviceScreenType.mobile; + /// Returns true if the device is a tablet bool get isTablet => deviceScreenType == DeviceScreenType.tablet; + /// Returns true if the device is a desktop bool get isDesktop => deviceScreenType == DeviceScreenType.desktop; - // Refined - + /// Returns true if the refined size is small bool get isSmall => refinedSize == RefinedSize.small; + /// Returns true if the refined size is normal bool get isNormal => refinedSize == RefinedSize.normal; + /// Returns true if the refined size is large bool get isLarge => refinedSize == RefinedSize.large; + /// Returns true if the refined size is extra large bool get isExtraLarge => refinedSize == RefinedSize.extraLarge; + /// Creates a new [SizingInformation] instance. + /// + /// All parameters are required: + /// * [deviceScreenType]: The type of device screen + /// * [refinedSize]: The refined size category + /// * [screenSize]: The total screen dimensions + /// * [localWidgetSize]: The dimensions of the local widget's constraints SizingInformation({ required this.deviceScreenType, required this.refinedSize, @@ -42,17 +67,27 @@ class SizingInformation { @override String toString() { - return 'DeviceType:$deviceScreenType RefinedSize:$refinedSize ScreenSize:$screenSize LocalWidgetSize:$localWidgetSize'; + return 'DeviceType:$deviceScreenType RefinedSize:$refinedSize ' + 'ScreenSize:$screenSize LocalWidgetSize:$localWidgetSize'; } } -/// Manually define screen resolution breakpoints +/// Manually define screen resolution breakpoints for device type detection. /// -/// Overrides the defaults +/// This class allows you to override the default breakpoints used to determine +/// whether a device should be considered small (mobile) or large +/// (tablet/desktop). The breakpoints are defined in logical pixels. class ScreenBreakpoints { + /// The breakpoint below which a device is considered small (mobile) final double small; + + /// The breakpoint above which a device is considered large (tablet/desktop) final double large; + /// Creates a new [ScreenBreakpoints] instance. + /// + /// Both [small] and [large] parameters are required and should be specified + /// in logical pixels. const ScreenBreakpoints({ required this.small, required this.large, @@ -64,25 +99,39 @@ class ScreenBreakpoints { } } -/// Manually define refined breakpoints +/// Manually define refined breakpoints for more granular size categories. +/// +/// This class allows you to override the default breakpoints used to determine +/// the refined size categories (small, normal, large, extraLarge) for different +/// device types. All breakpoints are defined in logical pixels. /// -/// Overrides the defaults +/// Default values are provided for common device sizes: +/// * Mobile: 320-480px +/// * Tablet: 600-900px +/// * Desktop: 950-4096px class RefinedBreakpoints { + /// Mobile device breakpoints final double mobileSmall; final double mobileNormal; final double mobileLarge; final double mobileExtraLarge; + /// Tablet device breakpoints final double tabletSmall; final double tabletNormal; final double tabletLarge; final double tabletExtraLarge; + /// Desktop device breakpoints final double desktopSmall; final double desktopNormal; final double desktopLarge; final double desktopExtraLarge; + /// Creates a new [RefinedBreakpoints] instance. + /// + /// All parameters are optional and default to common device sizes. + /// Values should be specified in logical pixels. const RefinedBreakpoints({ this.mobileSmall = 320, this.mobileNormal = 375, @@ -100,7 +149,13 @@ class RefinedBreakpoints { @override String toString() { - return "Tablet: Small - $tabletSmall Normal - $tabletNormal Large - $tabletLarge ExtraLarge - $tabletExtraLarge" + - "\nMobile: Small - $mobileSmall Normal - $mobileNormal Large - $mobileLarge ExtraLarge - $mobileExtraLarge"; + return "Tablet: Small - $tabletSmall " + + "Normal - $tabletNormal " + + "Large - $tabletLarge " + + "ExtraLarge - $tabletExtraLarge " + + "Mobile: Small - $mobileSmall " + + "Normal - $mobileNormal " + + "Large - $mobileLarge " + + "ExtraLarge - $mobileExtraLarge"; } } diff --git a/lib/src/widget_builders.dart b/lib/src/widget_builders.dart index a123688..ef0b1b9 100644 --- a/lib/src/widget_builders.dart +++ b/lib/src/widget_builders.dart @@ -1,12 +1,23 @@ import 'package:flutter/widgets.dart'; import '../responsive_builder2.dart'; +/// Signature for a function that builds a widget given a [BuildContext]]. +/// +/// Used by [ScreenTypeLayout.builder] to provide both the build context for +/// responsive widget construction. typedef WidgetBuilder = Widget Function(BuildContext); + +/// Signature for a function that builds a widget given a [BuildContext] +/// and [SizingInformation]. +/// +/// Used by [ScreenTypeLayout.builder2] to provide both the build context and +/// detailed sizing information for responsive widget construction. typedef WidgetBuilder2 = Widget Function(BuildContext, SizingInformation); /// A widget with a builder that provides you with the sizingInformation /// -/// This widget is used by the ScreenTypeLayout to provide different widget builders +/// This widget is used by the ScreenTypeLayout to provide different widget +/// builders class ResponsiveBuilder extends StatelessWidget { final Widget Function( BuildContext context, @@ -122,8 +133,8 @@ class ScreenTypeLayout extends StatelessWidget { required Widget mobile, Widget? tablet, Widget? desktop, - }) : this._breakpoints = breakpoints, - this._isWebOrDesktop = isWebOrDesktop, + }) : this._breakpoints = breakpoints, + this._isWebOrDesktop = isWebOrDesktop, this._watch = _builderOrNull(watch), this._watch2 = null, this._mobile = _builderOrNull(mobile)!, @@ -150,12 +161,12 @@ class ScreenTypeLayout extends StatelessWidget { Widget Function(BuildContext)? mobile, Widget Function(BuildContext)? tablet, Widget Function(BuildContext)? desktop, - }) : this._breakpoints = breakpoints, - this._isWebOrDesktop = isWebOrDesktop, - this._desktop = desktop, - this._tablet = tablet, - this._mobile = mobile, - this._watch = watch, + }) : this._breakpoints = breakpoints, + this._isWebOrDesktop = isWebOrDesktop, + this._desktop = desktop, + this._tablet = tablet, + this._mobile = mobile, + this._watch = watch, this._watch2 = null, this._phone2 = null, this._tablet2 = null, @@ -172,8 +183,8 @@ class ScreenTypeLayout extends StatelessWidget { WidgetBuilder2? phone, WidgetBuilder2? tablet, WidgetBuilder2? desktop, - }) : this._breakpoints = breakpoints, - this._isWebOrDesktop = isWebOrDesktop, + }) : this._breakpoints = breakpoints, + this._isWebOrDesktop = isWebOrDesktop, this._watch = null, this._watch2 = watch, this._mobile = null, @@ -214,17 +225,17 @@ class ScreenTypeLayout extends StatelessWidget { /// provided), and if none is available for the current device type, it falls /// back to a [WidgetBuilder2] (if provided) for more granular control. /// - /// Throws an assertion error if neither a mobile nor a desktop layout is supplied. + /// Throws an assertion error if neither a mobile nor a desktop layout is + /// supplied. @override Widget build(BuildContext context) { return ResponsiveBuilder( breakpoints: _breakpoints, isWebOrDesktop: _isWebOrDesktop, builder: (context, sizingInformation) { - if (_usingBuilder2()) { - return _handleWidgetBuilder2(context, sizingInformation)!; - } - return _handleWidgetBuilder(context, sizingInformation)!; + return _usingBuilder2() + ? _handleWidgetBuilder2(context, sizingInformation)! + : _handleWidgetBuilder(context, sizingInformation)!; }, ); } @@ -239,7 +250,8 @@ class ScreenTypeLayout extends StatelessWidget { if (sizingInformation.deviceScreenType == DeviceScreenType.desktop) { // If we have supplied the desktop layout then display that if (_desktop != null) return _desktop!(context); - // If no desktop layout is supplied we want to check if we have the size below it and display that + // If no desktop layout is supplied we want to check if we have the + // size below it and display that if (_tablet != null) return _tablet!(context); } @@ -266,7 +278,8 @@ class ScreenTypeLayout extends StatelessWidget { if (sizingInformation.deviceScreenType == DeviceScreenType.desktop) { // If we have supplied the desktop layout then display that if (_desktop2 != null) return _desktop2!(context, sizingInformation); - // If no desktop layout is supplied we want to check if we have the size below it and display that + // If no desktop layout is supplied we want to check if we have the + // size below it and display that if (_tablet2 != null) return _tablet2!(context, sizingInformation); } @@ -283,14 +296,19 @@ class ScreenTypeLayout extends StatelessWidget { } } -/// Provides a builder function for refined screen sizes to be used with [ScreenTypeLayout] +/// Provides a builder function for refined screen sizes to be used +/// with [ScreenTypeLayout] /// /// Each builder will get built based on the current device width. /// [breakpoints] define your own custom device resolutions -/// [extraLarge] will be built if width is greater than 2160 on Desktops, 1280 on Tablets, and 600 on Mobiles -/// [large] will be built when width is greater than 1440 on Desktops, 1024 on Tablets, and 414 on Mobiles -/// [normal] will be built when width is greater than 1080 on Desktops, 768 on Tablets, and 375 on Mobiles -/// [small] will be built if width is less than 720 on Desktops, 600 on Tablets, and 320 on Mobiles +/// [extraLarge] will be built if width is greater than 2160 on Desktops, 1280 +/// on Tablets, and 600 on Mobiles +/// [large] will be built when width is greater than 1440 on Desktops, 1024 on +/// Tablets, and 414 on Mobiles +/// [normal] will be built when width is greater than 1080 on Desktops, 768 on +/// Tablets, and 375 on Mobiles +/// [small] will be built if width is less than 720 on Desktops, 600 on Tablets, +/// and 320 on Mobiles class RefinedLayoutBuilder extends StatelessWidget { final RefinedBreakpoints? refinedBreakpoints; final bool? isWebOrDesktop; @@ -320,14 +338,16 @@ class RefinedLayoutBuilder extends StatelessWidget { if (sizingInformation.refinedSize == RefinedSize.extraLarge) { // If we have supplied the extra large layout then display that if (extraLarge != null) return extraLarge!(context); - // If no extra large layout is supplied we want to check if we have the size below it and display that + // If no extra large layout is supplied we want to check if we have + // the size below it and display that if (large != null) return large!(context); } if (sizingInformation.refinedSize == RefinedSize.large) { // If we have supplied the large layout then display that if (large != null) return large!(context); - // If no large layout is supplied we want to check if we have the size below it and display that + // If no large layout is supplied we want to check if we have the + // size below it and display that return normal(context); } @@ -336,7 +356,8 @@ class RefinedLayoutBuilder extends StatelessWidget { if (small != null) return small!(context); } - // If none of the layouts above are supplied or we're on the small size layout then we show the small layout + // If none of the layouts above are supplied or we're on the small size + // layout then we show the small layout return normal(context); }, ); diff --git a/pubspec.yaml b/pubspec.yaml index 2fead53..0f38bbd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,6 +13,7 @@ dependencies: flutter: sdk: flutter provider: ^6.1.5 + universal_platform: ^1.1.0 dev_dependencies: flutter_test: