pub.dev score: license, dartdoc, analysis, tests
- Add MIT license - Add dartdoc to all public APIs (UxKeyboard, BendBox, Bezier, Json) - Add analysis_options.yaml with flutter_lints and public_member_api_docs - Add repository, issue_tracker, topics to pubspec.yaml - Expand package description - Fix BendBox constructor (const, super.key) - Remove unused import in keyboard.dart - Replace boilerplate tests with UxKeyboard smoke tests - Move ux dependency to example's dependencies (not dev_dependencies)
This commit is contained in:
22
LICENSE
22
LICENSE
@@ -1 +1,21 @@
|
|||||||
TODO: Add your license here.
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 SWIPELAB LTD
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|||||||
5
analysis_options.yaml
Normal file
5
analysis_options.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
linter:
|
||||||
|
rules:
|
||||||
|
public_member_api_docs: true
|
||||||
@@ -1 +1 @@
|
|||||||
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"ux","path":"/Users/agra/projects/ux/","native_build":true,"dependencies":[],"dev_dependency":true}],"android":[{"name":"ux","path":"/Users/agra/projects/ux/","native_build":true,"dependencies":[],"dev_dependency":true}],"macos":[],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"ux","dependencies":[]}],"date_created":"2026-04-15 13:13:36.363037","version":"3.41.5","swift_package_manager_enabled":{"ios":false,"macos":false}}
|
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"ux","path":"/Users/agra/projects/ux/","native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"ux","path":"/Users/agra/projects/ux/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"ux","dependencies":[]}],"date_created":"2026-04-16 18:49:44.385595","version":"3.41.5","swift_package_manager_enabled":{"ios":false,"macos":false}}
|
||||||
@@ -62,6 +62,14 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_lints:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: flutter_lints
|
||||||
|
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.0"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -91,6 +99,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
version: "3.0.2"
|
||||||
|
lints:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: lints
|
||||||
|
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.0"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -177,12 +193,12 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.10"
|
version: "0.7.10"
|
||||||
ux:
|
ux:
|
||||||
dependency: "direct dev"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: ".."
|
path: ".."
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "0.2.0"
|
version: "0.3.0"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -8,56 +8,14 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
|
||||||
cupertino_icons: 1.0.8
|
cupertino_icons: 1.0.8
|
||||||
|
|
||||||
dev_dependencies:
|
|
||||||
flutter_test:
|
|
||||||
sdk: flutter
|
|
||||||
|
|
||||||
ux:
|
ux:
|
||||||
path: ../
|
path: ../
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
dev_dependencies:
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
flutter_lints: ^6.0.0
|
||||||
|
flutter_test:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
# The following section is specific to Flutter.
|
|
||||||
flutter:
|
flutter:
|
||||||
|
|
||||||
# The following line ensures that the Material Icons font is
|
|
||||||
# included with your application, so that you can use the icons in
|
|
||||||
# the material Icons class.
|
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|
||||||
# To add assets to your application, add an assets section, like this:
|
|
||||||
# assets:
|
|
||||||
# - images/a_dot_burr.jpeg
|
|
||||||
# - images/a_dot_ham.jpeg
|
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
|
||||||
# https://flutter.dev/assets-and-images/#resolution-aware.
|
|
||||||
|
|
||||||
# For details regarding adding assets from package dependencies, see
|
|
||||||
# https://flutter.dev/assets-and-images/#from-packages
|
|
||||||
|
|
||||||
# To add custom fonts to your application, add a fonts section here,
|
|
||||||
# in this "flutter" section. Each entry in this list should have a
|
|
||||||
# "family" key with the font family name, and a "fonts" key with a
|
|
||||||
# list giving the asset and other descriptors for the font. For
|
|
||||||
# example:
|
|
||||||
# fonts:
|
|
||||||
# - family: Schyler
|
|
||||||
# fonts:
|
|
||||||
# - asset: fonts/Schyler-Regular.ttf
|
|
||||||
# - asset: fonts/Schyler-Italic.ttf
|
|
||||||
# style: italic
|
|
||||||
# - family: Trajan Pro
|
|
||||||
# fonts:
|
|
||||||
# - asset: fonts/TrajanPro.ttf
|
|
||||||
# - asset: fonts/TrajanPro_Bold.ttf
|
|
||||||
# weight: 700
|
|
||||||
#
|
|
||||||
# For details regarding fonts from package dependencies,
|
|
||||||
# see https://flutter.dev/custom-fonts/#from-packages
|
|
||||||
|
|||||||
@@ -1,26 +1,11 @@
|
|||||||
// This is a basic Flutter widget test.
|
|
||||||
//
|
|
||||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
|
||||||
// utility that Flutter provides. For example, you can send tap and scroll
|
|
||||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
|
||||||
// tree, read text, and verify that the values of widget properties are correct.
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:ux_example/main.dart';
|
import 'package:ux_example/main.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('Verify Platform version', (WidgetTester tester) async {
|
testWidgets('ChatScreen renders', (WidgetTester tester) async {
|
||||||
// Build our app and trigger a frame.
|
await tester.pumpWidget(MaterialApp(home: ChatScreen()));
|
||||||
await tester.pumpWidget(MyApp());
|
expect(find.text('UxKeyboard Chat'), findsOneWidget);
|
||||||
|
expect(find.text('Type a message...'), findsOneWidget);
|
||||||
// Verify that platform version is retrieved.
|
|
||||||
expect(
|
|
||||||
find.byWidgetPredicate(
|
|
||||||
(Widget widget) =>
|
|
||||||
widget is Text && (widget.data?.startsWith('Running on:') ?? false),
|
|
||||||
),
|
|
||||||
findsOneWidget,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,19 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// A widget that paints a filled shape with curved (bent) edges.
|
||||||
|
///
|
||||||
|
/// Each edge bends inward by the amount specified in [inward].
|
||||||
class BendBox extends StatelessWidget {
|
class BendBox extends StatelessWidget {
|
||||||
|
/// How far each edge bends inward. Positive values bend toward the center.
|
||||||
final EdgeInsets inward;
|
final EdgeInsets inward;
|
||||||
|
|
||||||
|
/// The fill color of the shape.
|
||||||
final Color color;
|
final Color color;
|
||||||
|
|
||||||
BendBox({this.inward = const EdgeInsets.all(0), this.color = Colors.red});
|
/// Creates a [BendBox] with the given [inward] bend and [color].
|
||||||
|
const BendBox({super.key, this.inward = const EdgeInsets.all(0), this.color = Colors.red});
|
||||||
|
|
||||||
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return CustomPaint(painter: _BendBoxPainter(inward: inward, color: color));
|
return CustomPaint(painter: _BendBoxPainter(inward: inward, color: color));
|
||||||
}
|
}
|
||||||
@@ -20,6 +28,7 @@ class _BendBoxPainter extends CustomPainter {
|
|||||||
required this.color,
|
required this.color,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
void paint(Canvas canvas, Size size) {
|
void paint(Canvas canvas, Size size) {
|
||||||
final paint = Paint()
|
final paint = Paint()
|
||||||
..style = PaintingStyle.fill
|
..style = PaintingStyle.fill
|
||||||
@@ -39,5 +48,6 @@ class _BendBoxPainter extends CustomPainter {
|
|||||||
canvas.drawPath(path, paint);
|
canvas.drawPath(path, paint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
bool shouldRepaint(CustomPainter oldDelegate) => true;
|
bool shouldRepaint(CustomPainter oldDelegate) => true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
|
/// Computes the approximate arc length of a [Bezier] curve by sampling.
|
||||||
double bezierLength(Bezier bezier, [double steps = 10]) {
|
double bezierLength(Bezier bezier, [double steps = 10]) {
|
||||||
assert(steps != 0);
|
assert(steps != 0);
|
||||||
final step = 1 / steps;
|
final step = 1 / steps;
|
||||||
@@ -14,36 +15,56 @@ double bezierLength(Bezier bezier, [double steps = 10]) {
|
|||||||
return length;
|
return length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Base class for parametric bezier curves.
|
||||||
|
///
|
||||||
|
/// Evaluate a point on the curve with [point] at parameter `t` in [0, 1].
|
||||||
abstract class Bezier {
|
abstract class Bezier {
|
||||||
|
/// Returns the point on the curve at parameter [t] (0 = start, 1 = end).
|
||||||
Offset point(double t);
|
Offset point(double t);
|
||||||
|
|
||||||
|
/// The approximate arc length of the curve.
|
||||||
double get length => bezierLength(this);
|
double get length => bezierLength(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A straight line segment from [p0] to [p1].
|
||||||
class LinearBezier extends Bezier {
|
class LinearBezier extends Bezier {
|
||||||
Offset p0, p1;
|
/// The start point.
|
||||||
|
Offset p0;
|
||||||
|
|
||||||
|
/// The end point.
|
||||||
|
Offset p1;
|
||||||
|
|
||||||
|
/// Creates a linear bezier from [p0] to [p1].
|
||||||
LinearBezier(this.p0, this.p1);
|
LinearBezier(this.p0, this.p1);
|
||||||
|
|
||||||
|
@override
|
||||||
Offset point(double t) {
|
Offset point(double t) {
|
||||||
return p0 + (p1 - p0) * t;
|
return p0 + (p1 - p0) * t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
double get length => (p1 - p0).distance;
|
double get length => (p1 - p0).distance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A quadratic bezier curve with one control point.
|
||||||
class QuadraticBezier extends Bezier {
|
class QuadraticBezier extends Bezier {
|
||||||
double _quadraticBezier(double t, double p0, double p1, double p2) {
|
double _quadraticBezier(double t, double p0, double p1, double p2) {
|
||||||
//final lt = 1 - t;
|
|
||||||
//return lt * (lt * p0 + t * p1) + t * (lt * p1 + t * p2);
|
|
||||||
//return pow(1 - t, 2) * p0 + 2 * (1 - t) * t * p1 + pow(t, 2) * p2;
|
|
||||||
return p1 + pow(1 - t, 2) * (p0 - p1) + pow(t, 2) * (p2 - p1);
|
return p1 + pow(1 - t, 2) * (p0 - p1) + pow(t, 2) * (p2 - p1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Offset p0, p1, p2;
|
/// The start point.
|
||||||
|
Offset p0;
|
||||||
|
|
||||||
|
/// The control point.
|
||||||
|
Offset p1;
|
||||||
|
|
||||||
|
/// The end point.
|
||||||
|
Offset p2;
|
||||||
|
|
||||||
|
/// Creates a quadratic bezier from [p0] to [p2] with control point [p1].
|
||||||
QuadraticBezier(this.p0, this.p1, this.p2);
|
QuadraticBezier(this.p0, this.p1, this.p2);
|
||||||
|
|
||||||
|
@override
|
||||||
Offset point(double t) {
|
Offset point(double t) {
|
||||||
return Offset(
|
return Offset(
|
||||||
_quadraticBezier(t, p0.dx, p1.dx, p2.dx),
|
_quadraticBezier(t, p0.dx, p1.dx, p2.dx),
|
||||||
@@ -52,6 +73,7 @@ class QuadraticBezier extends Bezier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A cubic bezier curve with two control points.
|
||||||
class CubicBezier extends Bezier {
|
class CubicBezier extends Bezier {
|
||||||
double _cubicBezier(double t, double p0, double p1, double p2, double p3) {
|
double _cubicBezier(double t, double p0, double p1, double p2, double p3) {
|
||||||
return pow(1 - t, 3) * p0 +
|
return pow(1 - t, 3) * p0 +
|
||||||
@@ -60,35 +82,53 @@ class CubicBezier extends Bezier {
|
|||||||
pow(t, 3) * p3;
|
pow(t, 3) * p3;
|
||||||
}
|
}
|
||||||
|
|
||||||
Offset p0, p1, p2, p3;
|
/// The start point.
|
||||||
|
Offset p0;
|
||||||
|
|
||||||
|
/// The first control point.
|
||||||
|
Offset p1;
|
||||||
|
|
||||||
|
/// The second control point.
|
||||||
|
Offset p2;
|
||||||
|
|
||||||
|
/// The end point.
|
||||||
|
Offset p3;
|
||||||
|
|
||||||
|
/// Creates a cubic bezier from [p0] to [p3] with control points [p1] and [p2].
|
||||||
CubicBezier(this.p0, this.p1, this.p2, this.p3);
|
CubicBezier(this.p0, this.p1, this.p2, this.p3);
|
||||||
|
|
||||||
Offset point(double t) =>
|
@override
|
||||||
Offset(
|
Offset point(double t) => Offset(
|
||||||
_cubicBezier(t, p0.dx, p1.dx, p2.dx, p3.dx),
|
_cubicBezier(t, p0.dx, p1.dx, p2.dx, p3.dx),
|
||||||
_cubicBezier(t, p0.dy, p1.dy, p2.dy, p3.dy),
|
_cubicBezier(t, p0.dy, p1.dy, p2.dy, p3.dy),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A composite path of multiple bezier segments.
|
||||||
|
///
|
||||||
|
/// Build a path incrementally with [lineTo], [quadTo], and [cubeTo].
|
||||||
|
/// Evaluate any point along the total path with [point].
|
||||||
class PathBezier extends Bezier {
|
class PathBezier extends Bezier {
|
||||||
double _length = 0;
|
double _length = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
double get length => _length;
|
double get length => _length;
|
||||||
|
|
||||||
List<Bezier> _curves = [];
|
final List<Bezier> _curves = [];
|
||||||
List<double> _lens = [];
|
final List<double> _lens = [];
|
||||||
|
|
||||||
|
/// The starting point of the path.
|
||||||
final Offset p0;
|
final Offset p0;
|
||||||
Offset _p0;
|
Offset _p0;
|
||||||
|
|
||||||
|
/// Creates a path starting at [p0].
|
||||||
PathBezier(this.p0) : _p0 = p0;
|
PathBezier(this.p0) : _p0 = p0;
|
||||||
|
|
||||||
|
/// Creates a path tracing a rounded rectangle.
|
||||||
static PathBezier roundedRect(RRect rrect) {
|
static PathBezier roundedRect(RRect rrect) {
|
||||||
return PathBezier(Offset(rrect.left + rrect.width / 2, rrect.top))
|
return PathBezier(Offset(rrect.left + rrect.width / 2, rrect.top))
|
||||||
..lineTo(Offset(rrect.right - rrect.trRadiusX, rrect.top))
|
..lineTo(Offset(rrect.right - rrect.trRadiusX, rrect.top))
|
||||||
..quadTo(
|
..quadTo(Offset(rrect.right, rrect.top),
|
||||||
Offset(rrect.right, rrect.top),
|
|
||||||
Offset(rrect.right, rrect.top + rrect.trRadiusY))
|
Offset(rrect.right, rrect.top + rrect.trRadiusY))
|
||||||
..lineTo(Offset(rrect.right, rrect.bottom - rrect.brRadiusY))
|
..lineTo(Offset(rrect.right, rrect.bottom - rrect.brRadiusY))
|
||||||
..quadTo(Offset(rrect.right, rrect.bottom),
|
..quadTo(Offset(rrect.right, rrect.bottom),
|
||||||
@@ -102,7 +142,7 @@ class PathBezier extends Bezier {
|
|||||||
..lineTo(Offset(rrect.left + rrect.width / 2, rrect.top));
|
..lineTo(Offset(rrect.left + rrect.width / 2, rrect.top));
|
||||||
}
|
}
|
||||||
|
|
||||||
_add(Bezier bezier, Offset pn) {
|
void _add(Bezier bezier, Offset pn) {
|
||||||
final bl = bezierLength(bezier);
|
final bl = bezierLength(bezier);
|
||||||
_curves.add(bezier);
|
_curves.add(bezier);
|
||||||
_lens.add(bl);
|
_lens.add(bl);
|
||||||
@@ -110,32 +150,29 @@ class PathBezier extends Bezier {
|
|||||||
_p0 = pn;
|
_p0 = pn;
|
||||||
}
|
}
|
||||||
|
|
||||||
lineTo(Offset p1) => _add(LinearBezier(_p0, p1), p1);
|
/// Appends a straight line to [p1].
|
||||||
|
void lineTo(Offset p1) => _add(LinearBezier(_p0, p1), p1);
|
||||||
|
|
||||||
quadTo(Offset p1, Offset p2) => _add(QuadraticBezier(_p0, p1, p2), p2);
|
/// Appends a quadratic curve with control point [p1] to endpoint [p2].
|
||||||
|
void quadTo(Offset p1, Offset p2) =>
|
||||||
|
_add(QuadraticBezier(_p0, p1, p2), p2);
|
||||||
|
|
||||||
cubeTo(Offset p1, Offset p2, Offset p3) =>
|
/// Appends a cubic curve with control points [p1], [p2] to endpoint [p3].
|
||||||
|
void cubeTo(Offset p1, Offset p2, Offset p3) =>
|
||||||
_add(CubicBezier(_p0, p1, p2, p3), p3);
|
_add(CubicBezier(_p0, p1, p2, p3), p3);
|
||||||
|
|
||||||
relativeLineTo(Offset p1) =>
|
/// Appends a straight line to a point relative to the current position.
|
||||||
lineTo(
|
void relativeLineTo(Offset p1) => lineTo(p1 + _p0);
|
||||||
p1 + _p0,
|
|
||||||
);
|
|
||||||
|
|
||||||
relativeQuadTo(Offset p1, Offset p2) =>
|
/// Appends a quadratic curve with relative control and end points.
|
||||||
quadTo(
|
void relativeQuadTo(Offset p1, Offset p2) =>
|
||||||
p1 + p0,
|
quadTo(p1 + p0, p1 + p2 + p0);
|
||||||
p1 + p2 + p0,
|
|
||||||
);
|
|
||||||
|
|
||||||
relativeCubeTo(Offset p1, Offset p2, Offset p3) =>
|
|
||||||
cubeTo(
|
|
||||||
p0 + p1,
|
|
||||||
p0 + p1 + p2,
|
|
||||||
p0 + p1 + p2 + p3,
|
|
||||||
);
|
|
||||||
|
|
||||||
|
/// Appends a cubic curve with relative control and end points.
|
||||||
|
void relativeCubeTo(Offset p1, Offset p2, Offset p3) =>
|
||||||
|
cubeTo(p0 + p1, p0 + p1 + p2, p0 + p1 + p2 + p3);
|
||||||
|
|
||||||
|
@override
|
||||||
Offset point(double t) {
|
Offset point(double t) {
|
||||||
if (t > 1) {
|
if (t > 1) {
|
||||||
t = t - t.floor();
|
t = t - t.floor();
|
||||||
|
|||||||
@@ -1,15 +1,28 @@
|
|||||||
typedef T _JsonConvert<T>(Map<String, dynamic> json);
|
/// Signature for a function that converts a JSON map to a typed object.
|
||||||
|
typedef JsonConvert<T> = T Function(Map<String, dynamic> json);
|
||||||
|
|
||||||
|
/// Utilities for converting JSON structures to typed Dart objects.
|
||||||
class Json {
|
class Json {
|
||||||
static List<T> list<T>(List? json, _JsonConvert<T> fromJson) => json == null
|
/// Converts a JSON list of maps to a typed `List<T>`.
|
||||||
|
///
|
||||||
|
/// Returns an empty list if [json] is null.
|
||||||
|
static List<T> list<T>(List? json, JsonConvert<T> fromJson) => json == null
|
||||||
? []
|
? []
|
||||||
: json.cast<Map<String, dynamic>>().map(fromJson).toList();
|
: json.cast<Map<String, dynamic>>().map(fromJson).toList();
|
||||||
|
|
||||||
static Map<String, T> map<T>(Map? json, _JsonConvert<T> fromJson) => json == null
|
/// Converts a JSON map of maps to a typed `Map<String, T>`.
|
||||||
? {}
|
///
|
||||||
: Map.fromEntries(
|
/// Returns an empty map if [json] is null.
|
||||||
json.entries.map((e) => MapEntry(e.key, fromJson(e.value))));
|
static Map<String, T> map<T>(Map? json, JsonConvert<T> fromJson) =>
|
||||||
|
json == null
|
||||||
|
? {}
|
||||||
|
: Map.fromEntries(
|
||||||
|
json.entries.map((e) => MapEntry(e.key, fromJson(e.value))));
|
||||||
|
|
||||||
|
/// Reads a value at a dot-separated [path] from a nested JSON structure.
|
||||||
|
///
|
||||||
|
/// Supports both map keys and list indices (numeric segments).
|
||||||
|
/// Returns [defaultValue] if the path doesn't exist.
|
||||||
static dynamic path<T>(Map<String, dynamic> json, String path,
|
static dynamic path<T>(Map<String, dynamic> json, String path,
|
||||||
{dynamic defaultValue}) {
|
{dynamic defaultValue}) {
|
||||||
try {
|
try {
|
||||||
@@ -24,7 +37,6 @@ class Json {
|
|||||||
});
|
});
|
||||||
return current ?? defaultValue;
|
return current ?? defaultValue;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
print(error);
|
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import 'dart:ffi';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:flutter/animation.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
|
|
||||||
@@ -126,19 +125,50 @@ double _inverseLerp(List<double> samples, double value) {
|
|||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Frame-accurate keyboard height tracking for iOS and Android.
|
||||||
|
///
|
||||||
|
/// Reads the keyboard position directly from the native layer via FFI each
|
||||||
|
/// frame, bypassing Flutter's `MediaQuery.viewInsets` which lags behind.
|
||||||
|
///
|
||||||
|
/// Supports interactive dismiss (swipe-to-dismiss like iMessage) and provides
|
||||||
|
/// sampled native animation curves with adaptive learning.
|
||||||
|
///
|
||||||
|
/// Use the singleton [instance] and listen for changes via [addListener]:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// final keyboard = UxKeyboard.instance;
|
||||||
|
/// keyboard.enableInteractiveDismiss(trackingInset: 56);
|
||||||
|
/// ```
|
||||||
class UxKeyboard with ChangeNotifier {
|
class UxKeyboard with ChangeNotifier {
|
||||||
UxKeyboard._() {
|
UxKeyboard._() {
|
||||||
if (_lib == null) return;
|
if (_lib == null) return;
|
||||||
SchedulerBinding.instance.addPersistentFrameCallback(_onFrame);
|
SchedulerBinding.instance.addPersistentFrameCallback(_onFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The singleton instance.
|
||||||
static final UxKeyboard instance = UxKeyboard._();
|
static final UxKeyboard instance = UxKeyboard._();
|
||||||
|
|
||||||
double _height = 0;
|
double _height = 0;
|
||||||
|
|
||||||
|
/// The current keyboard height in logical pixels.
|
||||||
|
///
|
||||||
|
/// Updated every frame while the keyboard is animating or open.
|
||||||
|
/// Returns 0 when the keyboard is fully closed.
|
||||||
double get height => _height;
|
double get height => _height;
|
||||||
|
|
||||||
|
/// The last system-reported keyboard height.
|
||||||
|
///
|
||||||
|
/// Unlike [height], this is not interpolated — it reflects the target
|
||||||
|
/// height from the most recent keyboard notification.
|
||||||
double get systemHeight => _uxSystemHeight?.call() ?? 0;
|
double get systemHeight => _uxSystemHeight?.call() ?? 0;
|
||||||
|
|
||||||
|
/// Whether the keyboard is currently visible.
|
||||||
bool get isOpen => _height > 0;
|
bool get isOpen => _height > 0;
|
||||||
|
|
||||||
|
/// Whether an interactive dismiss pan gesture is active.
|
||||||
|
///
|
||||||
|
/// When true, the user is dragging the keyboard down. Use this to freeze
|
||||||
|
/// scroll views so they don't fight the pan gesture.
|
||||||
bool get isTracking => (_uxIsTracking?.call() ?? 0) > 0;
|
bool get isTracking => (_uxIsTracking?.call() ?? 0) > 0;
|
||||||
|
|
||||||
// Animation state — replays the keyboard's own animation inside Flutter.
|
// Animation state — replays the keyboard's own animation inside Flutter.
|
||||||
@@ -308,6 +338,13 @@ class UxKeyboard with ChangeNotifier {
|
|||||||
_obs.clear();
|
_obs.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Enables swipe-to-dismiss on the keyboard.
|
||||||
|
///
|
||||||
|
/// [trackingInset] is the height of your input bar in logical pixels.
|
||||||
|
/// The dismiss gesture activates when the finger enters the keyboard zone
|
||||||
|
/// below this inset.
|
||||||
void enableInteractiveDismiss({double trackingInset = 0}) => _uxEnableInteractiveDismiss?.call(trackingInset);
|
void enableInteractiveDismiss({double trackingInset = 0}) => _uxEnableInteractiveDismiss?.call(trackingInset);
|
||||||
|
|
||||||
|
/// Disables the swipe-to-dismiss gesture.
|
||||||
void disableInteractiveDismiss() => _uxDisableInteractiveDismiss?.call();
|
void disableInteractiveDismiss() => _uxDisableInteractiveDismiss?.call();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
int _idCounter = 0;
|
int _idCounter = 0;
|
||||||
|
|
||||||
|
/// Returns a monotonically increasing integer ID.
|
||||||
|
///
|
||||||
|
/// Useful for generating unique keys within a single isolate session.
|
||||||
|
/// Resets to 0 on app restart.
|
||||||
int nextId() => _idCounter++;
|
int nextId() => _idCounter++;
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
library ux;
|
/// Flutter toolkit for fluid, native-feeling UIs.
|
||||||
|
///
|
||||||
|
/// Includes [UxKeyboard] for frame-accurate keyboard height tracking,
|
||||||
|
/// [BendBox] for curved layout painting, and bezier curve utilities.
|
||||||
|
library;
|
||||||
|
|
||||||
export 'src/bend_box.dart';
|
export 'src/bend_box.dart';
|
||||||
export 'src/json_extension.dart';
|
export 'src/json_extension.dart';
|
||||||
export 'src/bezier.dart';
|
export 'src/bezier.dart';
|
||||||
export 'src/keyboard.dart';
|
export 'src/keyboard.dart';
|
||||||
|
|||||||
16
pubspec.lock
16
pubspec.lock
@@ -54,6 +54,14 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_lints:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: flutter_lints
|
||||||
|
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.0"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -83,6 +91,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
version: "3.0.2"
|
||||||
|
lints:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: lints
|
||||||
|
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.0"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
14
pubspec.yaml
14
pubspec.yaml
@@ -1,7 +1,16 @@
|
|||||||
name: ux
|
name: ux
|
||||||
description: UX Kit
|
description: >-
|
||||||
|
Flutter toolkit for fluid, native-feeling UIs. Includes frame-accurate
|
||||||
|
keyboard height tracking via FFI with interactive dismiss, bezier utilities,
|
||||||
|
and layout primitives.
|
||||||
version: 0.3.0
|
version: 0.3.0
|
||||||
homepage: https://swipelab.co/ux.html
|
homepage: https://swipelab.co/ux.html
|
||||||
|
repository: https://github.com/swipelab/ux
|
||||||
|
issue_tracker: https://github.com/swipelab/ux/issues
|
||||||
|
topics:
|
||||||
|
- keyboard
|
||||||
|
- ui
|
||||||
|
- animation
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.0.0"
|
sdk: ">=3.0.0"
|
||||||
@@ -11,6 +20,7 @@ dependencies:
|
|||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
flutter_lints: ^6.0.0
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
@@ -21,4 +31,4 @@ flutter:
|
|||||||
pluginClass: KeyboardPlugin
|
pluginClass: KeyboardPlugin
|
||||||
android:
|
android:
|
||||||
package: io.swipelab.ux
|
package: io.swipelab.ux
|
||||||
pluginClass: KeyboardPlugin
|
pluginClass: KeyboardPlugin
|
||||||
|
|||||||
@@ -1,21 +1,16 @@
|
|||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:ux/ux.dart';
|
import 'package:ux/ux.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
const MethodChannel channel = MethodChannel('ux');
|
test('UxKeyboard.instance is a singleton', () {
|
||||||
|
expect(UxKeyboard.instance, same(UxKeyboard.instance));
|
||||||
setUp(() {
|
|
||||||
channel.setMockMethodCallHandler((MethodCall methodCall) async {
|
|
||||||
return '42';
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
tearDown(() {
|
test('UxKeyboard.height starts at 0', () {
|
||||||
channel.setMockMethodCallHandler(null);
|
expect(UxKeyboard.instance.height, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getPlatformVersion', () async {
|
test('UxKeyboard.isOpen is false when height is 0', () {
|
||||||
expect(await UX.platformVersion, '42');
|
expect(UxKeyboard.instance.isOpen, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user