diff --git a/CHANGELOG.md b/CHANGELOG.md index ca0aaab..a1d8025 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # CHANGELOG +## Unreleased + +* Preserve source compatibility while documenting legacy APIs. +* Make `CustomConfig` snapshot mutable input lists like the other config modes. +* Add regression tests for config validation, runtime config updates, and + deterministic seeded random colors. +* Clarify Cloudflare deployment docs and package license attribution. + ## 0.2.5 * Improve `WaveWidget` paint performance for long-duration, high-amplitude diff --git a/LICENSE-APACHE-2.0 b/LICENSE-APACHE-2.0 new file mode 100644 index 0000000..7a77415 --- /dev/null +++ b/LICENSE-APACHE-2.0 @@ -0,0 +1,176 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, +and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by +the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all +other entities that control, are controlled by, or are under common +control with that entity. For the purposes of this definition, +"control" means (i) the power, direct or indirect, to cause the +direction or management of such entity, whether by contract or +otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity +exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, +including but not limited to software source code, documentation +source, and configuration files. + +"Object" form shall mean any form resulting from mechanical +transformation or translation of a Source form, including but +not limited to compiled object code, generated documentation, +and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or +Object form, made available under the License, as indicated by a +copyright notice that is included in or attached to the work +(an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object +form, that is based on (or derived from) the Work and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. For the purposes +of this License, Derivative Works shall not include works that remain +separable from, or merely link (or bind by name) to the interfaces of, +the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including +the original version of the Work and any modifications or additions +to that Work or Derivative Works thereof, that is intentionally +submitted to Licensor for inclusion in the Work by the copyright owner +or by an individual or Legal Entity authorized to submit on behalf of +the copyright owner. For the purposes of this definition, "submitted" +means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, +and issue tracking systems that are managed by, or on behalf of, the +Licensor for the purpose of discussing and improving the Work, but +excluding communication that is conspicuously marked or otherwise +designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity +on behalf of whom a Contribution has been received by Licensor and +subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the +Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +(except as stated in this section) patent license to make, have made, +use, offer to sell, sell, import, and otherwise transfer the Work, +where such license applies only to those patent claims licensable +by such Contributor that are necessarily infringed by their +Contribution(s) alone or by combination of their Contribution(s) +with the Work to which such Contribution(s) was submitted. If You +institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work +or a Contribution incorporated within the Work constitutes direct +or contributory patent infringement, then any patent licenses +granted to You under this License for that Work shall terminate +as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the +Work or Derivative Works thereof in any medium, with or without +modifications, and in Source or Object form, provided that You +meet the following conditions: + +(a) You must give any other recipients of the Work or +Derivative Works a copy of this License; and + +(b) You must cause any modified files to carry prominent notices +stating that You changed the files; and + +(c) You must retain, in the Source form of any Derivative Works +that You distribute, all copyright, patent, trademark, and +attribution notices from the Source form of the Work, +excluding those notices that do not pertain to any part of +the Derivative Works; and + +(d) If the Work includes a "NOTICE" text file as part of its +distribution, then any Derivative Works that You distribute must +include a readable copy of the attribution notices contained +within such NOTICE file, excluding those notices that do not +pertain to any part of the Derivative Works, in at least one +of the following places: within a NOTICE text file distributed +as part of the Derivative Works; within the Source form or +documentation, if provided along with the Derivative Works; or, +within a display generated by the Derivative Works, if and +wherever such third-party notices normally appear. The contents +of the NOTICE file are for informational purposes only and +do not modify the License. You may add Your own attribution +notices within Derivative Works that You distribute, alongside +or as an addendum to the NOTICE text from the Work, provided +that such additional attribution notices cannot be construed +as modifying the License. + +You may add Your own copyright statement to Your modifications and +may provide additional or different license terms and conditions +for use, reproduction, or distribution of Your modifications, or +for any such Derivative Works as a whole, provided Your use, +reproduction, and distribution of the Work otherwise complies with +the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, +any Contribution intentionally submitted for inclusion in the Work +by You to the Licensor shall be under the terms and conditions of +this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify +the terms of any separate license agreement you may have executed +with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade +names, trademarks, service marks, or product names of the Licensor, +except as required for reasonable and customary use in describing the +origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or +agreed to in writing, Licensor provides the Work (and each +Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied, including, without limitation, any warranties or conditions +of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +PARTICULAR PURPOSE. You are solely responsible for determining the +appropriateness of using or redistributing the Work and assume any +risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, +whether in tort (including negligence), contract, or otherwise, +unless required by applicable law (such as deliberate and grossly +negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, +incidental, or consequential damages of any character arising as a +result of this License or out of the use or inability to use the +Work (including but not limited to damages for loss of goodwill, +work stoppage, computer failure or malfunction, or any and all +other commercial damages or losses), even if such Contributor +has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing +the Work or Derivative Works thereof, You may choose to offer, +and charge a fee for, acceptance of support, warranty, indemnity, +or other liability obligations and/or rights consistent with this +License. However, in accepting such obligations, You may act only +on Your own behalf and on Your sole responsibility, not on behalf +of any other Contributor, and only if You agree to indemnify, +defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason +of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..b9c801f --- /dev/null +++ b/NOTICE @@ -0,0 +1,12 @@ +wave +Copyright (c) 2018 Yan Wong + +This package is distributed under the MIT License. See LICENSE. + +Portions of the wave painting implementation were adapted from +WaveView_flutter: + + https://github.com/While1true/WaveView_flutter + +WaveView_flutter is distributed under the Apache License, Version 2.0. +See LICENSE-APACHE-2.0. diff --git a/README.md b/README.md index 8026b9a..8730dc8 100644 --- a/README.md +++ b/README.md @@ -24,13 +24,13 @@ generated `WaveWidget` code into your app. ## Deployment -The web demo is built from `example/` and deployed with Cloudflare Workers Static Assets. See [docs/cloudflare-workers.md](docs/cloudflare-workers.md) for the GitHub Actions and migration setup. +The web demo is built from `example/` and deployed with Cloudflare Workers Static Assets. See [Cloudflare Workers deployment](https://github.com/glorylab/wave/blob/master/docs/cloudflare-workers.md) for the GitHub Actions setup. ## Install ```yaml dependencies: - wave: ^0.2.4 + wave: ^0.2.5 ``` ## Minimal Example @@ -135,6 +135,12 @@ WaveWidget( | `RandomConfig` | Fast generated palettes | `layers`, `seed`, `colors`, `durations`, `heightPercentages` | | `CustomConfig` | Exact colors or gradients | `colors` or `gradients`, `durations`, `heightPercentages`, `gradientBegin`, `gradientEnd` | +### API compatibility + +Prefer per-layer `heightPercentages` on the config classes. The legacy +`WaveWidget.heightPercentage` constructor parameter is retained for source +compatibility, but built-in configs now define their own layer heights. + ## Common Errors `CustomConfig` requires exactly one of `colors` or `gradients`. @@ -162,3 +168,9 @@ CustomConfig( `heightPercentages` and `opacityPercentages` values must be between `0` and `1`, and every `duration` must be positive. + +## License + +This package is distributed under the MIT License. Portions of the wave painting +implementation were adapted from WaveView_flutter under Apache-2.0; see +[NOTICE](NOTICE) and [LICENSE-APACHE-2.0](LICENSE-APACHE-2.0). diff --git a/docs/cloudflare-workers.md b/docs/cloudflare-workers.md index f11b1de..f570f90 100644 --- a/docs/cloudflare-workers.md +++ b/docs/cloudflare-workers.md @@ -11,7 +11,7 @@ The web demo is deployed as Cloudflare Workers Static Assets. The deployable app ## GitHub Secrets -Add these repository secrets before merging the deployment workflow: +Keep these repository secrets configured: - `CLOUDFLARE_ACCOUNT_ID`: the Cloudflare account ID that owns the Worker. - `CLOUDFLARE_API_TOKEN`: a Cloudflare API token scoped to the account with Workers edit permission. @@ -55,14 +55,14 @@ npx wrangler@4 deploy The Worker serves `example/build/web` and falls back to `index.html` for client-side routes. -## Moving the Domain From Vercel +## Production Checks -1. Merge this PR after the GitHub secrets are configured. -2. Confirm the first GitHub Actions deployment succeeds. -3. Open the Cloudflare Worker named `wave` and verify the `workers.dev` URL. -4. Confirm the `wave.glorylab.xyz` custom domain from `wrangler.jsonc` appears under the Worker domains/routes. -5. Point the domain DNS to Cloudflare if it is not already proxied there, then confirm `wave.glorylab.xyz` serves the Worker. -6. Disable the Vercel deployment after the Cloudflare route is healthy. -7. Remove the Vercel status check from branch protection if it is marked as required. +For changes that affect the demo, confirm these checks before merging: -During the transition, Vercel may still run preview deployments on pull requests. Treat Vercel preview failures separately from the Cloudflare Workers workflow until the Vercel Git integration is disabled. +1. `CI` passes for the supported Flutter matrix. +2. `Deploy Workers Static Assets` passes on the pull request build. +3. The push to `master` deploys successfully. +4. `https://wave.glorylab.xyz` returns the current Flutter web app. + +Branch protection should require only the active CI and Cloudflare checks. Do +not keep checks from retired deployment providers in the required check list. diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart index ab73b3a..c14f93c 100644 --- a/example/test/widget_test.dart +++ b/example/test/widget_test.dart @@ -1 +1,14 @@ -void main() {} +import 'package:flutter_test/flutter_test.dart'; +import 'package:wave_example/main.dart'; + +void main() { + testWidgets('renders the generator shell', (WidgetTester tester) async { + await tester.pumpWidget(const WaveGeneratorApp()); + await tester.pump(); + + expect(find.text('Wave Generator'), findsOneWidget); + expect(find.text('Live preview'), findsOneWidget); + expect(find.text('Controls'), findsOneWidget); + expect(tester.takeException(), isNull); + }); +} diff --git a/lib/src/config.dart b/lib/src/config.dart index 33d33ba..cd3f3df 100644 --- a/lib/src/config.dart +++ b/lib/src/config.dart @@ -183,43 +183,53 @@ class CustomConfig extends Config { required List? durations, required List? heightPercentages, this.blur, - }) : colors = colors, - gradients = gradients, - durations = durations, - heightPercentages = heightPercentages, + }) : colors = colors == null ? null : List.unmodifiable(colors), + gradients = gradients == null + ? null + : List>.unmodifiable( + gradients.map>( + (gradient) => List.unmodifiable(gradient), + ), + ), + durations = + durations == null ? null : List.unmodifiable(durations), + heightPercentages = heightPercentages == null + ? null + : List.unmodifiable(heightPercentages), super(colorMode: ColorMode.custom) { - if (colors == null && gradients == null) { + if (this.colors == null && this.gradients == null) { Config.throwNullError('custom', 'colors` or `gradients'); } - if (colors != null && gradients != null) { + if (this.colors != null && this.gradients != null) { throw FlutterError('Cannot provide both `colors` and `gradients`.'); } - if (durations == null) { + if (this.durations == null) { Config.throwNullError('custom', 'durations'); } - if (heightPercentages == null) { + if (this.heightPercentages == null) { Config.throwNullError('custom', 'heightPercentages'); } - final resolvedDurations = durations!; - final resolvedHeightPercentages = heightPercentages!; + final resolvedDurations = this.durations!; + final resolvedHeightPercentages = this.heightPercentages!; Config._resolveLayerCount( 'CustomConfig', - colors: colors, - gradients: gradients, + colors: this.colors, + gradients: this.gradients, durations: resolvedDurations, heightPercentages: resolvedHeightPercentages, ); Config._validateDurations(resolvedDurations); Config._validateHeightPercentages(resolvedHeightPercentages); - if (gradients == null && (gradientBegin != null || gradientEnd != null)) { + if (this.gradients == null && + (gradientBegin != null || gradientEnd != null)) { throw FlutterError( 'You set a gradient direction but forgot setting `gradients`.'); } - if (colors != null && colors.isEmpty) { + if (this.colors != null && this.colors!.isEmpty) { throw FlutterError('`colors` must define at least one layer.'); } - if (gradients != null) { - for (final gradient in gradients) { + if (this.gradients != null) { + for (final gradient in this.gradients!) { if (gradient.length < 2) { throw FlutterError( 'Every gradient in `gradients` must have at least two colors.'); diff --git a/lib/src/widget.dart b/lib/src/widget.dart index 2f17f1c..3f71c2f 100644 --- a/lib/src/widget.dart +++ b/lib/src/widget.dart @@ -1,204 +1,7 @@ -// Apache License -// Version 2.0, January 2004 -// http://www.apache.org/licenses/ +// SPDX-License-Identifier: MIT AND Apache-2.0 // -// TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -// -// 1. Definitions. -// -// "License" shall mean the terms and conditions for use, reproduction, -// and distribution as defined by Sections 1 through 9 of this document. -// -// "Licensor" shall mean the copyright owner or entity authorized by -// the copyright owner that is granting the License. -// -// "Legal Entity" shall mean the union of the acting entity and all -// other entities that control, are controlled by, or are under common -// control with that entity. For the purposes of this definition, -// "control" means (i) the power, direct or indirect, to cause the -// direction or management of such entity, whether by contract or -// otherwise, or (ii) ownership of fifty percent (50%) or more of the -// outstanding shares, or (iii) beneficial ownership of such entity. -// -// "You" (or "Your") shall mean an individual or Legal Entity -// exercising permissions granted by this License. -// -// "Source" form shall mean the preferred form for making modifications, -// including but not limited to software source code, documentation -// source, and configuration files. -// -// "Object" form shall mean any form resulting from mechanical -// transformation or translation of a Source form, including but -// not limited to compiled object code, generated documentation, -// and conversions to other media types. -// -// "Work" shall mean the work of authorship, whether in Source or -// Object form, made available under the License, as indicated by a -// copyright notice that is included in or attached to the work -// (an example is provided in the Appendix below). -// -// "Derivative Works" shall mean any work, whether in Source or Object -// form, that is based on (or derived from) the Work and for which the -// editorial revisions, annotations, elaborations, or other modifications -// represent, as a whole, an original work of authorship. For the purposes -// of this License, Derivative Works shall not include works that remain -// separable from, or merely link (or bind by name) to the interfaces of, -// the Work and Derivative Works thereof. -// -// "Contribution" shall mean any work of authorship, including -// the original version of the Work and any modifications or additions -// to that Work or Derivative Works thereof, that is intentionally -// submitted to Licensor for inclusion in the Work by the copyright owner -// or by an individual or Legal Entity authorized to submit on behalf of -// the copyright owner. For the purposes of this definition, "submitted" -// means any form of electronic, verbal, or written communication sent -// to the Licensor or its representatives, including but not limited to -// communication on electronic mailing lists, source code control systems, -// and issue tracking systems that are managed by, or on behalf of, the -// Licensor for the purpose of discussing and improving the Work, but -// excluding communication that is conspicuously marked or otherwise -// designated in writing by the copyright owner as "Not a Contribution." -// -// "Contributor" shall mean Licensor and any individual or Legal Entity -// on behalf of whom a Contribution has been received by Licensor and -// subsequently incorporated within the Work. -// -// 2. Grant of Copyright License. Subject to the terms and conditions of -// this License, each Contributor hereby grants to You a perpetual, -// worldwide, non-exclusive, no-charge, royalty-free, irrevocable -// copyright license to reproduce, prepare Derivative Works of, -// publicly display, publicly perform, sublicense, and distribute the -// Work and such Derivative Works in Source or Object form. -// -// 3. Grant of Patent License. Subject to the terms and conditions of -// this License, each Contributor hereby grants to You a perpetual, -// worldwide, non-exclusive, no-charge, royalty-free, irrevocable -// (except as stated in this section) patent license to make, have made, -// use, offer to sell, sell, import, and otherwise transfer the Work, -// where such license applies only to those patent claims licensable -// by such Contributor that are necessarily infringed by their -// Contribution(s) alone or by combination of their Contribution(s) -// with the Work to which such Contribution(s) was submitted. If You -// institute patent litigation against any entity (including a -// cross-claim or counterclaim in a lawsuit) alleging that the Work -// or a Contribution incorporated within the Work constitutes direct -// or contributory patent infringement, then any patent licenses -// granted to You under this License for that Work shall terminate -// as of the date such litigation is filed. -// -// 4. Redistribution. You may reproduce and distribute copies of the -// Work or Derivative Works thereof in any medium, with or without -// modifications, and in Source or Object form, provided that You -// meet the following conditions: -// -// (a) You must give any other recipients of the Work or -// Derivative Works a copy of this License; and -// -// (b) You must cause any modified files to carry prominent notices -// stating that You changed the files; and -// -// (c) You must retain, in the Source form of any Derivative Works -// that You distribute, all copyright, patent, trademark, and -// attribution notices from the Source form of the Work, -// excluding those notices that do not pertain to any part of -// the Derivative Works; and -// -// (d) If the Work includes a "NOTICE" text file as part of its -// distribution, then any Derivative Works that You distribute must -// include a readable copy of the attribution notices contained -// within such NOTICE file, excluding those notices that do not -// pertain to any part of the Derivative Works, in at least one -// of the following places: within a NOTICE text file distributed -// as part of the Derivative Works; within the Source form or -// documentation, if provided along with the Derivative Works; or, -// within a display generated by the Derivative Works, if and -// wherever such third-party notices normally appear. The contents -// of the NOTICE file are for informational purposes only and -// do not modify the License. You may add Your own attribution -// notices within Derivative Works that You distribute, alongside -// or as an addendum to the NOTICE text from the Work, provided -// that such additional attribution notices cannot be construed -// as modifying the License. -// -// You may add Your own copyright statement to Your modifications and -// may provide additional or different license terms and conditions -// for use, reproduction, or distribution of Your modifications, or -// for any such Derivative Works as a whole, provided Your use, -// reproduction, and distribution of the Work otherwise complies with -// the conditions stated in this License. -// -// 5. Submission of Contributions. Unless You explicitly state otherwise, -// any Contribution intentionally submitted for inclusion in the Work -// by You to the Licensor shall be under the terms and conditions of -// this License, without any additional terms or conditions. -// Notwithstanding the above, nothing herein shall supersede or modify -// the terms of any separate license agreement you may have executed -// with Licensor regarding such Contributions. -// -// 6. Trademarks. This License does not grant permission to use the trade -// names, trademarks, service marks, or product names of the Licensor, -// except as required for reasonable and customary use in describing the -// origin of the Work and reproducing the content of the NOTICE file. -// -// 7. Disclaimer of Warranty. Unless required by applicable law or -// agreed to in writing, Licensor provides the Work (and each -// Contributor provides its Contributions) on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied, including, without limitation, any warranties or conditions -// of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A -// PARTICULAR PURPOSE. You are solely responsible for determining the -// appropriateness of using or redistributing the Work and assume any -// risks associated with Your exercise of permissions under this License. -// -// 8. Limitation of Liability. In no event and under no legal theory, -// whether in tort (including negligence), contract, or otherwise, -// unless required by applicable law (such as deliberate and grossly -// negligent acts) or agreed to in writing, shall any Contributor be -// liable to You for damages, including any direct, indirect, special, -// incidental, or consequential damages of any character arising as a -// result of this License or out of the use or inability to use the -// Work (including but not limited to damages for loss of goodwill, -// work stoppage, computer failure or malfunction, or any and all -// other commercial damages or losses), even if such Contributor -// has been advised of the possibility of such damages. -// -// 9. Accepting Warranty or Additional Liability. While redistributing -// the Work or Derivative Works thereof, You may choose to offer, -// and charge a fee for, acceptance of support, warranty, indemnity, -// or other liability obligations and/or rights consistent with this -// License. However, in accepting such obligations, You may act only -// on Your own behalf and on Your sole responsibility, not on behalf -// of any other Contributor, and only if You agree to indemnify, -// defend, and hold each Contributor harmless for any liability -// incurred by, or claims asserted against, such Contributor by reason -// of your accepting any such warranty or additional liability. -// -// END OF TERMS AND CONDITIONS -// -// APPENDIX: How to apply the Apache License to your work. -// -// To apply the Apache License to your work, attach the following -// boilerplate notice, with the fields enclosed by brackets "[]" -// replaced with your own identifying information. (Don't include -// the brackets!) The text should be enclosed in the appropriate -// comment syntax for the file format. We also recommend that a -// file or class name and description of purpose be included on the -// same "printed page" as the copyright notice for easier -// identification within third-party archives. -// -// Copyright [2018] [While1true] -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// This implementation includes code adapted from WaveView_flutter. +// See NOTICE and LICENSE-APACHE-2.0 for third-party attribution details. import 'dart:async'; import 'dart:math'; @@ -224,8 +27,13 @@ class WaveWidget extends StatefulWidget { /// Horizontal frequency of the wave path. final double waveFrequency; - /// Default vertical offset used by the painter when a layer config does not - /// provide its own height percentage. + /// Legacy vertical offset retained for source compatibility. + /// + /// Built-in configs provide per-layer `heightPercentages`, so new code should + /// set heights on [SingleConfig], [RandomConfig], or [CustomConfig] instead. + @Deprecated( + 'Use heightPercentages on SingleConfig, RandomConfig, or CustomConfig.', + ) final double heightPercentage; /// Total animation duration in milliseconds when [isLoop] is false. @@ -247,7 +55,10 @@ class WaveWidget extends StatefulWidget { this.waveAmplitude = 20.0, this.wavePhase = 10.0, this.waveFrequency = 1.6, - this.heightPercentage = 0.2, + @Deprecated( + 'Use heightPercentages on SingleConfig, RandomConfig, or CustomConfig.', + ) + this.heightPercentage = 0.2, this.duration = 6000, this.backgroundColor, this.backgroundImage, @@ -498,15 +309,34 @@ class _WaveLayerAnimation { }); } -/// Meta data of layer +/// Legacy metadata object for a painted wave layer. +/// +/// This class is no longer used by the painter and remains exported only for +/// source compatibility with older code. +@Deprecated( + 'Layer is an internal implementation detail and may be removed in a future ' + 'major release.', +) class Layer { + /// Solid color for the layer. final Color? color; + + /// Gradient colors for the layer. final List? gradient; + + /// Optional blur mask for the layer. final MaskFilter? blur; + + /// Cached path for the layer. final Path? path; + + /// Vertical amplitude for the layer. final double? amplitude; + + /// Animation phase for the layer. final double? phase; + /// Creates legacy layer metadata. Layer({ this.color, this.gradient, diff --git a/test/wave_test.dart b/test/wave_test.dart index de318f4..0308438 100644 --- a/test/wave_test.dart +++ b/test/wave_test.dart @@ -4,6 +4,13 @@ import 'package:wave/wave.dart'; void main() { group('Config', () { + test('rejects invalid layer counts', () { + expect( + () => SingleConfig(layers: 0), + throwsA(isA()), + ); + }); + test('rejects mismatched custom gradient lengths', () { expect( () => CustomConfig( @@ -29,6 +36,81 @@ void main() { throwsA(isA()), ); }); + + test('rejects invalid duration, height, and opacity values', () { + expect( + () => SingleConfig(durations: [0]), + throwsA(isA()), + ); + expect( + () => SingleConfig(heightPercentages: [1.2]), + throwsA(isA()), + ); + expect( + () => SingleConfig(opacityPercentages: [-0.1]), + throwsA(isA()), + ); + }); + + test('snapshots mutable custom color inputs', () { + final colors = [Colors.red, Colors.blue]; + final durations = [1000, 2000]; + final heightPercentages = [0.2, 0.3]; + final config = CustomConfig( + colors: colors, + durations: durations, + heightPercentages: heightPercentages, + ); + + colors[0] = Colors.green; + durations[0] = 3000; + heightPercentages[0] = 0.8; + + expect(config.colors, [Colors.red, Colors.blue]); + expect(config.durations, [1000, 2000]); + expect(config.heightPercentages, [0.2, 0.3]); + expect(() => config.colors!.add(Colors.black), throwsUnsupportedError); + expect(() => config.durations!.add(3000), throwsUnsupportedError); + expect( + () => config.heightPercentages!.add(0.4), + throwsUnsupportedError, + ); + }); + + test('snapshots mutable custom gradient inputs', () { + final gradients = >[ + [Colors.red, Colors.blue], + [Colors.green, Colors.yellow], + ]; + final config = CustomConfig( + gradients: gradients, + durations: [1000, 2000], + heightPercentages: [0.2, 0.3], + ); + + gradients[0][0] = Colors.black; + gradients.add([Colors.purple, Colors.orange]); + + expect(config.gradients, [ + [Colors.red, Colors.blue], + [Colors.green, Colors.yellow], + ]); + expect( + () => config.gradients![0].add(Colors.black), + throwsUnsupportedError, + ); + expect( + () => config.gradients!.add([Colors.black, Colors.white]), + throwsUnsupportedError, + ); + }); + + test('generates deterministic random colors with a seed', () { + final first = RandomConfig(seed: 7, layers: 3); + final second = RandomConfig(seed: 7, layers: 3); + + expect(second.colors, first.colors); + }); }); group('Wave widget', () { @@ -102,6 +184,46 @@ void main() { expect(tester.takeException(), isNull); }); + + testWidgets('updates config and layer count at runtime', + (WidgetTester tester) async { + await tester.pumpWidget(getWaveWidget( + config: SingleConfig(layers: 2), + )); + await tester.pump(const Duration(milliseconds: 16)); + + await tester.pumpWidget(getWaveWidget( + config: CustomConfig( + gradients: const [ + [Colors.red, Colors.blue], + [Colors.green, Colors.yellow], + [Colors.purple, Colors.orange], + ], + durations: const [5000, 4000, 3000], + heightPercentages: const [0.2, 0.3, 0.4], + ), + )); + await tester.pump(const Duration(milliseconds: 16)); + + expect(tester.takeException(), isNull); + }); + + testWidgets('updates frequency without recreating layer counts', + (WidgetTester tester) async { + await tester.pumpWidget(getWaveWidget( + config: RandomConfig(seed: 7, layers: 3), + waveFrequency: 1, + )); + await tester.pump(const Duration(milliseconds: 16)); + + await tester.pumpWidget(getWaveWidget( + config: RandomConfig(seed: 7, layers: 3), + waveFrequency: 2, + )); + await tester.pump(const Duration(milliseconds: 16)); + + expect(tester.takeException(), isNull); + }); }); }