From a5a48dcddd0585170624186d087447446a4515eb Mon Sep 17 00:00:00 2001 From: Francesco Iapicca Date: Sat, 20 Apr 2024 23:00:23 +0300 Subject: [PATCH 01/15] [adaptive_style] add DeviceSize and mostSimilarTo --- packages/adaptive_style/.gitignore | 29 +++++++++++++ packages/adaptive_style/CHANGELOG.md | 3 ++ packages/adaptive_style/LICENSE | 1 + packages/adaptive_style/README.md | 39 +++++++++++++++++ packages/adaptive_style/analysis_options.yaml | 4 ++ .../adaptive_style/lib/adaptive_style.dart | 1 + .../adaptive_style/lib/src/device_size.dart | 27 ++++++++++++ .../adaptive_style/lib/src/extension.dart | 42 +++++++++++++++++++ packages/adaptive_style/pubspec.yaml | 15 +++++++ 9 files changed, 161 insertions(+) create mode 100644 packages/adaptive_style/.gitignore create mode 100644 packages/adaptive_style/CHANGELOG.md create mode 100644 packages/adaptive_style/LICENSE create mode 100644 packages/adaptive_style/README.md create mode 100644 packages/adaptive_style/analysis_options.yaml create mode 100644 packages/adaptive_style/lib/adaptive_style.dart create mode 100644 packages/adaptive_style/lib/src/device_size.dart create mode 100644 packages/adaptive_style/lib/src/extension.dart create mode 100644 packages/adaptive_style/pubspec.yaml diff --git a/packages/adaptive_style/.gitignore b/packages/adaptive_style/.gitignore new file mode 100644 index 00000000..ac5aa989 --- /dev/null +++ b/packages/adaptive_style/.gitignore @@ -0,0 +1,29 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +build/ diff --git a/packages/adaptive_style/CHANGELOG.md b/packages/adaptive_style/CHANGELOG.md new file mode 100644 index 00000000..41cc7d81 --- /dev/null +++ b/packages/adaptive_style/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/packages/adaptive_style/LICENSE b/packages/adaptive_style/LICENSE new file mode 100644 index 00000000..ba75c69f --- /dev/null +++ b/packages/adaptive_style/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/packages/adaptive_style/README.md b/packages/adaptive_style/README.md new file mode 100644 index 00000000..02fe8eca --- /dev/null +++ b/packages/adaptive_style/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. + +## Usage + +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + +```dart +const like = 'sample'; +``` + +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/packages/adaptive_style/analysis_options.yaml b/packages/adaptive_style/analysis_options.yaml new file mode 100644 index 00000000..a5744c1c --- /dev/null +++ b/packages/adaptive_style/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/adaptive_style/lib/adaptive_style.dart b/packages/adaptive_style/lib/adaptive_style.dart new file mode 100644 index 00000000..938a5922 --- /dev/null +++ b/packages/adaptive_style/lib/adaptive_style.dart @@ -0,0 +1 @@ +library adaptive_style; diff --git a/packages/adaptive_style/lib/src/device_size.dart b/packages/adaptive_style/lib/src/device_size.dart new file mode 100644 index 00000000..5dc67150 --- /dev/null +++ b/packages/adaptive_style/lib/src/device_size.dart @@ -0,0 +1,27 @@ +import 'dart:ui'; + +/// a collection of well known screen dimesions +/// as offered by [chrome device toolbar] +enum DeviceSize { + iphoneSE(Size(375, 667)), + iphoneXR(Size(414, 896)), + iphone12PRO(Size(390, 844)), + iphone14PRO(Size(430, 932)), + pixel7(Size(412, 915)), + galaxyS8Plus(Size(360, 740)), + galaxyS20Ultra(Size(412, 915)), + iPadMini(Size(768, 1024)), + iPadAir(Size(820, 1180)), + iPadPro(Size(1024, 1366)), + surfacePro7(Size(912, 1368)), + surfaceDuo(Size(540, 720)), + galaxyFold(Size(280, 653)), + galaxyA51(Size(412, 914)), + galaxyA71(Size(412, 914)), + nestHub(Size(1024, 600)), + nestHubMax(Size(1280, 800)); + + const DeviceSize(this.size); + + final Size size; +} diff --git a/packages/adaptive_style/lib/src/extension.dart b/packages/adaptive_style/lib/src/extension.dart new file mode 100644 index 00000000..e2123bdc --- /dev/null +++ b/packages/adaptive_style/lib/src/extension.dart @@ -0,0 +1,42 @@ +import 'dart:math' as math show min, sqrt; +import 'dart:ui'; + +import 'device_size.dart'; + +extension DeviceSizeSizesX on List { + List get sizes => [for (final value in this) value.size]; +} + +extension FixedRatioSizeScaleX on T { + double fixedRatioScale(T size) => math.min( + width / size.width, + height / size.height, + ); +} + +extension ReverseScaleX on T { + double get reversedRatio => 1 / this; +} + +extension SizeAreaX on T { + double get area => width * height; +} + +extension MostSimilarSizeX on Iterable { + T mostSimilarTo(T size) { + final sizes = [ + for (final element in this) + ( + element, + + /// this is arbitrary but seems to work + (size.aspectRatio - element.aspectRatio).abs() * + math.sqrt((size.area - element.area).abs()) + ) + ]..sort( + (a, b) => a.$2.compareTo(b.$2), + ); + + return sizes.first.$1; + } +} diff --git a/packages/adaptive_style/pubspec.yaml b/packages/adaptive_style/pubspec.yaml new file mode 100644 index 00000000..e16570af --- /dev/null +++ b/packages/adaptive_style/pubspec.yaml @@ -0,0 +1,15 @@ +name: adaptive_style +description: Make you Flutter UI both responsive and adaptive +version: 0.0.1 +homepage: https://github.com/yakforward-ou/yak_packages + +environment: + sdk: '>=3.3.3 <4.0.0' + +dependencies: + flutter: + sdk: flutter +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^3.0.2 From f841e83802a7c9530a4ca9c9dedeb9181c838368 Mon Sep 17 00:00:00 2001 From: Francesco Iapicca Date: Mon, 22 Apr 2024 17:26:18 +0300 Subject: [PATCH 02/15] [yak_flutter] add ScalebleWidget & co. --- packages/yak_flutter/CHANGELOG.md | 4 +- .../lib/src/widgets/media_query_widget.dart | 34 ++++ .../src/widgets/scalable_edge_positioned.dart | 134 +++++++++++++++ .../lib/src/widgets/scalable_widget.dart | 156 ++++++++++++++++++ packages/yak_flutter/pubspec.yaml | 2 +- 5 files changed, 327 insertions(+), 3 deletions(-) create mode 100644 packages/yak_flutter/lib/src/widgets/media_query_widget.dart create mode 100644 packages/yak_flutter/lib/src/widgets/scalable_edge_positioned.dart create mode 100644 packages/yak_flutter/lib/src/widgets/scalable_widget.dart diff --git a/packages/yak_flutter/CHANGELOG.md b/packages/yak_flutter/CHANGELOG.md index 79de3d73..d6a24979 100644 --- a/packages/yak_flutter/CHANGELOG.md +++ b/packages/yak_flutter/CHANGELOG.md @@ -1,5 +1,5 @@ -### 3.0.1 -- add `EdgePositioned` +### 3.0.2 +- add `EdgePositioned`, `ScalebleWidget`, `MediaQueryWidget` and `ScalableEdgePositioned` ### 3.0.0 - rework due to yak_result v3 diff --git a/packages/yak_flutter/lib/src/widgets/media_query_widget.dart b/packages/yak_flutter/lib/src/widgets/media_query_widget.dart new file mode 100644 index 00000000..c49f82f3 --- /dev/null +++ b/packages/yak_flutter/lib/src/widgets/media_query_widget.dart @@ -0,0 +1,34 @@ +import 'package:flutter/widgets.dart'; + +abstract class MediaQueryWidget extends Widget { + const MediaQueryWidget({super.key}); + + @override + ComponentElement createElement() => MediaQueryElement(this); + + MediaQueryData mediaQueryFrom(MediaQueryData mediaQuery); + + Widget build(BuildContext context); +} + +class MediaQueryElement extends ComponentElement { + MediaQueryElement(MediaQueryWidget super.widget); + + @override + Widget build() { + final mediaQueryWidget = widget as MediaQueryWidget; + return MediaQuery( + data: mediaQueryWidget.mediaQueryFrom(MediaQuery.of(this)), + child: Builder( + builder: mediaQueryWidget.build, + ), + ); + } + + @override + void update(MediaQueryWidget newWidget) { + super.update(newWidget); + assert(widget == newWidget); + rebuild(force: true); + } +} diff --git a/packages/yak_flutter/lib/src/widgets/scalable_edge_positioned.dart b/packages/yak_flutter/lib/src/widgets/scalable_edge_positioned.dart new file mode 100644 index 00000000..e3c81409 --- /dev/null +++ b/packages/yak_flutter/lib/src/widgets/scalable_edge_positioned.dart @@ -0,0 +1,134 @@ +import 'package:flutter/widgets.dart'; + +import 'scalable_widget.dart'; + +@immutable +abstract class ScalableEdgePositioned extends Positioned { + final EdgeInsets padding; + final double dimension; + final double scale; + ScalableEdgePositioned({ + required child, + required this.dimension, + required this.padding, + required this.scale, + super.key, + }) : super( + child: ScalebleWidget( + scale: scale, + child: child, + ), + ); + + factory ScalableEdgePositioned.top({ + required Widget child, + double dimension, + double scale, + EdgeInsets padding, + Key key, + }) = _EdgePositioneTop; + + factory ScalableEdgePositioned.bottom({ + required Widget child, + double dimension, + double scale, + EdgeInsets padding, + Key key, + }) = _EdgePositioneBottom; + + factory ScalableEdgePositioned.left({ + required Widget child, + double dimension, + double scale, + EdgeInsets padding, + Key key, + }) = _EdgePositioneLeft; + + factory ScalableEdgePositioned.right({ + required Widget child, + double dimension, + double scale, + EdgeInsets padding, + Key key, + }) = _EdgePositioneRight; +} + +@immutable +class _EdgePositioneBottom extends ScalableEdgePositioned { + _EdgePositioneBottom({ + required super.child, + super.dimension = 0, + super.scale = 1, + super.padding = EdgeInsets.zero, + super.key, + }); + + @override + double get left => padding.left * scale; + @override + double get right => padding.right * scale; + @override + double get bottom => padding.bottom * scale; + @override + double get height => (dimension - padding.vertical) * scale; +} + +@immutable +class _EdgePositioneTop extends ScalableEdgePositioned { + _EdgePositioneTop({ + required super.child, + super.dimension = 0, + super.scale = 1, + super.padding = EdgeInsets.zero, + super.key, + }); + + @override + double get left => padding.left * scale; + @override + double get right => padding.right * scale; + @override + double get top => padding.top * scale; + @override + double get height => (dimension - padding.vertical) * scale; +} + +@immutable +class _EdgePositioneLeft extends ScalableEdgePositioned { + _EdgePositioneLeft({ + required super.child, + super.dimension = 0, + super.scale = 1, + super.padding = EdgeInsets.zero, + super.key, + }); + + @override + double get left => padding.left * scale; + @override + double get top => padding.top * scale; + @override + double get bottom => padding.bottom * scale; + @override + double get width => (dimension - padding.horizontal) * scale; +} + +@immutable +class _EdgePositioneRight extends ScalableEdgePositioned { + _EdgePositioneRight({ + required super.child, + super.dimension = 0, + super.scale = 1, + super.padding = EdgeInsets.zero, + super.key, + }); + + @override + double get right => padding.right * scale; + @override + double get top => padding.top * scale; + @override + double get bottom => padding.bottom * scale; + @override + double get width => (dimension - padding.horizontal) * scale; +} diff --git a/packages/yak_flutter/lib/src/widgets/scalable_widget.dart b/packages/yak_flutter/lib/src/widgets/scalable_widget.dart new file mode 100644 index 00000000..d41fd4fd --- /dev/null +++ b/packages/yak_flutter/lib/src/widgets/scalable_widget.dart @@ -0,0 +1,156 @@ +import 'dart:ui' as ui; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter/rendering.dart'; + +import 'media_query_widget.dart'; + +class ScalebleWidget extends SingleChildRenderObjectWidget { + final double scale; + + ScalebleWidget({ + super.key, + required this.scale, + required Widget child, + }) : super( + child: ScalableMediaQueryWidget( + scale: scale, + child: child, + ), + ); + + @override + ScaleTransform createRenderObject(context) => ScaleTransform( + scale: scale, + textDirection: Directionality.maybeOf(context), + ); + + @override + void updateRenderObject(context, ScaleTransform renderObject) => + renderObject.scale(scale); +} + +class ScaleTransform extends RenderProxyBox { + ScaleTransform({ + TextDirection? textDirection, + required double scale, + }) : _scale = scale, + _transform = _scaleToMatrix(scale); + + @override + bool get alwaysNeedsCompositing => child != null; + + static Matrix4 _scaleToMatrix(double scale) => + Matrix4.diagonal3Values(scale, scale, 1.0); + + Matrix4 _transform; + double _scale; + void scale(double value) { + if (_scale == value) { + return; + } + _scale = value; + _transform = _scaleToMatrix(value); + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + + @override + void paint(PaintingContext context, Offset offset) { + if (child == null) { + return; + } + final transform = _effectiveTransform; + + final Offset? childOffset = MatrixUtils.getAsTranslation(transform); + if (childOffset != null) { + super.paint(context, offset + childOffset); + layer = null; + return; + } + + final determinant = transform.determinant(); + if (determinant == 0 || !determinant.isFinite) { + layer = null; + return; + } + layer = context.pushTransform( + needsCompositing, + offset, + transform, + super.paint, + oldLayer: layer is TransformLayer ? layer as TransformLayer? : null, + ); + } + + void setIdentity() { + _transform.setIdentity(); + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + + Matrix4 get _effectiveTransform => Matrix4.identity()..multiply(_transform); + + @override + bool hitTest(BoxHitTestResult result, {required Offset position}) { + return hitTestChildren(result, position: position); + } + + @override + bool hitTestChildren(result, {required Offset position}) => + result.addWithPaintTransform( + transform: _effectiveTransform, + position: position, + hitTest: (result, position) => super.hitTestChildren( + result, + position: position, + ), + ); + + @override + void applyPaintTransform(child, transform) => + transform.multiply(_effectiveTransform); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DoubleProperty('scale', _scale)); + } +} + +class ScalableMediaQueryWidget extends MediaQueryWidget { + final double scale; + final Widget child; + + const ScalableMediaQueryWidget({ + required this.child, + required this.scale, + super.key, + }); + + @override + Widget build(context) => child; + + @override + @nonVirtual + MediaQueryData mediaQueryFrom(mediaQuery) => mediaQuery.copyWith( + size: mediaQuery.size * scale, + viewInsets: mediaQuery.viewInsets * scale, + systemGestureInsets: mediaQuery.systemGestureInsets * scale, + viewPadding: mediaQuery.viewPadding * scale, + padding: mediaQuery.padding * scale, + devicePixelRatio: mediaQuery.devicePixelRatio * scale, + displayFeatures: [ + for (final feature in mediaQuery.displayFeatures) + ui.DisplayFeature( + type: feature.type, + state: feature.state, + bounds: Rect.fromPoints( + feature.bounds.topLeft * scale, + feature.bounds.bottomRight * scale, + ), + ), + ], + ); +} diff --git a/packages/yak_flutter/pubspec.yaml b/packages/yak_flutter/pubspec.yaml index 9c7d5d8c..29c1048c 100644 --- a/packages/yak_flutter/pubspec.yaml +++ b/packages/yak_flutter/pubspec.yaml @@ -1,6 +1,6 @@ name: yak_flutter description: A collection of tools, widgets and extensions for Flutter framework. -version: 3.0.1 +version: 3.0.2 homepage: https://github.com/yakforward-ou/yak_packages environment: From 60b2e187f6ba40f3c02b25542eeeb6b042f1f6a1 Mon Sep 17 00:00:00 2001 From: Francesco Iapicca Date: Mon, 22 Apr 2024 17:29:55 +0300 Subject: [PATCH 03/15] [yak_flutter] add missing exports --- packages/yak_flutter/lib/src/widgets/all.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/yak_flutter/lib/src/widgets/all.dart b/packages/yak_flutter/lib/src/widgets/all.dart index 007c96d6..9c29e59e 100644 --- a/packages/yak_flutter/lib/src/widgets/all.dart +++ b/packages/yak_flutter/lib/src/widgets/all.dart @@ -1,2 +1,5 @@ -export 'preferred_size_themed.dart'; export 'edge_positioned.dart'; +export 'media_query_widget.dart'; +export 'preferred_size_themed.dart'; +export 'scalable_edge_positioned.dart'; +export 'scalable_widget.dart'; From d2ca9d2760155570ad1701d49c89a4319fab15e1 Mon Sep 17 00:00:00 2001 From: Francesco Iapicca Date: Tue, 23 Apr 2024 17:44:58 +0300 Subject: [PATCH 04/15] [adaptive_style] add example --- examples/adaptive_style_example/.gitignore | 43 +++++++++++++++ examples/adaptive_style_example/README.md | 16 ++++++ .../analysis_options.yaml | 28 ++++++++++ examples/adaptive_style_example/lib/main.dart | 54 +++++++++++++++++++ examples/adaptive_style_example/pubspec.yaml | 21 ++++++++ .../pubspec_overrides.yaml | 14 +++++ 6 files changed, 176 insertions(+) create mode 100644 examples/adaptive_style_example/.gitignore create mode 100644 examples/adaptive_style_example/README.md create mode 100644 examples/adaptive_style_example/analysis_options.yaml create mode 100644 examples/adaptive_style_example/lib/main.dart create mode 100644 examples/adaptive_style_example/pubspec.yaml create mode 100644 examples/adaptive_style_example/pubspec_overrides.yaml diff --git a/examples/adaptive_style_example/.gitignore b/examples/adaptive_style_example/.gitignore new file mode 100644 index 00000000..29a3a501 --- /dev/null +++ b/examples/adaptive_style_example/.gitignore @@ -0,0 +1,43 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/examples/adaptive_style_example/README.md b/examples/adaptive_style_example/README.md new file mode 100644 index 00000000..674b5a65 --- /dev/null +++ b/examples/adaptive_style_example/README.md @@ -0,0 +1,16 @@ +# adaptive_style_example + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/examples/adaptive_style_example/analysis_options.yaml b/examples/adaptive_style_example/analysis_options.yaml new file mode 100644 index 00000000..0d290213 --- /dev/null +++ b/examples/adaptive_style_example/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/examples/adaptive_style_example/lib/main.dart b/examples/adaptive_style_example/lib/main.dart new file mode 100644 index 00000000..1c0bb0bb --- /dev/null +++ b/examples/adaptive_style_example/lib/main.dart @@ -0,0 +1,54 @@ +import 'package:adaptive_style/adaptive_style.dart'; +import 'package:flutter/material.dart'; + +void main() => runApp(const MyApplication()); + +class MyApplication extends StatelessWidget { + const MyApplication({super.key}); + + @override + Widget build(context) => const AdaptiveSizeProvider( + child: MaterialApp( + home: MyHomePage(), + ), + ); +} + +class MyHomePage extends StatelessWidget { + const MyHomePage({super.key}); + + @override + Widget build(context) => Scaffold( + body: AdaptiveSizeBuilder(builder: (context, adaptiveSize) { + final deviceSize = adaptiveSize.mostSimilarDeviceSize; + return switch (deviceSize) { + (DeviceSize.iphoneSE) => const Stack( + children: [ + Padding( + padding: EdgeInsets.all(10), + child: Center( + child: ColoredBox( + color: Colors.blue, + child: SizedBox.expand(), + ), + ), + ), + ], + ), + _ => const Stack( + children: [ + Padding( + padding: EdgeInsets.all(10), + child: Center( + child: ColoredBox( + color: Colors.red, + child: SizedBox.expand(), + ), + ), + ), + ], + ), + }; + }), + ); +} diff --git a/examples/adaptive_style_example/pubspec.yaml b/examples/adaptive_style_example/pubspec.yaml new file mode 100644 index 00000000..ff526d1d --- /dev/null +++ b/examples/adaptive_style_example/pubspec.yaml @@ -0,0 +1,21 @@ +name: adaptive_style_example +description: "An example app for adaptive_style." + +publish_to: 'none' +version: 1.0.0+1 + +environment: + sdk: ">=3.3.3 <4.0.0" + +dependencies: + flutter: + sdk: flutter + adaptive_style: ^0.0.1 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^3.0.1 + +flutter: + uses-material-design: true diff --git a/examples/adaptive_style_example/pubspec_overrides.yaml b/examples/adaptive_style_example/pubspec_overrides.yaml new file mode 100644 index 00000000..36ab3a24 --- /dev/null +++ b/examples/adaptive_style_example/pubspec_overrides.yaml @@ -0,0 +1,14 @@ +# melos_managed_dependency_overrides: adaptive_style,yak_flutter,yak_result,yak_runner,yak_tween,yak_utils +dependency_overrides: + adaptive_style: + path: ../../packages/adaptive_style + yak_flutter: + path: ../../packages/yak_flutter + yak_result: + path: ../../packages/yak_result + yak_runner: + path: ../../packages/yak_runner + yak_tween: + path: ../../packages/yak_tween + yak_utils: + path: ../../packages/yak_utils From 0539209611022e6b36bd2960340ce4c67583c25c Mon Sep 17 00:00:00 2001 From: Francesco Iapicca Date: Tue, 23 Apr 2024 17:45:23 +0300 Subject: [PATCH 05/15] [adaptive_style] add first implementation --- .../adaptive_style/lib/adaptive_style.dart | 7 ++ .../adaptive_style/lib/src/adaptive_size.dart | 44 ++++++++++++ .../lib/src/adaptive_size_builder.dart | 27 +++++++ .../lib/src/adaptive_size_provider.dart | 70 +++++++++++++++++++ .../adaptive_style/lib/src/device_size.dart | 2 +- .../adaptive_style/lib/src/extension.dart | 56 ++++++++++++--- .../lib/src/inherited_adaptive_size.dart | 24 +++++++ packages/adaptive_style/pubspec.yaml | 2 + .../adaptive_style/pubspec_overrides.yaml | 12 ++++ 9 files changed, 234 insertions(+), 10 deletions(-) create mode 100644 packages/adaptive_style/lib/src/adaptive_size.dart create mode 100644 packages/adaptive_style/lib/src/adaptive_size_builder.dart create mode 100644 packages/adaptive_style/lib/src/adaptive_size_provider.dart create mode 100644 packages/adaptive_style/lib/src/inherited_adaptive_size.dart create mode 100644 packages/adaptive_style/pubspec_overrides.yaml diff --git a/packages/adaptive_style/lib/adaptive_style.dart b/packages/adaptive_style/lib/adaptive_style.dart index 938a5922..5d532c79 100644 --- a/packages/adaptive_style/lib/adaptive_style.dart +++ b/packages/adaptive_style/lib/adaptive_style.dart @@ -1 +1,8 @@ library adaptive_style; + +export 'src/adaptive_size_builder.dart'; +export 'src/adaptive_size_provider.dart'; +export 'src/adaptive_size.dart'; +export 'src/device_size.dart'; +export 'src/extension.dart'; +export 'src/inherited_adaptive_size.dart'; diff --git a/packages/adaptive_style/lib/src/adaptive_size.dart b/packages/adaptive_style/lib/src/adaptive_size.dart new file mode 100644 index 00000000..029e178d --- /dev/null +++ b/packages/adaptive_style/lib/src/adaptive_size.dart @@ -0,0 +1,44 @@ +import 'package:flutter/widgets.dart'; +import 'package:yak_flutter/yak_flutter.dart'; +import 'extension.dart'; +import 'device_size.dart'; + +typedef AdaptiveSizeNotifier = RestrictedNotifier; + +final class AdaptiveSizeData { + const AdaptiveSizeData({ + required this.realScreenSize, + required this.scaledSize, + required this.scale, + required this.deviceSizes, + required this.mostSimilarDeviceSize, + }); + + factory AdaptiveSizeData.fromSize( + Size size, { + List deviceSizes = DeviceSize.values, + }) { + final mostSimilarDeviceSize = deviceSizes.mostSimilarTo(size); + final scale = mostSimilarDeviceSize.size.closestDimentionScale(size); + return AdaptiveSizeData( + deviceSizes: deviceSizes, + realScreenSize: size, + mostSimilarDeviceSize: mostSimilarDeviceSize, + scale: scale, + scaledSize: size * scale, + ); + } + + final Size realScreenSize; + final Size scaledSize; + final double scale; + final List deviceSizes; + final DeviceSize mostSimilarDeviceSize; + + @override + bool operator ==(Object other) => + other is AdaptiveSizeData && other.hashCode == hashCode; + + @override + int get hashCode => Object.hash(realScreenSize, deviceSizes); +} diff --git a/packages/adaptive_style/lib/src/adaptive_size_builder.dart b/packages/adaptive_style/lib/src/adaptive_size_builder.dart new file mode 100644 index 00000000..37f22d44 --- /dev/null +++ b/packages/adaptive_style/lib/src/adaptive_size_builder.dart @@ -0,0 +1,27 @@ +import 'package:flutter/widgets.dart'; + +import 'adaptive_size.dart'; +import 'inherited_adaptive_size.dart'; + +typedef AdaptiveSizeBuild = Widget Function(BuildContext, AdaptiveSizeData); + +class AdaptiveSizeBuilder extends StatelessWidget { + final AdaptiveSizeBuild builder; + const AdaptiveSizeBuilder({ + super.key, + required this.builder, + }); + + @override + Widget build(context) { + final adaptiveSize = InheritedAdaptiveSize.maybeOf(context); + if (adaptiveSize == null) { + throw Exception('InheritedAdaptiveSize not found in BuildContext'); + } + + return ValueListenableBuilder( + valueListenable: adaptiveSize as ValueNotifier, + builder: (context, value, _) => builder(context, value), + ); + } +} diff --git a/packages/adaptive_style/lib/src/adaptive_size_provider.dart b/packages/adaptive_style/lib/src/adaptive_size_provider.dart new file mode 100644 index 00000000..9be76fef --- /dev/null +++ b/packages/adaptive_style/lib/src/adaptive_size_provider.dart @@ -0,0 +1,70 @@ +import 'dart:async'; + +import 'package:flutter/widgets.dart'; +import 'package:yak_flutter/yak_flutter.dart'; + +import 'adaptive_size.dart'; +import 'device_size.dart'; +import 'inherited_adaptive_size.dart'; + +class AdaptiveSizeProvider extends StatefulWidget { + final Widget child; + final List deviceSizes; + const AdaptiveSizeProvider({ + super.key, + required this.child, + this.deviceSizes = DeviceSize.values, + }); + + @override + State createState() => _AdaptiveSizeProvidertState(); +} + +class _AdaptiveSizeProvidertState extends State { + final _initialized = Completer(); + late final ValueNotifier _notifier; + + @override + void didChangeDependencies() { + if (!_initialized.isCompleted) { + _initialize(context); + return; + } + _update(context); + super.didChangeDependencies(); + } + + @override + void dispose() { + _notifier.dispose(); + super.dispose(); + } + + void _update(BuildContext context) { + final size = MediaQuery.sizeOf(context); + if (size == _notifier.value.realScreenSize && + widget.deviceSizes == _notifier.value.deviceSizes) { + return; + } + + _notifier.value = AdaptiveSizeData.fromSize( + size, + deviceSizes: widget.deviceSizes, + ); + } + + void _initialize(BuildContext size) { + final adaptiveSize = AdaptiveSizeData.fromSize( + MediaQuery.sizeOf(context), + deviceSizes: widget.deviceSizes, + ); + _notifier = ValueNotifier(adaptiveSize); + _initialized.complete(true); + } + + @override + Widget build(context) => InheritedAdaptiveSize( + notifier: RestrictedNotifier(_notifier), + child: widget.child, + ); +} diff --git a/packages/adaptive_style/lib/src/device_size.dart b/packages/adaptive_style/lib/src/device_size.dart index 5dc67150..b2eb5df1 100644 --- a/packages/adaptive_style/lib/src/device_size.dart +++ b/packages/adaptive_style/lib/src/device_size.dart @@ -1,4 +1,4 @@ -import 'dart:ui'; +import 'dart:ui' show Size; /// a collection of well known screen dimesions /// as offered by [chrome device toolbar] diff --git a/packages/adaptive_style/lib/src/extension.dart b/packages/adaptive_style/lib/src/extension.dart index e2123bdc..bc1e180c 100644 --- a/packages/adaptive_style/lib/src/extension.dart +++ b/packages/adaptive_style/lib/src/extension.dart @@ -1,5 +1,5 @@ import 'dart:math' as math show min, sqrt; -import 'dart:ui'; +import 'dart:ui' show Size; import 'device_size.dart'; @@ -7,8 +7,8 @@ extension DeviceSizeSizesX on List { List get sizes => [for (final value in this) value.size]; } -extension FixedRatioSizeScaleX on T { - double fixedRatioScale(T size) => math.min( +extension ClosestDimentionScaleX on T { + double closestDimentionScale(T size) => math.min( width / size.width, height / size.height, ); @@ -22,16 +22,16 @@ extension SizeAreaX on T { double get area => width * height; } -extension MostSimilarSizeX on Iterable { - T mostSimilarTo(T size) { +extension MostSimilarSizeX on Iterable { + DeviceSize mostSimilarTo(T size) { final sizes = [ - for (final element in this) + for (final deviceSize in this) ( - element, + deviceSize, /// this is arbitrary but seems to work - (size.aspectRatio - element.aspectRatio).abs() * - math.sqrt((size.area - element.area).abs()) + (size.aspectRatio - deviceSize.size.aspectRatio).abs() * + math.sqrt((size.area - deviceSize.size.area).abs()) ) ]..sort( (a, b) => a.$2.compareTo(b.$2), @@ -40,3 +40,41 @@ extension MostSimilarSizeX on Iterable { return sizes.first.$1; } } + +extension DeviceSizeAverageSizeX on List { + Size get averagePortrait { + final portraits = [ + for (final device in this) + if (device.size.aspectRatio < 1) device.size, + ]; + var height = .0; + var width = .0; + for (final portrait in portraits) { + width += portrait.width; + height += portrait.height; + } + + return Size( + width / portraits.length, + height / portraits.length, + ); + } + + Size get averageLandscape { + final landscapes = [ + for (final device in this) + if (device.size.aspectRatio > 1) device.size, + ]; + var height = .0; + var width = .0; + for (final landscape in landscapes) { + width += landscape.width; + height += landscape.height; + } + + return Size( + width / landscapes.length, + height / landscapes.length, + ); + } +} diff --git a/packages/adaptive_style/lib/src/inherited_adaptive_size.dart b/packages/adaptive_style/lib/src/inherited_adaptive_size.dart new file mode 100644 index 00000000..440f96ee --- /dev/null +++ b/packages/adaptive_style/lib/src/inherited_adaptive_size.dart @@ -0,0 +1,24 @@ +import 'package:flutter/widgets.dart'; +import 'package:yak_flutter/yak_flutter.dart'; + +import 'adaptive_size.dart'; + +class InheritedAdaptiveSize + extends InheritedRestrictedNotifier { + const InheritedAdaptiveSize({ + super.key, + required super.notifier, + required super.child, + }); + + static AdaptiveSizeNotifier? maybeOf(BuildContext context) { + final inherited = + context.dependOnInheritedWidgetOfExactType(); + + if (inherited == null) { + return null; + } + + return inherited.notifier; + } +} diff --git a/packages/adaptive_style/pubspec.yaml b/packages/adaptive_style/pubspec.yaml index e16570af..b6e17420 100644 --- a/packages/adaptive_style/pubspec.yaml +++ b/packages/adaptive_style/pubspec.yaml @@ -9,6 +9,8 @@ environment: dependencies: flutter: sdk: flutter + yak_flutter: ^3.0.2 + dev_dependencies: flutter_test: sdk: flutter diff --git a/packages/adaptive_style/pubspec_overrides.yaml b/packages/adaptive_style/pubspec_overrides.yaml new file mode 100644 index 00000000..44068726 --- /dev/null +++ b/packages/adaptive_style/pubspec_overrides.yaml @@ -0,0 +1,12 @@ +# melos_managed_dependency_overrides: yak_flutter,yak_result,yak_runner,yak_tween,yak_utils +dependency_overrides: + yak_flutter: + path: ../yak_flutter + yak_result: + path: ../yak_result + yak_runner: + path: ../yak_runner + yak_tween: + path: ../yak_tween + yak_utils: + path: ../yak_utils From 59b6b50ee57b316e9c75d7797201d4382a7d82df Mon Sep 17 00:00:00 2001 From: Francesco Iapicca Date: Tue, 23 Apr 2024 17:46:33 +0300 Subject: [PATCH 06/15] [yak_flutter] add inherited_value_notifier --- packages/yak_flutter/CHANGELOG.md | 3 +- packages/yak_flutter/lib/src/widgets/all.dart | 1 + .../src/widgets/inherited_value_notifier.dart | 74 +++++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 packages/yak_flutter/lib/src/widgets/inherited_value_notifier.dart diff --git a/packages/yak_flutter/CHANGELOG.md b/packages/yak_flutter/CHANGELOG.md index d6a24979..454290a3 100644 --- a/packages/yak_flutter/CHANGELOG.md +++ b/packages/yak_flutter/CHANGELOG.md @@ -1,5 +1,6 @@ ### 3.0.2 -- add `EdgePositioned`, `ScalebleWidget`, `MediaQueryWidget` and `ScalableEdgePositioned` +- add `EdgePositioned`, `ScalebleWidget`, `MediaQueryWidget` , +- `InheritedRestrictedNotifier` and `ScalableEdgePositioned` ### 3.0.0 - rework due to yak_result v3 diff --git a/packages/yak_flutter/lib/src/widgets/all.dart b/packages/yak_flutter/lib/src/widgets/all.dart index 9c29e59e..8552d04d 100644 --- a/packages/yak_flutter/lib/src/widgets/all.dart +++ b/packages/yak_flutter/lib/src/widgets/all.dart @@ -3,3 +3,4 @@ export 'media_query_widget.dart'; export 'preferred_size_themed.dart'; export 'scalable_edge_positioned.dart'; export 'scalable_widget.dart'; +export 'inherited_value_notifier.dart'; diff --git a/packages/yak_flutter/lib/src/widgets/inherited_value_notifier.dart b/packages/yak_flutter/lib/src/widgets/inherited_value_notifier.dart new file mode 100644 index 00000000..441b75b9 --- /dev/null +++ b/packages/yak_flutter/lib/src/widgets/inherited_value_notifier.dart @@ -0,0 +1,74 @@ +import 'package:flutter/widgets.dart'; + +typedef Initialize = T Function(); +typedef Dispose = void Function(T); + +typedef HandleListener = void Function(VoidCallback); + +extension type RestrictedNotifier(ValueNotifier notifier) { + HandleListener get addListener => notifier.addListener; + HandleListener get removeListener => notifier.removeListener; + T get value => notifier.value; + set value(T value) => notifier.value = value; +} + +class InheritedRestrictedNotifier extends InheritedWidget { + const InheritedRestrictedNotifier({ + super.key, + required this.notifier, + required super.child, + }); + + final RestrictedNotifier notifier; + + factory InheritedRestrictedNotifier.of(BuildContext context) { + final inherited = context + .dependOnInheritedWidgetOfExactType>(); + if (inherited == null) { + throw Exception( + 'InheritedRestrictedNotifier<$T> not found in BuildContext'); + } + return inherited; + } + + @override + bool updateShouldNotify(InheritedRestrictedNotifier oldWidget) => false; +} + +class InheritedRestrictedNotifierdWidget + extends StatefulWidget { + final Widget child; + final T value; + + const InheritedRestrictedNotifierdWidget({ + super.key, + required this.child, + required this.value, + }); + + @override + State> createState() => + _InheritedRestrictedNotifierdWidgettState(); +} + +class _InheritedRestrictedNotifierdWidgettState + extends State> { + late final ValueNotifier notifier; + @override + void initState() { + notifier = ValueNotifier(widget.value); + super.initState(); + } + + @override + void dispose() { + notifier.dispose(); + super.dispose(); + } + + @override + Widget build(context) => InheritedRestrictedNotifier( + notifier: RestrictedNotifier(notifier), + child: widget.child, + ); +} From c05b284e5d148421fc6686d6e6132ae82f8e2fd4 Mon Sep 17 00:00:00 2001 From: Francesco Iapicca Date: Sun, 28 Apr 2024 21:26:49 +0300 Subject: [PATCH 07/15] [adaptive_style] rework with extension types --- examples/adaptive_style_example/lib/main.dart | 50 ++++++++----------- examples/adaptive_style_example/pubspec.yaml | 2 + .../adaptive_style/lib/adaptive_style.dart | 2 +- .../lib/src/_adaptive_size.dart | 42 ++++++++++++++++ .../adaptive_style/lib/src/adaptive_size.dart | 44 ---------------- .../lib/src/adaptive_size_builder.dart | 18 +++---- .../lib/src/adaptive_size_provider.dart | 47 ++++++++--------- .../adaptive_style/lib/src/device_size.dart | 49 ++++++++++-------- .../adaptive_style/lib/src/extension.dart | 20 +++----- .../lib/src/inherited_adaptive_size.dart | 11 ++-- packages/adaptive_style/lib/src/size_ref.dart | 6 +++ .../adaptive_style/lib/src/size_scale.dart | 17 +++++++ packages/adaptive_style/pubspec.yaml | 3 +- 13 files changed, 163 insertions(+), 148 deletions(-) create mode 100644 packages/adaptive_style/lib/src/_adaptive_size.dart delete mode 100644 packages/adaptive_style/lib/src/adaptive_size.dart create mode 100644 packages/adaptive_style/lib/src/size_ref.dart create mode 100644 packages/adaptive_style/lib/src/size_scale.dart diff --git a/examples/adaptive_style_example/lib/main.dart b/examples/adaptive_style_example/lib/main.dart index 1c0bb0bb..5198ad95 100644 --- a/examples/adaptive_style_example/lib/main.dart +++ b/examples/adaptive_style_example/lib/main.dart @@ -7,7 +7,8 @@ class MyApplication extends StatelessWidget { const MyApplication({super.key}); @override - Widget build(context) => const AdaptiveSizeProvider( + Widget build(context) => const SizeRefProvider( + deviceSizes: [DeviceSize.iphoneSE], child: MaterialApp( home: MyHomePage(), ), @@ -18,37 +19,26 @@ class MyHomePage extends StatelessWidget { const MyHomePage({super.key}); @override - Widget build(context) => Scaffold( - body: AdaptiveSizeBuilder(builder: (context, adaptiveSize) { - final deviceSize = adaptiveSize.mostSimilarDeviceSize; - return switch (deviceSize) { - (DeviceSize.iphoneSE) => const Stack( - children: [ - Padding( - padding: EdgeInsets.all(10), - child: Center( - child: ColoredBox( - color: Colors.blue, - child: SizedBox.expand(), - ), + Widget build(context) => SizeRefBuilder(builder: (context, sizeRef) { + return switch (sizeRef.size) { + (DeviceSize.iphoneSE) => Center( + child: SizedBox.fromSize( + size: sizeRef.size * sizeRef.scale.min, + child: Scaffold( + backgroundColor: Colors.black, + body: Center( + child: FlutterLogo( + size: 100 * sizeRef.scale.min, ), ), - ], + ), ), - _ => const Stack( - children: [ - Padding( - padding: EdgeInsets.all(10), - child: Center( - child: ColoredBox( - color: Colors.red, - child: SizedBox.expand(), - ), - ), - ), - ], + ), + _ => const Scaffold( + body: Center( + child: FlutterLogo(size: 100), ), - }; - }), - ); + ), + }; + }); } diff --git a/examples/adaptive_style_example/pubspec.yaml b/examples/adaptive_style_example/pubspec.yaml index ff526d1d..96eb5991 100644 --- a/examples/adaptive_style_example/pubspec.yaml +++ b/examples/adaptive_style_example/pubspec.yaml @@ -11,6 +11,8 @@ dependencies: flutter: sdk: flutter adaptive_style: ^0.0.1 + yak_flutter: ^3.0.2 + dev_dependencies: flutter_test: diff --git a/packages/adaptive_style/lib/adaptive_style.dart b/packages/adaptive_style/lib/adaptive_style.dart index 5d532c79..9fcec9df 100644 --- a/packages/adaptive_style/lib/adaptive_style.dart +++ b/packages/adaptive_style/lib/adaptive_style.dart @@ -2,7 +2,7 @@ library adaptive_style; export 'src/adaptive_size_builder.dart'; export 'src/adaptive_size_provider.dart'; -export 'src/adaptive_size.dart'; +export 'src/_adaptive_size.dart'; export 'src/device_size.dart'; export 'src/extension.dart'; export 'src/inherited_adaptive_size.dart'; diff --git a/packages/adaptive_style/lib/src/_adaptive_size.dart b/packages/adaptive_style/lib/src/_adaptive_size.dart new file mode 100644 index 00000000..aff0d970 --- /dev/null +++ b/packages/adaptive_style/lib/src/_adaptive_size.dart @@ -0,0 +1,42 @@ +// import 'package:flutter/widgets.dart'; +// import 'package:yak_flutter/yak_flutter.dart'; +// import 'extension.dart'; +// import 'device_size.dart'; + +// typedef AdaptiveSizeNotifier = RestrictedNotifier; + +// final class AdaptiveSizeData { +// const AdaptiveSizeData({ +// required this.screenSize, +// required this.scale, +// required this.availableSizes, +// required this.mostSimilarDeviceSize, +// }); + +// // factory AdaptiveSizeData.fromSize( +// // Size size, { +// // List availableSizes = DeviceSize.values, +// // }) { +// // final mostSimilarDeviceSize = availableSizes.mostSimilarTo(size); +// // final scale = mostSimilarDeviceSize.size.closestDimentionScale(size); + +// // return AdaptiveSizeData( +// // availableSizes: availableSizes, +// // realScreenSize: size, +// // mostSimilarDeviceSize: mostSimilarDeviceSize, +// // scale: scale, +// // ); +// // } + +// final Size screenSize; +// final Offset widthScale, ; +// final List availableSizes; +// final DeviceSize mostSimilarDeviceSize; + +// @override +// bool operator ==(Object other) => +// other is AdaptiveSizeData && other.hashCode == hashCode; + +// @override +// int get hashCode => Object.hash(screenSize, availableSizes); +// } diff --git a/packages/adaptive_style/lib/src/adaptive_size.dart b/packages/adaptive_style/lib/src/adaptive_size.dart deleted file mode 100644 index 029e178d..00000000 --- a/packages/adaptive_style/lib/src/adaptive_size.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:yak_flutter/yak_flutter.dart'; -import 'extension.dart'; -import 'device_size.dart'; - -typedef AdaptiveSizeNotifier = RestrictedNotifier; - -final class AdaptiveSizeData { - const AdaptiveSizeData({ - required this.realScreenSize, - required this.scaledSize, - required this.scale, - required this.deviceSizes, - required this.mostSimilarDeviceSize, - }); - - factory AdaptiveSizeData.fromSize( - Size size, { - List deviceSizes = DeviceSize.values, - }) { - final mostSimilarDeviceSize = deviceSizes.mostSimilarTo(size); - final scale = mostSimilarDeviceSize.size.closestDimentionScale(size); - return AdaptiveSizeData( - deviceSizes: deviceSizes, - realScreenSize: size, - mostSimilarDeviceSize: mostSimilarDeviceSize, - scale: scale, - scaledSize: size * scale, - ); - } - - final Size realScreenSize; - final Size scaledSize; - final double scale; - final List deviceSizes; - final DeviceSize mostSimilarDeviceSize; - - @override - bool operator ==(Object other) => - other is AdaptiveSizeData && other.hashCode == hashCode; - - @override - int get hashCode => Object.hash(realScreenSize, deviceSizes); -} diff --git a/packages/adaptive_style/lib/src/adaptive_size_builder.dart b/packages/adaptive_style/lib/src/adaptive_size_builder.dart index 37f22d44..e45c8a2e 100644 --- a/packages/adaptive_style/lib/src/adaptive_size_builder.dart +++ b/packages/adaptive_style/lib/src/adaptive_size_builder.dart @@ -1,26 +1,26 @@ import 'package:flutter/widgets.dart'; -import 'adaptive_size.dart'; import 'inherited_adaptive_size.dart'; +import 'size_ref.dart'; -typedef AdaptiveSizeBuild = Widget Function(BuildContext, AdaptiveSizeData); +typedef SizeRefBuild = Widget Function(BuildContext, SizeRef); -class AdaptiveSizeBuilder extends StatelessWidget { - final AdaptiveSizeBuild builder; - const AdaptiveSizeBuilder({ +class SizeRefBuilder extends StatelessWidget { + final SizeRefBuild builder; + const SizeRefBuilder({ super.key, required this.builder, }); @override Widget build(context) { - final adaptiveSize = InheritedAdaptiveSize.maybeOf(context); - if (adaptiveSize == null) { - throw Exception('InheritedAdaptiveSize not found in BuildContext'); + final sizeRefNotifier = InheritedSizeRef.maybeOf(context); + if (sizeRefNotifier == null) { + throw Exception('InheritedSizeRef not found in BuildContext'); } return ValueListenableBuilder( - valueListenable: adaptiveSize as ValueNotifier, + valueListenable: sizeRefNotifier as ValueNotifier, builder: (context, value, _) => builder(context, value), ); } diff --git a/packages/adaptive_style/lib/src/adaptive_size_provider.dart b/packages/adaptive_style/lib/src/adaptive_size_provider.dart index 9be76fef..800de418 100644 --- a/packages/adaptive_style/lib/src/adaptive_size_provider.dart +++ b/packages/adaptive_style/lib/src/adaptive_size_provider.dart @@ -1,33 +1,33 @@ import 'dart:async'; +import 'package:adaptive_style/adaptive_style.dart'; import 'package:flutter/widgets.dart'; import 'package:yak_flutter/yak_flutter.dart'; +import 'size_ref.dart'; +import 'size_scale.dart'; -import 'adaptive_size.dart'; -import 'device_size.dart'; -import 'inherited_adaptive_size.dart'; - -class AdaptiveSizeProvider extends StatefulWidget { +class SizeRefProvider extends StatefulWidget { final Widget child; final List deviceSizes; - const AdaptiveSizeProvider({ + const SizeRefProvider({ super.key, required this.child, - this.deviceSizes = DeviceSize.values, + this.deviceSizes = const [], }); @override - State createState() => _AdaptiveSizeProvidertState(); + State createState() => _SizeRefProvidertState(); } -class _AdaptiveSizeProvidertState extends State { +class _SizeRefProvidertState extends State { final _initialized = Completer(); - late final ValueNotifier _notifier; + late final ValueNotifier _notifier; @override void didChangeDependencies() { if (!_initialized.isCompleted) { _initialize(context); + _initialized.complete(true); return; } _update(context); @@ -42,28 +42,25 @@ class _AdaptiveSizeProvidertState extends State { void _update(BuildContext context) { final size = MediaQuery.sizeOf(context); - if (size == _notifier.value.realScreenSize && - widget.deviceSizes == _notifier.value.deviceSizes) { + final mostSimilarSize = widget.deviceSizes.mostSimilarTo(size); + final scale = SizeScale(size, mostSimilarSize); + final SizeRef sizeRef = (scale: scale, size: mostSimilarSize); + if (sizeRef == _notifier.value) { return; } - - _notifier.value = AdaptiveSizeData.fromSize( - size, - deviceSizes: widget.deviceSizes, - ); + _notifier.value = sizeRef; } - void _initialize(BuildContext size) { - final adaptiveSize = AdaptiveSizeData.fromSize( - MediaQuery.sizeOf(context), - deviceSizes: widget.deviceSizes, - ); - _notifier = ValueNotifier(adaptiveSize); - _initialized.complete(true); + void _initialize(BuildContext context) { + final size = MediaQuery.sizeOf(context); + final mostSimilarSize = widget.deviceSizes.mostSimilarTo(size); + final scale = SizeScale(size, mostSimilarSize); + final SizeRef sizeRef = (scale: scale, size: mostSimilarSize); + _notifier = ValueNotifier(sizeRef); } @override - Widget build(context) => InheritedAdaptiveSize( + Widget build(context) => InheritedSizeRef( notifier: RestrictedNotifier(_notifier), child: widget.child, ); diff --git a/packages/adaptive_style/lib/src/device_size.dart b/packages/adaptive_style/lib/src/device_size.dart index b2eb5df1..063aee3f 100644 --- a/packages/adaptive_style/lib/src/device_size.dart +++ b/packages/adaptive_style/lib/src/device_size.dart @@ -1,27 +1,36 @@ import 'dart:ui' show Size; +import 'package:flutter/widgets.dart' show Orientation; /// a collection of well known screen dimesions /// as offered by [chrome device toolbar] -enum DeviceSize { - iphoneSE(Size(375, 667)), - iphoneXR(Size(414, 896)), - iphone12PRO(Size(390, 844)), - iphone14PRO(Size(430, 932)), - pixel7(Size(412, 915)), - galaxyS8Plus(Size(360, 740)), - galaxyS20Ultra(Size(412, 915)), - iPadMini(Size(768, 1024)), - iPadAir(Size(820, 1180)), - iPadPro(Size(1024, 1366)), - surfacePro7(Size(912, 1368)), - surfaceDuo(Size(540, 720)), - galaxyFold(Size(280, 653)), - galaxyA51(Size(412, 914)), - galaxyA71(Size(412, 914)), - nestHub(Size(1024, 600)), - nestHubMax(Size(1280, 800)); - const DeviceSize(this.size); +// enum Orientation { +// landscape, +// portrait, +// } - final Size size; +extension type const DeviceSize._(Size value) implements Size { + const DeviceSize(Size value) : this._(value); + static const iphoneSE = DeviceSize(Size(375, 667)); + static const iphoneXR = DeviceSize(Size(414, 896)); + static const iphone12PRO = DeviceSize(Size(390, 844)); + static const iphone14PRO = DeviceSize(Size(430, 932)); + static const pixel7 = DeviceSize(Size(412, 915)); + static const galaxyS8Plus = DeviceSize(Size(360, 740)); + static const galaxyS20Ultra = DeviceSize(Size(412, 915)); + static const iPadMini = DeviceSize(Size(768, 1024)); + static const iPadAir = DeviceSize(Size(820, 1180)); + static const iPadPro = DeviceSize(Size(1024, 1366)); + static const surfacePro7 = DeviceSize(Size(912, 1368)); + static const surfaceDuo = DeviceSize(Size(540, 720)); + static const galaxyFold = DeviceSize(Size(280, 653)); + static const galaxyA51 = DeviceSize(Size(412, 914)); + static const galaxyA71 = DeviceSize(Size(412, 914)); + static const nestHub = DeviceSize(Size(1024, 600)); + static const nestHubMax = DeviceSize(Size(1280, 800)); + + Orientation get orientation => + aspectRatio <= 1 ? Orientation.portrait : Orientation.landscape; + + DeviceSize flip() => DeviceSize(Size(height, width)); } diff --git a/packages/adaptive_style/lib/src/extension.dart b/packages/adaptive_style/lib/src/extension.dart index bc1e180c..f394f6e8 100644 --- a/packages/adaptive_style/lib/src/extension.dart +++ b/packages/adaptive_style/lib/src/extension.dart @@ -3,15 +3,9 @@ import 'dart:ui' show Size; import 'device_size.dart'; -extension DeviceSizeSizesX on List { - List get sizes => [for (final value in this) value.size]; -} - extension ClosestDimentionScaleX on T { - double closestDimentionScale(T size) => math.min( - width / size.width, - height / size.height, - ); + double closestDimentionScale(T size) => + math.min(size.width / width, size.height / height); } extension ReverseScaleX on T { @@ -30,8 +24,8 @@ extension MostSimilarSizeX on Iterable { deviceSize, /// this is arbitrary but seems to work - (size.aspectRatio - deviceSize.size.aspectRatio).abs() * - math.sqrt((size.area - deviceSize.size.area).abs()) + (size.aspectRatio - deviceSize.aspectRatio).abs() * + math.sqrt((size.area - deviceSize.area).abs()) ) ]..sort( (a, b) => a.$2.compareTo(b.$2), @@ -41,11 +35,11 @@ extension MostSimilarSizeX on Iterable { } } -extension DeviceSizeAverageSizeX on List { +extension DeviceSizeAverageSizeX on Iterable { Size get averagePortrait { final portraits = [ for (final device in this) - if (device.size.aspectRatio < 1) device.size, + if (device.aspectRatio < 1) device, ]; var height = .0; var width = .0; @@ -63,7 +57,7 @@ extension DeviceSizeAverageSizeX on List { Size get averageLandscape { final landscapes = [ for (final device in this) - if (device.size.aspectRatio > 1) device.size, + if (device.aspectRatio > 1) device, ]; var height = .0; var width = .0; diff --git a/packages/adaptive_style/lib/src/inherited_adaptive_size.dart b/packages/adaptive_style/lib/src/inherited_adaptive_size.dart index 440f96ee..3eca21d4 100644 --- a/packages/adaptive_style/lib/src/inherited_adaptive_size.dart +++ b/packages/adaptive_style/lib/src/inherited_adaptive_size.dart @@ -1,11 +1,12 @@ import 'package:flutter/widgets.dart'; import 'package:yak_flutter/yak_flutter.dart'; -import 'adaptive_size.dart'; +import 'size_ref.dart'; -class InheritedAdaptiveSize - extends InheritedRestrictedNotifier { - const InheritedAdaptiveSize({ +typedef AdaptiveSizeNotifier = RestrictedNotifier; + +class InheritedSizeRef extends InheritedRestrictedNotifier { + const InheritedSizeRef({ super.key, required super.notifier, required super.child, @@ -13,7 +14,7 @@ class InheritedAdaptiveSize static AdaptiveSizeNotifier? maybeOf(BuildContext context) { final inherited = - context.dependOnInheritedWidgetOfExactType(); + context.dependOnInheritedWidgetOfExactType(); if (inherited == null) { return null; diff --git a/packages/adaptive_style/lib/src/size_ref.dart b/packages/adaptive_style/lib/src/size_ref.dart new file mode 100644 index 00000000..e28c73aa --- /dev/null +++ b/packages/adaptive_style/lib/src/size_ref.dart @@ -0,0 +1,6 @@ +import 'size_scale.dart'; +import 'device_size.dart'; + +/// a record representing the closest supported size +/// and the scale between the real size and the supported size +typedef SizeRef = ({DeviceSize size, SizeScale scale}); diff --git a/packages/adaptive_style/lib/src/size_scale.dart b/packages/adaptive_style/lib/src/size_scale.dart new file mode 100644 index 00000000..5b55963d --- /dev/null +++ b/packages/adaptive_style/lib/src/size_scale.dart @@ -0,0 +1,17 @@ +import 'dart:ui' show Offset, Size; +import 'dart:math' as math show min, max; + +extension type SizeScale._(Offset scale) implements Offset { + double get withScale => scale.dx; + double get heightScale => scale.dy; + + double get min => math.min(withScale, heightScale); + double get max => math.max(withScale, heightScale); + + factory SizeScale(Size real, Size ideal) => SizeScale._( + Offset( + real.width / ideal.width, + real.height / ideal.height, + ), + ); +} diff --git a/packages/adaptive_style/pubspec.yaml b/packages/adaptive_style/pubspec.yaml index b6e17420..291d86c6 100644 --- a/packages/adaptive_style/pubspec.yaml +++ b/packages/adaptive_style/pubspec.yaml @@ -10,7 +10,8 @@ dependencies: flutter: sdk: flutter yak_flutter: ^3.0.2 - + meta: ^1.11.0 + dev_dependencies: flutter_test: sdk: flutter From 75d55083acd33f84932e3fb2193a15b8d7c96e4b Mon Sep 17 00:00:00 2001 From: Francesco Iapicca Date: Sun, 28 Apr 2024 21:31:01 +0300 Subject: [PATCH 08/15] [adaptive_style] remove unused data --- .../adaptive_style/lib/adaptive_style.dart | 1 - .../lib/src/_adaptive_size.dart | 42 ------------------- 2 files changed, 43 deletions(-) delete mode 100644 packages/adaptive_style/lib/src/_adaptive_size.dart diff --git a/packages/adaptive_style/lib/adaptive_style.dart b/packages/adaptive_style/lib/adaptive_style.dart index 9fcec9df..92d8c075 100644 --- a/packages/adaptive_style/lib/adaptive_style.dart +++ b/packages/adaptive_style/lib/adaptive_style.dart @@ -2,7 +2,6 @@ library adaptive_style; export 'src/adaptive_size_builder.dart'; export 'src/adaptive_size_provider.dart'; -export 'src/_adaptive_size.dart'; export 'src/device_size.dart'; export 'src/extension.dart'; export 'src/inherited_adaptive_size.dart'; diff --git a/packages/adaptive_style/lib/src/_adaptive_size.dart b/packages/adaptive_style/lib/src/_adaptive_size.dart deleted file mode 100644 index aff0d970..00000000 --- a/packages/adaptive_style/lib/src/_adaptive_size.dart +++ /dev/null @@ -1,42 +0,0 @@ -// import 'package:flutter/widgets.dart'; -// import 'package:yak_flutter/yak_flutter.dart'; -// import 'extension.dart'; -// import 'device_size.dart'; - -// typedef AdaptiveSizeNotifier = RestrictedNotifier; - -// final class AdaptiveSizeData { -// const AdaptiveSizeData({ -// required this.screenSize, -// required this.scale, -// required this.availableSizes, -// required this.mostSimilarDeviceSize, -// }); - -// // factory AdaptiveSizeData.fromSize( -// // Size size, { -// // List availableSizes = DeviceSize.values, -// // }) { -// // final mostSimilarDeviceSize = availableSizes.mostSimilarTo(size); -// // final scale = mostSimilarDeviceSize.size.closestDimentionScale(size); - -// // return AdaptiveSizeData( -// // availableSizes: availableSizes, -// // realScreenSize: size, -// // mostSimilarDeviceSize: mostSimilarDeviceSize, -// // scale: scale, -// // ); -// // } - -// final Size screenSize; -// final Offset widthScale, ; -// final List availableSizes; -// final DeviceSize mostSimilarDeviceSize; - -// @override -// bool operator ==(Object other) => -// other is AdaptiveSizeData && other.hashCode == hashCode; - -// @override -// int get hashCode => Object.hash(screenSize, availableSizes); -// } From b46a86620fee3c1f088c8fac8e4da689e7557caa Mon Sep 17 00:00:00 2001 From: Francesco Iapicca Date: Sun, 28 Apr 2024 21:33:56 +0300 Subject: [PATCH 09/15] [adaptive_style] simplify example --- examples/adaptive_style_example/lib/main.dart | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/examples/adaptive_style_example/lib/main.dart b/examples/adaptive_style_example/lib/main.dart index 5198ad95..b89de9cd 100644 --- a/examples/adaptive_style_example/lib/main.dart +++ b/examples/adaptive_style_example/lib/main.dart @@ -21,16 +21,11 @@ class MyHomePage extends StatelessWidget { @override Widget build(context) => SizeRefBuilder(builder: (context, sizeRef) { return switch (sizeRef.size) { - (DeviceSize.iphoneSE) => Center( - child: SizedBox.fromSize( - size: sizeRef.size * sizeRef.scale.min, - child: Scaffold( - backgroundColor: Colors.black, - body: Center( - child: FlutterLogo( - size: 100 * sizeRef.scale.min, - ), - ), + (DeviceSize.iphoneSE) => Scaffold( + backgroundColor: Colors.black, + body: Center( + child: FlutterLogo( + size: 100 * sizeRef.scale.min, ), ), ), From 77d9bf6e16b06f1ac5224ee80d76ad24cf058b57 Mon Sep 17 00:00:00 2001 From: Francesco Iapicca Date: Tue, 30 Apr 2024 21:55:45 +0300 Subject: [PATCH 10/15] [adaptive_style] implemented AdaptiveStack --- examples/adaptive_style_example/lib/main.dart | 42 +++-- .../adaptive_style/lib/adaptive_style.dart | 12 +- .../lib/src/adaptive_edge_positioned.dart | 106 ++++++++++++ .../lib/src/adaptive_mediaquery_widget.dart | 26 +++ .../lib/src/adaptive_size_builder.dart | 27 --- .../lib/src/adaptive_stack.dart | 87 ++++++++++ .../lib/src/adaptive_widget.dart | 28 ++++ .../lib/src/adaptive_widget_builder.dart | 33 ++++ .../lib/src/inherited_adaptive_size.dart | 25 --- .../lib/src/inherited_scale_ref.dart | 31 ++++ .../lib/src/{size_ref.dart => scale_ref.dart} | 2 +- ..._provider.dart => scale_ref_provider.dart} | 41 +++-- packages/yak_flutter/lib/src/widgets/all.dart | 5 +- .../src/widgets/inherited_value_notifier.dart | 2 + .../src/widgets/scalable_edge_positioned.dart | 45 +++-- .../widgets/scalable_sized_positioned.dart | 32 ++++ .../lib/src/widgets/scalable_widget.dart | 156 ------------------ .../lib/src/widgets/sized_positioned.dart | 26 +++ 18 files changed, 450 insertions(+), 276 deletions(-) create mode 100644 packages/adaptive_style/lib/src/adaptive_edge_positioned.dart create mode 100644 packages/adaptive_style/lib/src/adaptive_mediaquery_widget.dart delete mode 100644 packages/adaptive_style/lib/src/adaptive_size_builder.dart create mode 100644 packages/adaptive_style/lib/src/adaptive_stack.dart create mode 100644 packages/adaptive_style/lib/src/adaptive_widget.dart create mode 100644 packages/adaptive_style/lib/src/adaptive_widget_builder.dart delete mode 100644 packages/adaptive_style/lib/src/inherited_adaptive_size.dart create mode 100644 packages/adaptive_style/lib/src/inherited_scale_ref.dart rename packages/adaptive_style/lib/src/{size_ref.dart => scale_ref.dart} (73%) rename packages/adaptive_style/lib/src/{adaptive_size_provider.dart => scale_ref_provider.dart} (52%) create mode 100644 packages/yak_flutter/lib/src/widgets/scalable_sized_positioned.dart delete mode 100644 packages/yak_flutter/lib/src/widgets/scalable_widget.dart create mode 100644 packages/yak_flutter/lib/src/widgets/sized_positioned.dart diff --git a/examples/adaptive_style_example/lib/main.dart b/examples/adaptive_style_example/lib/main.dart index b89de9cd..f65478c7 100644 --- a/examples/adaptive_style_example/lib/main.dart +++ b/examples/adaptive_style_example/lib/main.dart @@ -7,9 +7,9 @@ class MyApplication extends StatelessWidget { const MyApplication({super.key}); @override - Widget build(context) => const SizeRefProvider( - deviceSizes: [DeviceSize.iphoneSE], - child: MaterialApp( + Widget build(context) => ScaleRefProvider( + deviceSizes: const [DeviceSize.iphoneSE], + builder: (context) => const MaterialApp( home: MyHomePage(), ), ); @@ -19,21 +19,27 @@ class MyHomePage extends StatelessWidget { const MyHomePage({super.key}); @override - Widget build(context) => SizeRefBuilder(builder: (context, sizeRef) { - return switch (sizeRef.size) { - (DeviceSize.iphoneSE) => Scaffold( - backgroundColor: Colors.black, - body: Center( - child: FlutterLogo( - size: 100 * sizeRef.scale.min, - ), + Widget build(context) => Material( + child: AdaptiveStackBuilder( + builder: (context, scaleRef, parentSize) => [ + AdaptiveEdgePositioned( + parentSize: parentSize, + padding: const EdgeInsets.all(10), + dimension: 100, + edge: Edge.bottom, + builder: (context, scaleRef, parentSize) => const ColoredBox( + color: Colors.red, ), ), - _ => const Scaffold( - body: Center( - child: FlutterLogo(size: 100), - ), - ), - }; - }); + ], + ), + // child: AdaptiveEdgePositioned( + // padding: const EdgeInsets.all(10), + // dimension: 100, + // edge: Edge.bottom, + // builder: (context, scaleRef, _) => const ColoredBox( + // color: Colors.red, + // ), + // ), + ); } diff --git a/packages/adaptive_style/lib/adaptive_style.dart b/packages/adaptive_style/lib/adaptive_style.dart index 92d8c075..cedc31f5 100644 --- a/packages/adaptive_style/lib/adaptive_style.dart +++ b/packages/adaptive_style/lib/adaptive_style.dart @@ -1,7 +1,13 @@ library adaptive_style; -export 'src/adaptive_size_builder.dart'; -export 'src/adaptive_size_provider.dart'; +export 'src/adaptive_edge_positioned.dart'; +export 'src/adaptive_mediaquery_widget.dart'; +export 'src/adaptive_stack.dart'; +export 'src/adaptive_widget_builder.dart'; +export 'src/adaptive_widget.dart'; export 'src/device_size.dart'; export 'src/extension.dart'; -export 'src/inherited_adaptive_size.dart'; +export 'src/inherited_scale_ref.dart'; +export 'src/scale_ref_provider.dart'; +export 'src/scale_ref.dart'; +export 'src/size_scale.dart'; diff --git a/packages/adaptive_style/lib/src/adaptive_edge_positioned.dart b/packages/adaptive_style/lib/src/adaptive_edge_positioned.dart new file mode 100644 index 00000000..968554c5 --- /dev/null +++ b/packages/adaptive_style/lib/src/adaptive_edge_positioned.dart @@ -0,0 +1,106 @@ +import 'package:flutter/widgets.dart'; + +import 'adaptive_widget.dart'; +import 'adaptive_widget_builder.dart'; + +enum Edge { top, bottom, left, right } + +class AdaptiveEdgePositioned extends AdaptiveWidget { + final EdgeInsets padding; + final double dimension; + final AdaptiveWidgetBuilder _builder; + final Edge edge; + const AdaptiveEdgePositioned({ + super.key, + this.padding = EdgeInsets.zero, + required AdaptiveWidgetBuilder builder, + required this.dimension, + required this.edge, + required this.parentSize, + }) : _builder = builder; + + @override + Widget builder(context, scaleRef, parentSize) => + + /// TODO should calculate parent data + ScalableEdgePositioned( + edge: edge, + dimension: dimension, + scale: scaleRef.scale.min, + child: _builder(context, scaleRef, parentSize), + ); + + @override + final Size parentSize; +} + +class ScalableEdgePositioned extends Positioned { + final EdgeInsets padding; + final double dimension; + final double scale; + final Edge edge; + const ScalableEdgePositioned({ + required this.edge, + required super.child, + required this.dimension, + this.padding = EdgeInsets.zero, + required this.scale, + super.key, + }); + + @override + @protected + @visibleForTesting + double? get left { + return switch (edge) { + (Edge.right) => null, + _ => padding.left * scale, + }; + } + + @override + @protected + @visibleForTesting + double? get right { + return switch (edge) { + (Edge.left) => null, + _ => padding.right * scale, + }; + } + + @override + @protected + @visibleForTesting + double? get bottom { + return switch (edge) { + (Edge.top) => null, + _ => padding.bottom * scale, + }; + } + + @override + @protected + @visibleForTesting + double? get top { + return switch (edge) { + (Edge.bottom) => null, + _ => padding.top * scale, + }; + } + + @override + double? get height { + return switch (edge) { + (Edge.bottom || Edge.top) => (dimension - padding.vertical) * scale, + _ => null, + }; + } + + @override + double? get width { + return switch (edge) { + (Edge.left || Edge.right) => (dimension - padding.horizontal) * scale, + _ => null, + }; + } +} diff --git a/packages/adaptive_style/lib/src/adaptive_mediaquery_widget.dart b/packages/adaptive_style/lib/src/adaptive_mediaquery_widget.dart new file mode 100644 index 00000000..1b1b56d2 --- /dev/null +++ b/packages/adaptive_style/lib/src/adaptive_mediaquery_widget.dart @@ -0,0 +1,26 @@ +import 'package:flutter/widgets.dart'; +import 'package:yak_flutter/yak_flutter.dart'; + +class AdaptiveMediaQueryWidget extends MediaQueryWidget { + final double scale; + final WidgetBuilder builder; + const AdaptiveMediaQueryWidget({ + required this.builder, + required this.scale, + super.key, + }); + + @override + Widget build(context) => key == null + ? Builder(builder: builder) + : KeyedSubtree( + key: key, + child: Builder(builder: builder), + ); + + @override + MediaQueryData mediaQueryFrom(MediaQueryData mediaQuery) => + mediaQuery.copyWith( + textScaler: TextScaler.linear(scale), + ); +} diff --git a/packages/adaptive_style/lib/src/adaptive_size_builder.dart b/packages/adaptive_style/lib/src/adaptive_size_builder.dart deleted file mode 100644 index e45c8a2e..00000000 --- a/packages/adaptive_style/lib/src/adaptive_size_builder.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:flutter/widgets.dart'; - -import 'inherited_adaptive_size.dart'; -import 'size_ref.dart'; - -typedef SizeRefBuild = Widget Function(BuildContext, SizeRef); - -class SizeRefBuilder extends StatelessWidget { - final SizeRefBuild builder; - const SizeRefBuilder({ - super.key, - required this.builder, - }); - - @override - Widget build(context) { - final sizeRefNotifier = InheritedSizeRef.maybeOf(context); - if (sizeRefNotifier == null) { - throw Exception('InheritedSizeRef not found in BuildContext'); - } - - return ValueListenableBuilder( - valueListenable: sizeRefNotifier as ValueNotifier, - builder: (context, value, _) => builder(context, value), - ); - } -} diff --git a/packages/adaptive_style/lib/src/adaptive_stack.dart b/packages/adaptive_style/lib/src/adaptive_stack.dart new file mode 100644 index 00000000..4fc21249 --- /dev/null +++ b/packages/adaptive_style/lib/src/adaptive_stack.dart @@ -0,0 +1,87 @@ +import 'package:flutter/widgets.dart'; +import 'package:meta/meta.dart'; + +import 'adaptive_widget.dart'; +import 'adaptive_widget_builder.dart'; +import 'scale_ref.dart'; + +class AdaptiveStack extends AdaptiveWidget { + final AlignmentDirectional alignment; + final AdaptiveChildrenBuilder _builder; + final Clip clipBehavior; + final StackFit fit; + final TextDirection? textDirection; + + const AdaptiveStack({ + required AdaptiveChildrenBuilder builder, + required this.parentSize, + this.alignment = AlignmentDirectional.topStart, + this.clipBehavior = Clip.hardEdge, + this.fit = StackFit.loose, + this.textDirection, + super.key, + }) : _builder = builder; + + @override + Widget builder(context, scaleRef, parentSize) => SizedBox.fromSize( + size: parentSize, + child: AdaptiveStackLayout( + parentSize: parentSize, + scaleRef: scaleRef, + context: context, + builder: _builder, + alignment: alignment, + textDirection: textDirection, + fit: fit, + clipBehavior: clipBehavior, + ), + ); + + @override + final Size parentSize; +} + +class AdaptiveStackLayout extends Stack { + final AdaptiveChildrenBuilder builder; + final Size parentSize; + final ScaleRef scaleRef; + final BuildContext context; + const AdaptiveStackLayout({ + super.key, + super.alignment = AlignmentDirectional.topStart, + super.textDirection, + super.fit = StackFit.loose, + super.clipBehavior = Clip.hardEdge, + required this.builder, + required this.parentSize, + required this.context, + required this.scaleRef, + }); + + @override + @nonVirtual + List get children => builder(context, scaleRef, parentSize); +} + +class AdaptiveStackBuilder extends LayoutBuilder { + final AlignmentDirectional alignment; + final Clip clipBehavior; + final StackFit fit; + final TextDirection? textDirection; + AdaptiveStackBuilder({ + required AdaptiveChildrenBuilder builder, + this.alignment = AlignmentDirectional.topStart, + this.clipBehavior = Clip.hardEdge, + this.fit = StackFit.loose, + this.textDirection, + super.key, + }) : super( + builder: (context, contraints) => AdaptiveStack( + alignment: alignment, + builder: builder, + clipBehavior: clipBehavior, + fit: fit, + parentSize: contraints.biggest, + textDirection: textDirection, + )); +} diff --git a/packages/adaptive_style/lib/src/adaptive_widget.dart b/packages/adaptive_style/lib/src/adaptive_widget.dart new file mode 100644 index 00000000..d3fc6694 --- /dev/null +++ b/packages/adaptive_style/lib/src/adaptive_widget.dart @@ -0,0 +1,28 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; + +import 'inherited_scale_ref.dart'; +import 'scale_ref.dart'; + +abstract class AdaptiveWidget extends Widget implements StatelessWidget { + const AdaptiveWidget({super.key}); + + @override + StatelessElement createElement() => StatelessElement(this); + + @override + @protected + @nonVirtual + Widget build(BuildContext context) { + final maybeScaleRef = InheritedScaleRef.maybeOf(context); + if (maybeScaleRef == null) { + throw Exception('ScaleRef not found in context,' + ' make sure to have "ScaleRefProvider" in your Widget Tree'); + } + return builder(context, maybeScaleRef, parentSize); + } + + Size get parentSize; + + Widget builder(BuildContext context, ScaleRef scaleRef, Size parentSize); +} diff --git a/packages/adaptive_style/lib/src/adaptive_widget_builder.dart b/packages/adaptive_style/lib/src/adaptive_widget_builder.dart new file mode 100644 index 00000000..3a7e120d --- /dev/null +++ b/packages/adaptive_style/lib/src/adaptive_widget_builder.dart @@ -0,0 +1,33 @@ +import 'package:flutter/widgets.dart'; + +import 'scale_ref.dart'; + +/// a function that return a `Widget` from a +/// `BuildContext` a `ScaleRef` and the `Size` of the parent Widget +typedef AdaptiveWidgetBuilder = Widget Function( + BuildContext context, + ScaleRef scaleRef, + Size parentSize, +); + +mixin AdaptiveWidgetBuilderMixin on Widget { + Widget builder( + BuildContext context, + ScaleRef scaleRef, + Size parentSize, + ); +} + +typedef AdaptiveChildrenBuilder = List Function( + BuildContext context, + ScaleRef scaleRef, + Size parentSize, +); + +mixin AdaptiveChildrenBuilderrMixin on Widget { + List builder( + BuildContext context, + ScaleRef scaleRef, + Size parentSize, + ); +} diff --git a/packages/adaptive_style/lib/src/inherited_adaptive_size.dart b/packages/adaptive_style/lib/src/inherited_adaptive_size.dart deleted file mode 100644 index 3eca21d4..00000000 --- a/packages/adaptive_style/lib/src/inherited_adaptive_size.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:yak_flutter/yak_flutter.dart'; - -import 'size_ref.dart'; - -typedef AdaptiveSizeNotifier = RestrictedNotifier; - -class InheritedSizeRef extends InheritedRestrictedNotifier { - const InheritedSizeRef({ - super.key, - required super.notifier, - required super.child, - }); - - static AdaptiveSizeNotifier? maybeOf(BuildContext context) { - final inherited = - context.dependOnInheritedWidgetOfExactType(); - - if (inherited == null) { - return null; - } - - return inherited.notifier; - } -} diff --git a/packages/adaptive_style/lib/src/inherited_scale_ref.dart b/packages/adaptive_style/lib/src/inherited_scale_ref.dart new file mode 100644 index 00000000..a1cd4588 --- /dev/null +++ b/packages/adaptive_style/lib/src/inherited_scale_ref.dart @@ -0,0 +1,31 @@ +import 'package:flutter/widgets.dart'; +import 'package:yak_flutter/yak_flutter.dart'; + +import 'scale_ref.dart'; + +typedef ScaleRefNotifier = RestrictedNotifier; + +class InheritedScaleRef extends InheritedWidget { + const InheritedScaleRef({ + super.key, + required this.scaleRef, + required super.child, + }); + + final ScaleRef scaleRef; + + static ScaleRef? maybeOf(BuildContext context) { + final inherited = + context.dependOnInheritedWidgetOfExactType(); + + if (inherited == null) { + return null; + } + + return inherited.scaleRef; + } + + @override + bool updateShouldNotify(covariant InheritedScaleRef oldWidget) => + oldWidget.scaleRef != scaleRef; +} diff --git a/packages/adaptive_style/lib/src/size_ref.dart b/packages/adaptive_style/lib/src/scale_ref.dart similarity index 73% rename from packages/adaptive_style/lib/src/size_ref.dart rename to packages/adaptive_style/lib/src/scale_ref.dart index e28c73aa..30b553f3 100644 --- a/packages/adaptive_style/lib/src/size_ref.dart +++ b/packages/adaptive_style/lib/src/scale_ref.dart @@ -3,4 +3,4 @@ import 'device_size.dart'; /// a record representing the closest supported size /// and the scale between the real size and the supported size -typedef SizeRef = ({DeviceSize size, SizeScale scale}); +typedef ScaleRef = ({DeviceSize idealSize, SizeScale scale}); diff --git a/packages/adaptive_style/lib/src/adaptive_size_provider.dart b/packages/adaptive_style/lib/src/scale_ref_provider.dart similarity index 52% rename from packages/adaptive_style/lib/src/adaptive_size_provider.dart rename to packages/adaptive_style/lib/src/scale_ref_provider.dart index 800de418..e697f1ab 100644 --- a/packages/adaptive_style/lib/src/adaptive_size_provider.dart +++ b/packages/adaptive_style/lib/src/scale_ref_provider.dart @@ -2,26 +2,25 @@ import 'dart:async'; import 'package:adaptive_style/adaptive_style.dart'; import 'package:flutter/widgets.dart'; -import 'package:yak_flutter/yak_flutter.dart'; -import 'size_ref.dart'; -import 'size_scale.dart'; -class SizeRefProvider extends StatefulWidget { - final Widget child; +import 'inherited_scale_ref.dart'; + +class ScaleRefProvider extends StatefulWidget { + final WidgetBuilder builder; final List deviceSizes; - const SizeRefProvider({ + const ScaleRefProvider({ super.key, - required this.child, + required this.builder, this.deviceSizes = const [], }); @override - State createState() => _SizeRefProvidertState(); + State createState() => _ScaleRefProvidertState(); } -class _SizeRefProvidertState extends State { +class _ScaleRefProvidertState extends State { final _initialized = Completer(); - late final ValueNotifier _notifier; + late final ValueNotifier _notifier; @override void didChangeDependencies() { @@ -44,24 +43,30 @@ class _SizeRefProvidertState extends State { final size = MediaQuery.sizeOf(context); final mostSimilarSize = widget.deviceSizes.mostSimilarTo(size); final scale = SizeScale(size, mostSimilarSize); - final SizeRef sizeRef = (scale: scale, size: mostSimilarSize); - if (sizeRef == _notifier.value) { + final ScaleRef scaleRef = (scale: scale, idealSize: mostSimilarSize); + if (scaleRef == _notifier.value) { return; } - _notifier.value = sizeRef; + _notifier.value = scaleRef; } void _initialize(BuildContext context) { final size = MediaQuery.sizeOf(context); final mostSimilarSize = widget.deviceSizes.mostSimilarTo(size); final scale = SizeScale(size, mostSimilarSize); - final SizeRef sizeRef = (scale: scale, size: mostSimilarSize); - _notifier = ValueNotifier(sizeRef); + final ScaleRef scaleRef = (scale: scale, idealSize: mostSimilarSize); + _notifier = ValueNotifier(scaleRef); } @override - Widget build(context) => InheritedSizeRef( - notifier: RestrictedNotifier(_notifier), - child: widget.child, + Widget build(context) => ValueListenableBuilder( + valueListenable: _notifier, + builder: (context, scaleRef, _) => InheritedScaleRef( + scaleRef: scaleRef, + child: AdaptiveMediaQueryWidget( + builder: widget.builder, + scale: _notifier.value.scale.min, + ), + ), ); } diff --git a/packages/yak_flutter/lib/src/widgets/all.dart b/packages/yak_flutter/lib/src/widgets/all.dart index 8552d04d..dbcb2f2d 100644 --- a/packages/yak_flutter/lib/src/widgets/all.dart +++ b/packages/yak_flutter/lib/src/widgets/all.dart @@ -1,6 +1,7 @@ export 'edge_positioned.dart'; +export 'inherited_value_notifier.dart'; export 'media_query_widget.dart'; export 'preferred_size_themed.dart'; export 'scalable_edge_positioned.dart'; -export 'scalable_widget.dart'; -export 'inherited_value_notifier.dart'; +export 'scalable_sized_positioned.dart'; +export 'sized_positioned.dart'; diff --git a/packages/yak_flutter/lib/src/widgets/inherited_value_notifier.dart b/packages/yak_flutter/lib/src/widgets/inherited_value_notifier.dart index 441b75b9..e7ad4223 100644 --- a/packages/yak_flutter/lib/src/widgets/inherited_value_notifier.dart +++ b/packages/yak_flutter/lib/src/widgets/inherited_value_notifier.dart @@ -10,6 +10,8 @@ extension type RestrictedNotifier(ValueNotifier notifier) { HandleListener get removeListener => notifier.removeListener; T get value => notifier.value; set value(T value) => notifier.value = value; + + ValueNotifier unrestricted() => this as ValueNotifier; } class InheritedRestrictedNotifier extends InheritedWidget { diff --git a/packages/yak_flutter/lib/src/widgets/scalable_edge_positioned.dart b/packages/yak_flutter/lib/src/widgets/scalable_edge_positioned.dart index e3c81409..c8598cbe 100644 --- a/packages/yak_flutter/lib/src/widgets/scalable_edge_positioned.dart +++ b/packages/yak_flutter/lib/src/widgets/scalable_edge_positioned.dart @@ -1,61 +1,54 @@ import 'package:flutter/widgets.dart'; -import 'scalable_widget.dart'; - @immutable abstract class ScalableEdgePositioned extends Positioned { final EdgeInsets padding; final double dimension; final double scale; - ScalableEdgePositioned({ - required child, + const ScalableEdgePositioned({ + required super.child, required this.dimension, required this.padding, required this.scale, super.key, - }) : super( - child: ScalebleWidget( - scale: scale, - child: child, - ), - ); + }); - factory ScalableEdgePositioned.top({ + const factory ScalableEdgePositioned.top({ required Widget child, double dimension, double scale, EdgeInsets padding, Key key, - }) = _EdgePositioneTop; + }) = _EdgePositionedTop; - factory ScalableEdgePositioned.bottom({ + const factory ScalableEdgePositioned.bottom({ required Widget child, double dimension, double scale, EdgeInsets padding, Key key, - }) = _EdgePositioneBottom; + }) = _EdgePositionedBottom; - factory ScalableEdgePositioned.left({ + const factory ScalableEdgePositioned.left({ required Widget child, double dimension, double scale, EdgeInsets padding, Key key, - }) = _EdgePositioneLeft; + }) = _EdgePositionedLeft; - factory ScalableEdgePositioned.right({ + const factory ScalableEdgePositioned.right({ required Widget child, double dimension, double scale, EdgeInsets padding, Key key, - }) = _EdgePositioneRight; + }) = _EdgePositionedRight; } @immutable -class _EdgePositioneBottom extends ScalableEdgePositioned { - _EdgePositioneBottom({ +class _EdgePositionedBottom extends ScalableEdgePositioned { + const _EdgePositionedBottom({ required super.child, super.dimension = 0, super.scale = 1, @@ -74,8 +67,8 @@ class _EdgePositioneBottom extends ScalableEdgePositioned { } @immutable -class _EdgePositioneTop extends ScalableEdgePositioned { - _EdgePositioneTop({ +class _EdgePositionedTop extends ScalableEdgePositioned { + const _EdgePositionedTop({ required super.child, super.dimension = 0, super.scale = 1, @@ -94,8 +87,8 @@ class _EdgePositioneTop extends ScalableEdgePositioned { } @immutable -class _EdgePositioneLeft extends ScalableEdgePositioned { - _EdgePositioneLeft({ +class _EdgePositionedLeft extends ScalableEdgePositioned { + const _EdgePositionedLeft({ required super.child, super.dimension = 0, super.scale = 1, @@ -114,8 +107,8 @@ class _EdgePositioneLeft extends ScalableEdgePositioned { } @immutable -class _EdgePositioneRight extends ScalableEdgePositioned { - _EdgePositioneRight({ +class _EdgePositionedRight extends ScalableEdgePositioned { + const _EdgePositionedRight({ required super.child, super.dimension = 0, super.scale = 1, diff --git a/packages/yak_flutter/lib/src/widgets/scalable_sized_positioned.dart b/packages/yak_flutter/lib/src/widgets/scalable_sized_positioned.dart new file mode 100644 index 00000000..5ca5a797 --- /dev/null +++ b/packages/yak_flutter/lib/src/widgets/scalable_sized_positioned.dart @@ -0,0 +1,32 @@ +import 'package:flutter/widgets.dart'; + +class ScalableSizedPositioned extends Positioned { + final EdgeInsets position; + final Size size; + final Size parent; + final double scale; + const ScalableSizedPositioned({ + required this.scale, + required this.parent, + required super.child, + required this.size, + required this.position, + super.key, + }); + + @override + double get left => + parent.width / 2 - ((position.left + (size.width / 2)) * scale); + + @override + double get right => + parent.width / 2 - ((position.right + (size.width / 2)) * scale); + + @override + double get top => + parent.height / 2 - ((position.top + (size.height / 2)) * scale); + + @override + double get bottom => + parent.height / 2 - ((position.bottom + (size.height / 2)) * scale); +} diff --git a/packages/yak_flutter/lib/src/widgets/scalable_widget.dart b/packages/yak_flutter/lib/src/widgets/scalable_widget.dart deleted file mode 100644 index d41fd4fd..00000000 --- a/packages/yak_flutter/lib/src/widgets/scalable_widget.dart +++ /dev/null @@ -1,156 +0,0 @@ -import 'dart:ui' as ui; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter/rendering.dart'; - -import 'media_query_widget.dart'; - -class ScalebleWidget extends SingleChildRenderObjectWidget { - final double scale; - - ScalebleWidget({ - super.key, - required this.scale, - required Widget child, - }) : super( - child: ScalableMediaQueryWidget( - scale: scale, - child: child, - ), - ); - - @override - ScaleTransform createRenderObject(context) => ScaleTransform( - scale: scale, - textDirection: Directionality.maybeOf(context), - ); - - @override - void updateRenderObject(context, ScaleTransform renderObject) => - renderObject.scale(scale); -} - -class ScaleTransform extends RenderProxyBox { - ScaleTransform({ - TextDirection? textDirection, - required double scale, - }) : _scale = scale, - _transform = _scaleToMatrix(scale); - - @override - bool get alwaysNeedsCompositing => child != null; - - static Matrix4 _scaleToMatrix(double scale) => - Matrix4.diagonal3Values(scale, scale, 1.0); - - Matrix4 _transform; - double _scale; - void scale(double value) { - if (_scale == value) { - return; - } - _scale = value; - _transform = _scaleToMatrix(value); - markNeedsPaint(); - markNeedsSemanticsUpdate(); - } - - @override - void paint(PaintingContext context, Offset offset) { - if (child == null) { - return; - } - final transform = _effectiveTransform; - - final Offset? childOffset = MatrixUtils.getAsTranslation(transform); - if (childOffset != null) { - super.paint(context, offset + childOffset); - layer = null; - return; - } - - final determinant = transform.determinant(); - if (determinant == 0 || !determinant.isFinite) { - layer = null; - return; - } - layer = context.pushTransform( - needsCompositing, - offset, - transform, - super.paint, - oldLayer: layer is TransformLayer ? layer as TransformLayer? : null, - ); - } - - void setIdentity() { - _transform.setIdentity(); - markNeedsPaint(); - markNeedsSemanticsUpdate(); - } - - Matrix4 get _effectiveTransform => Matrix4.identity()..multiply(_transform); - - @override - bool hitTest(BoxHitTestResult result, {required Offset position}) { - return hitTestChildren(result, position: position); - } - - @override - bool hitTestChildren(result, {required Offset position}) => - result.addWithPaintTransform( - transform: _effectiveTransform, - position: position, - hitTest: (result, position) => super.hitTestChildren( - result, - position: position, - ), - ); - - @override - void applyPaintTransform(child, transform) => - transform.multiply(_effectiveTransform); - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties.add(DoubleProperty('scale', _scale)); - } -} - -class ScalableMediaQueryWidget extends MediaQueryWidget { - final double scale; - final Widget child; - - const ScalableMediaQueryWidget({ - required this.child, - required this.scale, - super.key, - }); - - @override - Widget build(context) => child; - - @override - @nonVirtual - MediaQueryData mediaQueryFrom(mediaQuery) => mediaQuery.copyWith( - size: mediaQuery.size * scale, - viewInsets: mediaQuery.viewInsets * scale, - systemGestureInsets: mediaQuery.systemGestureInsets * scale, - viewPadding: mediaQuery.viewPadding * scale, - padding: mediaQuery.padding * scale, - devicePixelRatio: mediaQuery.devicePixelRatio * scale, - displayFeatures: [ - for (final feature in mediaQuery.displayFeatures) - ui.DisplayFeature( - type: feature.type, - state: feature.state, - bounds: Rect.fromPoints( - feature.bounds.topLeft * scale, - feature.bounds.bottomRight * scale, - ), - ), - ], - ); -} diff --git a/packages/yak_flutter/lib/src/widgets/sized_positioned.dart b/packages/yak_flutter/lib/src/widgets/sized_positioned.dart new file mode 100644 index 00000000..54dc83e1 --- /dev/null +++ b/packages/yak_flutter/lib/src/widgets/sized_positioned.dart @@ -0,0 +1,26 @@ +import 'package:flutter/widgets.dart'; + +class SizedPositioned extends Positioned { + final EdgeInsets position; + final Size size; + final Size parent; + const SizedPositioned({ + required this.parent, + required super.child, + required this.size, + required this.position, + super.key, + }); + + @override + double get left => parent.width / 2 - position.left - size.width / 2; + + @override + double get right => parent.width / 2 - position.right - size.width / 2; + + @override + double get top => parent.width / 2 - position.top - size.height / 2; + + @override + double get bottom => parent.width / 2 - position.bottom - size.height / 2; +} From 7f9c579bc00a361b99520168a31d1677d26bad03 Mon Sep 17 00:00:00 2001 From: Francesco Iapicca Date: Wed, 1 May 2024 16:46:49 +0300 Subject: [PATCH 11/15] [adaptive_style] implemented AdaptiveAnchorPositioned --- examples/adaptive_style_example/lib/main.dart | 32 +++-- .../adaptive_style/lib/adaptive_style.dart | 2 + .../lib/src/adaptive_edge_positioned.dart | 111 +++----------- .../adaptive_style/lib/src/anchor_edges.dart | 135 ++++++++++++++++++ .../lib/src/positioned_data.dart | 100 +++++++++++++ 5 files changed, 276 insertions(+), 104 deletions(-) create mode 100644 packages/adaptive_style/lib/src/anchor_edges.dart create mode 100644 packages/adaptive_style/lib/src/positioned_data.dart diff --git a/examples/adaptive_style_example/lib/main.dart b/examples/adaptive_style_example/lib/main.dart index f65478c7..00ba2bdb 100644 --- a/examples/adaptive_style_example/lib/main.dart +++ b/examples/adaptive_style_example/lib/main.dart @@ -22,24 +22,26 @@ class MyHomePage extends StatelessWidget { Widget build(context) => Material( child: AdaptiveStackBuilder( builder: (context, scaleRef, parentSize) => [ - AdaptiveEdgePositioned( + AdaptiveAnchorPositioned( parentSize: parentSize, - padding: const EdgeInsets.all(10), - dimension: 100, - edge: Edge.bottom, - builder: (context, scaleRef, parentSize) => const ColoredBox( - color: Colors.red, + data: const AdaptiveAnchorData( + dimension: 100, + edges: AnchorEdges.bottom(), ), - ), + builder: (context, scaleRef, parentSize) => AdaptiveStack( + parentSize: parentSize, + builder: (context, scaleRef, parentSize) => [ + const SizedBox.expand(child: ColoredBox(color: Colors.red)), + Center( + child: SizedBox.square( + dimension: 50 * scaleRef.scale.min, + child: const FlutterLogo(), + ), + ), + ], + ), + ) ], ), - // child: AdaptiveEdgePositioned( - // padding: const EdgeInsets.all(10), - // dimension: 100, - // edge: Edge.bottom, - // builder: (context, scaleRef, _) => const ColoredBox( - // color: Colors.red, - // ), - // ), ); } diff --git a/packages/adaptive_style/lib/adaptive_style.dart b/packages/adaptive_style/lib/adaptive_style.dart index cedc31f5..ae3b53b9 100644 --- a/packages/adaptive_style/lib/adaptive_style.dart +++ b/packages/adaptive_style/lib/adaptive_style.dart @@ -11,3 +11,5 @@ export 'src/inherited_scale_ref.dart'; export 'src/scale_ref_provider.dart'; export 'src/scale_ref.dart'; export 'src/size_scale.dart'; +export 'src/anchor_edges.dart'; +export 'src/positioned_data.dart'; diff --git a/packages/adaptive_style/lib/src/adaptive_edge_positioned.dart b/packages/adaptive_style/lib/src/adaptive_edge_positioned.dart index 968554c5..959dda23 100644 --- a/packages/adaptive_style/lib/src/adaptive_edge_positioned.dart +++ b/packages/adaptive_style/lib/src/adaptive_edge_positioned.dart @@ -2,105 +2,38 @@ import 'package:flutter/widgets.dart'; import 'adaptive_widget.dart'; import 'adaptive_widget_builder.dart'; +import 'positioned_data.dart'; -enum Edge { top, bottom, left, right } - -class AdaptiveEdgePositioned extends AdaptiveWidget { - final EdgeInsets padding; - final double dimension; +class AdaptiveAnchorPositioned extends AdaptiveWidget { + final AdaptiveAnchorData data; final AdaptiveWidgetBuilder _builder; - final Edge edge; - const AdaptiveEdgePositioned({ + + const AdaptiveAnchorPositioned({ super.key, - this.padding = EdgeInsets.zero, required AdaptiveWidgetBuilder builder, - required this.dimension, - required this.edge, + required this.data, required this.parentSize, }) : _builder = builder; - @override - Widget builder(context, scaleRef, parentSize) => - - /// TODO should calculate parent data - ScalableEdgePositioned( - edge: edge, - dimension: dimension, - scale: scaleRef.scale.min, - child: _builder(context, scaleRef, parentSize), - ); - @override final Size parentSize; -} - -class ScalableEdgePositioned extends Positioned { - final EdgeInsets padding; - final double dimension; - final double scale; - final Edge edge; - const ScalableEdgePositioned({ - required this.edge, - required super.child, - required this.dimension, - this.padding = EdgeInsets.zero, - required this.scale, - super.key, - }); - - @override - @protected - @visibleForTesting - double? get left { - return switch (edge) { - (Edge.right) => null, - _ => padding.left * scale, - }; - } - - @override - @protected - @visibleForTesting - double? get right { - return switch (edge) { - (Edge.left) => null, - _ => padding.right * scale, - }; - } - - @override - @protected - @visibleForTesting - double? get bottom { - return switch (edge) { - (Edge.top) => null, - _ => padding.bottom * scale, - }; - } - - @override - @protected - @visibleForTesting - double? get top { - return switch (edge) { - (Edge.bottom) => null, - _ => padding.top * scale, - }; - } - - @override - double? get height { - return switch (edge) { - (Edge.bottom || Edge.top) => (dimension - padding.vertical) * scale, - _ => null, - }; - } @override - double? get width { - return switch (edge) { - (Edge.left || Edge.right) => (dimension - padding.horizontal) * scale, - _ => null, - }; + Widget builder(context, scaleRef, parentSize) { + final positionedData = data.positioned( + parentSize: parentSize, + scale: scaleRef.scale.min, + ); + return Positioned( + left: positionedData.edges.left, + right: positionedData.edges.right, + top: positionedData.edges.top, + bottom: positionedData.edges.bottom, + child: _builder( + context, + scaleRef, + positionedData.size, + ), + ); } } diff --git a/packages/adaptive_style/lib/src/anchor_edges.dart b/packages/adaptive_style/lib/src/anchor_edges.dart new file mode 100644 index 00000000..5c12bec3 --- /dev/null +++ b/packages/adaptive_style/lib/src/anchor_edges.dart @@ -0,0 +1,135 @@ +import 'package:meta/meta.dart'; + +abstract class AnchorEdges { + const AnchorEdges(); + double? get top; + double? get bottom; + double? get left; + double? get right; + + const factory AnchorEdges.top({ + double top, + double left, + double right, + }) = AnchorEdgesTop; + + const factory AnchorEdges.bottom({ + double bottom, + double left, + double right, + }) = AnchorEdgesBottom; + + const factory AnchorEdges.left({ + double top, + double bottom, + double left, + }) = AnchorEdgesLeft; + + const factory AnchorEdges.right({ + double top, + double bottom, + double right, + }) = AnchorEdgesRight; + + AnchorEdges scale(double scale); + + @override + @nonVirtual + bool operator ==(other) => other is AnchorEdges && other.hashCode == hashCode; + + @override + @nonVirtual + int get hashCode => Object.hashAll([top, bottom, left, right]); +} + +class AnchorEdgesLeft extends AnchorEdges { + const AnchorEdgesLeft({ + this.bottom = 0, + this.left = 0, + this.top = 0, + }); + @override + final double bottom; + @override + final double top; + @override + final double left; + @override + final right = null; + + @override + AnchorEdgesLeft scale(double scale) => AnchorEdgesLeft( + bottom: bottom * scale, + top: top * scale, + left: left * scale, + ); +} + +class AnchorEdgesRight implements AnchorEdges { + const AnchorEdgesRight({ + this.bottom = 0, + this.right = 0, + this.top = 0, + }); + @override + final double bottom; + @override + final double top; + @override + final double right; + @override + final left = null; + + @override + AnchorEdgesRight scale(double scale) => AnchorEdgesRight( + bottom: bottom * scale, + top: top * scale, + right: right * scale, + ); +} + +class AnchorEdgesTop implements AnchorEdges { + const AnchorEdgesTop({ + this.top = 0, + this.right = 0, + this.left = 0, + }); + @override + final double left; + @override + final double top; + @override + final double right; + @override + final bottom = null; + + @override + AnchorEdgesTop scale(double scale) => AnchorEdgesTop( + left: left * scale, + top: top * scale, + right: right * scale, + ); +} + +class AnchorEdgesBottom implements AnchorEdges { + const AnchorEdgesBottom({ + this.bottom = 0, + this.right = 0, + this.left = 0, + }); + @override + final double left; + @override + final double bottom; + @override + final double right; + @override + final top = null; + + @override + AnchorEdgesBottom scale(double scale) => AnchorEdgesBottom( + left: left * scale, + bottom: bottom * scale, + right: right * scale, + ); +} diff --git a/packages/adaptive_style/lib/src/positioned_data.dart b/packages/adaptive_style/lib/src/positioned_data.dart new file mode 100644 index 00000000..196a2022 --- /dev/null +++ b/packages/adaptive_style/lib/src/positioned_data.dart @@ -0,0 +1,100 @@ +import 'package:flutter/widgets.dart'; + +import 'anchor_edges.dart'; + +final class PositionedData { + final EdgeInsets edges; + final Size size; + + const PositionedData(this.edges, this.size); + + @override + bool operator ==(other) => + other is PositionedData && other.hashCode == hashCode; + + @override + int get hashCode => Object.hash(edges, size); +} + +final class AdaptiveAnchorData { + final AnchorEdges edges; + final double dimension; + + const AdaptiveAnchorData({ + required this.edges, + required this.dimension, + }); + + PositionedData positioned({ + double scale = 0, + required Size parentSize, + }) { + final scaledEdges = edges.scale(scale); + final scaledDimension = dimension * scale; + switch (scaledEdges) { + case (AnchorEdgesTop topEdge): + return PositionedData( + EdgeInsets.only( + bottom: parentSize.height - scaledDimension - topEdge.top, + top: topEdge.top, + left: topEdge.left, + right: topEdge.right, + ), + Size( + parentSize.width - topEdge.left - topEdge.right, + scaledDimension, + ), + ); + case (AnchorEdgesBottom bottomEdge): + return PositionedData( + EdgeInsets.only( + top: parentSize.height - scaledDimension - bottomEdge.bottom, + bottom: bottomEdge.bottom, + left: bottomEdge.left, + right: bottomEdge.right, + ), + Size( + parentSize.width - bottomEdge.left - bottomEdge.right, + scaledDimension, + ), + ); + + case (AnchorEdgesLeft leftEdge): + return PositionedData( + EdgeInsets.only( + top: leftEdge.top, + bottom: leftEdge.bottom, + left: leftEdge.left, + right: parentSize.width - scaledDimension - leftEdge.left, + ), + Size( + scaledDimension, + parentSize.height - leftEdge.top - leftEdge.bottom, + ), + ); + case (AnchorEdgesRight rightEdge): + return PositionedData( + EdgeInsets.only( + top: rightEdge.top, + bottom: rightEdge.bottom, + left: parentSize.width - scaledDimension - rightEdge.right, + right: rightEdge.right, + ), + Size( + scaledDimension, + parentSize.height - rightEdge.top - rightEdge.bottom, + ), + ); + + default: + throw Exception('undefined AnchorEdges'); + } + } + + @override + bool operator ==(other) => + other is AdaptiveAnchorData && other.hashCode == hashCode; + + @override + int get hashCode => Object.hashAll([edges, dimension]); +} From 9390df6f25f8df07f583f93b8a10d2d6efafeb3a Mon Sep 17 00:00:00 2001 From: Francesco Iapicca Date: Wed, 1 May 2024 16:58:12 +0300 Subject: [PATCH 12/15] [adaptive_style] changes from adaptive_mediaquery_widget to scale_mediaquery_widget --- .../lib/src/adaptive_mediaquery_widget.dart | 28 +++++++++++++++---- .../lib/src/scale_ref_provider.dart | 9 ++++-- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/packages/adaptive_style/lib/src/adaptive_mediaquery_widget.dart b/packages/adaptive_style/lib/src/adaptive_mediaquery_widget.dart index 1b1b56d2..8ffcd432 100644 --- a/packages/adaptive_style/lib/src/adaptive_mediaquery_widget.dart +++ b/packages/adaptive_style/lib/src/adaptive_mediaquery_widget.dart @@ -1,14 +1,30 @@ import 'package:flutter/widgets.dart'; import 'package:yak_flutter/yak_flutter.dart'; -class AdaptiveMediaQueryWidget extends MediaQueryWidget { +typedef ScaleMediaQuery = MediaQueryData Function( + MediaQueryData mediaQuery, + double scale, +); + +MediaQueryData _defaultScaleMediaQuery( + MediaQueryData mediaQuery, + double scale, +) => + mediaQuery.copyWith( + textScaler: TextScaler.linear(scale), + ); + +class ScaleMediaQueryWidget extends MediaQueryWidget { final double scale; final WidgetBuilder builder; - const AdaptiveMediaQueryWidget({ + final ScaleMediaQuery scaleMediaQuery; + + const ScaleMediaQueryWidget({ required this.builder, required this.scale, + ScaleMediaQuery? scaleMediaQuery, super.key, - }); + }) : scaleMediaQuery = scaleMediaQuery ?? _defaultScaleMediaQuery; @override Widget build(context) => key == null @@ -19,8 +35,8 @@ class AdaptiveMediaQueryWidget extends MediaQueryWidget { ); @override - MediaQueryData mediaQueryFrom(MediaQueryData mediaQuery) => - mediaQuery.copyWith( - textScaler: TextScaler.linear(scale), + MediaQueryData mediaQueryFrom(MediaQueryData mediaQuery) => scaleMediaQuery( + mediaQuery, + scale, ); } diff --git a/packages/adaptive_style/lib/src/scale_ref_provider.dart b/packages/adaptive_style/lib/src/scale_ref_provider.dart index e697f1ab..15d06985 100644 --- a/packages/adaptive_style/lib/src/scale_ref_provider.dart +++ b/packages/adaptive_style/lib/src/scale_ref_provider.dart @@ -1,9 +1,12 @@ import 'dart:async'; -import 'package:adaptive_style/adaptive_style.dart'; import 'package:flutter/widgets.dart'; - +import 'adaptive_mediaquery_widget.dart'; +import 'device_size.dart'; import 'inherited_scale_ref.dart'; +import 'scale_ref.dart'; +import 'extension.dart'; +import 'size_scale.dart'; class ScaleRefProvider extends StatefulWidget { final WidgetBuilder builder; @@ -63,7 +66,7 @@ class _ScaleRefProvidertState extends State { valueListenable: _notifier, builder: (context, scaleRef, _) => InheritedScaleRef( scaleRef: scaleRef, - child: AdaptiveMediaQueryWidget( + child: ScaleMediaQueryWidget( builder: widget.builder, scale: _notifier.value.scale.min, ), From 5a2f73d72d7ce28e6135a271a9a029b222642ae2 Mon Sep 17 00:00:00 2001 From: Francesco Iapicca Date: Wed, 1 May 2024 16:58:28 +0300 Subject: [PATCH 13/15] [adaptive_style] changes from adaptive_mediaquery_widget to scale_mediaquery_widget (1) --- packages/adaptive_style/lib/adaptive_style.dart | 2 +- ...tive_mediaquery_widget.dart => scale_mediaquery_widget.dart} | 0 packages/adaptive_style/lib/src/scale_ref_provider.dart | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename packages/adaptive_style/lib/src/{adaptive_mediaquery_widget.dart => scale_mediaquery_widget.dart} (100%) diff --git a/packages/adaptive_style/lib/adaptive_style.dart b/packages/adaptive_style/lib/adaptive_style.dart index ae3b53b9..2e0cbe72 100644 --- a/packages/adaptive_style/lib/adaptive_style.dart +++ b/packages/adaptive_style/lib/adaptive_style.dart @@ -1,7 +1,7 @@ library adaptive_style; export 'src/adaptive_edge_positioned.dart'; -export 'src/adaptive_mediaquery_widget.dart'; +export 'src/scale_mediaquery_widget.dart'; export 'src/adaptive_stack.dart'; export 'src/adaptive_widget_builder.dart'; export 'src/adaptive_widget.dart'; diff --git a/packages/adaptive_style/lib/src/adaptive_mediaquery_widget.dart b/packages/adaptive_style/lib/src/scale_mediaquery_widget.dart similarity index 100% rename from packages/adaptive_style/lib/src/adaptive_mediaquery_widget.dart rename to packages/adaptive_style/lib/src/scale_mediaquery_widget.dart diff --git a/packages/adaptive_style/lib/src/scale_ref_provider.dart b/packages/adaptive_style/lib/src/scale_ref_provider.dart index 15d06985..5029eefe 100644 --- a/packages/adaptive_style/lib/src/scale_ref_provider.dart +++ b/packages/adaptive_style/lib/src/scale_ref_provider.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:flutter/widgets.dart'; -import 'adaptive_mediaquery_widget.dart'; +import 'scale_mediaquery_widget.dart'; import 'device_size.dart'; import 'inherited_scale_ref.dart'; import 'scale_ref.dart'; From da3288734c9f9d78265b2da9e5e250482261d02c Mon Sep 17 00:00:00 2001 From: Francesco Iapicca Date: Wed, 1 May 2024 18:52:35 +0300 Subject: [PATCH 14/15] [adaptive_style] add readme --- examples/adaptive_style_example/README.md | 15 ++---- packages/adaptive_style/README.md | 54 +++++++++---------- .../adaptive_style/lib/adaptive_style.dart | 2 +- ...ed.dart => adaptive_achor_positioned.dart} | 0 4 files changed, 31 insertions(+), 40 deletions(-) rename packages/adaptive_style/lib/src/{adaptive_edge_positioned.dart => adaptive_achor_positioned.dart} (100%) diff --git a/examples/adaptive_style_example/README.md b/examples/adaptive_style_example/README.md index 674b5a65..4fd883d2 100644 --- a/examples/adaptive_style_example/README.md +++ b/examples/adaptive_style_example/README.md @@ -1,16 +1,7 @@ # adaptive_style_example -A new Flutter project. +### an example app for `adaptive_style` -## Getting Started +see more on [pub.dev][pub] -This project is a starting point for a Flutter application. - -A few resources to get you started if this is your first Flutter project: - -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) - -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +[pub]: https://pub.dev/packages/adaptive_style diff --git a/packages/adaptive_style/README.md b/packages/adaptive_style/README.md index 02fe8eca..b48abd14 100644 --- a/packages/adaptive_style/README.md +++ b/packages/adaptive_style/README.md @@ -1,39 +1,39 @@ - -TODO: Put a short description of the package here that helps potential users -know whether this package might be useful for them. +### adaptive_style -## Features +a collection of widgets to build scalable UI -TODO: List what your package can do. Maybe include images, gifs, or videos. +# WARNING +this package is an early release and under development +breaking changes are most likely to occur -## Getting started +### how does it work? -TODO: List prerequisites and provide or point to information on how to -start using the package. +- wrap your app in a `ScaleRefProvider` +- provide a list of supported sizes +``` dart +ScaleRefProvider( + deviceSizes: const [DeviceSize.iphoneSE], +///... +``` +- use `SizeRef` to get + - the closest supported size + - the scale between the device size and the supported size -## Usage +### what's included? + +- `AdaptiveWidget` the base to build your own custom adaptive widget +- `AdaptiveStack` just like a `Stack` but builds it's children as an `AdaptiveWidget` +- `AdaptiveAnchorPositioned` a `Positioned` that place itself at one of the parent's edges -TODO: Include short and useful examples for package users. Add longer examples -to `/example` folder. -```dart -const like = 'sample'; -``` -## Additional information +[melos badge]: https://img.shields.io/badge/maintained%20with-melos-f700ff.svg +[melos]: https://github.com/invertase/melos +[license]: https://opensource.org/licenses/MIT +[license badge]: https://img.shields.io/badge/license-MIT-blue.svg -TODO: Tell users more about the package: where to find more information, how to -contribute to the package, how to file issues, what response they can expect -from the package authors, and more. diff --git a/packages/adaptive_style/lib/adaptive_style.dart b/packages/adaptive_style/lib/adaptive_style.dart index 2e0cbe72..48a4d670 100644 --- a/packages/adaptive_style/lib/adaptive_style.dart +++ b/packages/adaptive_style/lib/adaptive_style.dart @@ -1,6 +1,6 @@ library adaptive_style; -export 'src/adaptive_edge_positioned.dart'; +export 'src/adaptive_achor_positioned.dart'; export 'src/scale_mediaquery_widget.dart'; export 'src/adaptive_stack.dart'; export 'src/adaptive_widget_builder.dart'; diff --git a/packages/adaptive_style/lib/src/adaptive_edge_positioned.dart b/packages/adaptive_style/lib/src/adaptive_achor_positioned.dart similarity index 100% rename from packages/adaptive_style/lib/src/adaptive_edge_positioned.dart rename to packages/adaptive_style/lib/src/adaptive_achor_positioned.dart From a6ec9a6a9ff4e287b4d369e88722f9898ec738d0 Mon Sep 17 00:00:00 2001 From: Francesco Iapicca Date: Wed, 1 May 2024 18:57:26 +0300 Subject: [PATCH 15/15] remove pub-lish.yml from .github/workflow --- .github/workflows/pub-lish.yml | 38 ---------------------------------- 1 file changed, 38 deletions(-) delete mode 100644 .github/workflows/pub-lish.yml diff --git a/.github/workflows/pub-lish.yml b/.github/workflows/pub-lish.yml deleted file mode 100644 index be3f7748..00000000 --- a/.github/workflows/pub-lish.yml +++ /dev/null @@ -1,38 +0,0 @@ -# name: 'pub-lish' -# on: -# push: -# tags: -# - 'v[0-9]+.[0-9]+.[0-9]+*' -# branches: -# - main -# jobs: -# pub-lish: -# name: 'publish to pub.dev' -# runs-on: ubuntu-latest -# timeout-minutes: 10 -# steps: -# - name: 'checkout' -# uses: actions/checkout@v4 -# - name: 'setup flutter' -# uses: subosito/flutter-action@v2 -# with: -# channel: 'stable' -# - name: 'setup credentials' -# run: | -# mkdir $HOME/.pub-cache/ && \ -# touch $HOME/.pub-cache/credentials.json && \ -# cat < $HOME/.pub-cache/credentials.json -# { -# "accessToken":"${{ secrets.PUB_DEV_PUBLISH_ACCESS_TOKEN }}", -# "refreshToken":"${{ secrets.PUB_DEV_PUBLISH_REFRESH_TOKEN }}", -# "tokenEndpoint":"https://accounts.google.com/o/oauth2/token", -# "scopes":["https://www.googleapis.com/auth/userinfo.email","openid"], -# "expiration":${{ secrets.PUB_DEV_PUBLISH_EXPIRATION }} -# } -# EOF -# - name: 'install melos' -# uses: bluefireteam/melos-action@v1 -# - name: 'publishing' -# run: | -# yes | melos publish --no-dry-run --git-tag-version - \ No newline at end of file