Commit fa9f91bc by Rizal Hermawan

fix: fixed bugs after downgrading the flutter version

parents
{
"configVersion": 2,
"packages": [
{
"name": "async",
"rootUri": "file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/async-2.8.1",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "boolean_selector",
"rootUri": "file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/boolean_selector-2.1.0",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "characters",
"rootUri": "file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/characters-1.1.0",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "charcode",
"rootUri": "file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/charcode-1.3.1",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "clock",
"rootUri": "file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/clock-1.1.0",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "collection",
"rootUri": "file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/collection-1.15.0",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "fake_async",
"rootUri": "file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/fake_async-1.2.0",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "flutter",
"rootUri": "file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/packages/flutter",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "flutter_test",
"rootUri": "file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/packages/flutter_test",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "matcher",
"rootUri": "file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.10",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "meta",
"rootUri": "file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/meta-1.7.0",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "path",
"rootUri": "file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/path-1.8.0",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "sky_engine",
"rootUri": "file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/bin/cache/pkg/sky_engine",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "source_span",
"rootUri": "file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.1",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "stack_trace",
"rootUri": "file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "stream_channel",
"rootUri": "file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/stream_channel-2.1.0",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "string_scanner",
"rootUri": "file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.1.0",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "term_glyph",
"rootUri": "file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.2.0",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "test_api",
"rootUri": "file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/test_api-0.4.2",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "typed_data",
"rootUri": "file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/typed_data-1.3.0",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "vector_math",
"rootUri": "file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/vector_math-2.1.0",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "implicitly_animated_reorderable_list_2",
"rootUri": "../",
"packageUri": "lib/",
"languageVersion": "2.12"
}
],
"generated": "2023-12-04T07:55:04.703197Z",
"generator": "pub",
"generatorVersion": "2.14.4"
}
async
2.12
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/async-2.8.1/
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/async-2.8.1/lib/
boolean_selector
2.12
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/boolean_selector-2.1.0/
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/boolean_selector-2.1.0/lib/
characters
2.12
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/characters-1.1.0/
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/characters-1.1.0/lib/
charcode
2.12
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/charcode-1.3.1/
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/charcode-1.3.1/lib/
clock
2.12
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/clock-1.1.0/
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/clock-1.1.0/lib/
collection
2.12
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/collection-1.15.0/
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/collection-1.15.0/lib/
fake_async
2.12
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/fake_async-1.2.0/
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/fake_async-1.2.0/lib/
matcher
2.12
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.10/
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.10/lib/
meta
2.12
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/meta-1.7.0/
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/meta-1.7.0/lib/
path
2.12
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/path-1.8.0/
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/path-1.8.0/lib/
source_span
2.12
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.1/
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.1/lib/
stack_trace
2.12
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0/
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0/lib/
stream_channel
2.12
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/stream_channel-2.1.0/
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/stream_channel-2.1.0/lib/
string_scanner
2.12
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.1.0/
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.1.0/lib/
term_glyph
2.12
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.2.0/
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.2.0/lib/
test_api
2.12
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/test_api-0.4.2/
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/test_api-0.4.2/lib/
typed_data
2.12
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/typed_data-1.3.0/
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/typed_data-1.3.0/lib/
vector_math
2.12
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/vector_math-2.1.0/
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/vector_math-2.1.0/lib/
sky_engine
2.12
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/bin/cache/pkg/sky_engine/
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/bin/cache/pkg/sky_engine/lib/
flutter
2.12
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/packages/flutter/
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/packages/flutter/lib/
flutter_test
2.12
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/packages/flutter_test/
file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/packages/flutter_test/lib/
implicitly_animated_reorderable_list_2
2.12
file:///C:/24%20Rizal%20Hermawan/job/mobile/codingan/edit_pub/implicitly_animated_reorderable_list_2-0.5.1/
file:///C:/24%20Rizal%20Hermawan/job/mobile/codingan/edit_pub/implicitly_animated_reorderable_list_2-0.5.1/lib/
2
2.5.3
\ No newline at end of file
# This file is deprecated. Tools should instead consume
# `.dart_tool/package_config.json`.
#
# For more info see: https://dart.dev/go/dot-packages-deprecation
#
# Generated by pub on 2023-12-04 14:55:04.690276.
async:file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/async-2.8.1/lib/
boolean_selector:file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/boolean_selector-2.1.0/lib/
characters:file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/characters-1.1.0/lib/
charcode:file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/charcode-1.3.1/lib/
clock:file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/clock-1.1.0/lib/
collection:file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/collection-1.15.0/lib/
fake_async:file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/fake_async-1.2.0/lib/
flutter:file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/packages/flutter/lib/
flutter_test:file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/packages/flutter_test/lib/
matcher:file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.10/lib/
meta:file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/meta-1.7.0/lib/
path:file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/path-1.8.0/lib/
sky_engine:file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/bin/cache/pkg/sky_engine/lib/
source_span:file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.1/lib/
stack_trace:file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0/lib/
stream_channel:file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/stream_channel-2.1.0/lib/
string_scanner:file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.1.0/lib/
term_glyph:file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.2.0/lib/
test_api:file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/test_api-0.4.2/lib/
typed_data:file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/typed_data-1.3.0/lib/
vector_math:file:///C:/24%20Rizal%20Hermawan/flutter_2.5.3/.pub-cache/hosted/pub.dartlang.org/vector_math-2.1.0/lib/
implicitly_animated_reorderable_list_2:lib/
## 0.4.4
* **Fixes**
## 0.4.3
* **Fixed** `WidgetsBinding` null warning
## 0.4.2
* **Fixed** #54, #72
## 0.4.0
* **Added** NNBD support
* **Fixed** #19, #49, #50, #52
* **Improved** `Handle` is now able to capture pointer events which allows an `ImplicitlyAnimatedReorderableList` to be placed inside another scrollable without any workarounds.
* **Breaking** Renamed `dragDuration` to `reorderDuration`
* **Added** Field `liftDuration`
* **Added** Field `settleDuration`
## 0.3.2
* **Fixed** #47
## 0.3.1
* **Fixed** #43
* **Fixed** Changelog
## 0.3.0
* **Fixed** #23
## 0.2.5
* **Fixed** #14
## 0.2.1
* **Improved** `ImplicitlyAnimatedList` now always uses the latest items, even if `listEquals()` is `true`.
## 0.2.0
* **Added** support for headers and footers on the `ImplicitlyAnimatedReorderableList`.
* **Added** `child` property on `Reorderable` that can be used instead off the `builder` that will use a default elevation animation instead of being forced to specify your own custom animation.
## 0.1.10
* **Fixed** Bugs
## 0.1.4
* **Improved** `Handle` is now scroll aware and only initiates a drag when the scroll position didn't change.
* **Added** horizontal scrollDirection support for `ImplicitlyAnimatedReorderableList`
## 0.1.0
* Initial release
MIT License
Copyright (c) 2020 bxqm
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.
\ No newline at end of file
# Implicitly Animated Reorderable List
[![pub package](https://img.shields.io/pub/v/implicitly_animated_reorderable_list_2.svg)](https://pub.dartlang.org/packages/implicitly_animated_reorderable_list_2)
[![GitHub Stars](https://img.shields.io/github/stars/bxqm/implicitly_animated_reorderable_list_2.svg?logo=github)](https://github.com/bxqm/implicitly_animated_reorderable_list_2)
A Flutter `ListView` that implicitly calculates the changes between two lists using the `MyersDiff` algorithm and animates between them for you. The `ImplicitlyAnimatedReorderableList` adds reordering support to its items with fully custom animations.
<p style="text-align:center">
<img width="356px" alt="Demo" src="https://raw.githubusercontent.com/bxqm/implicitly_animated_reorderable_list_2/master/assets/demo.gif"/>
</p>
Click [here](https://github.com/bxqm/implicitly_animated_reorderable_list_2/blob/master/example/lib/ui/) to view the full example.
## Installing
Add it to your `pubspec.yaml` file:
```yaml
dependencies:
implicitly_animated_reorderable_list_2: ^0.4.2
```
Install packages from the command line
```
flutter packages get
```
If you like this package, consider supporting it by giving it a star on [GitHub](https://github.com/bxqm/implicitly_animated_reorderable_list_2) and a like on [pub.dev](https://pub.dev/packages/implicitly_animated_reorderable_list_2) :heart:
## Usage
The package contains two `ListViews`: `ImplicitlyAnimatedList` which is the base class and offers implicit animation for item `insertions`, `removals` as well as `updates`, and `ImplicitlyAnimatedReorderableList` which extends the `ImplicitlyAnimatedList` and adds reordering support to its items. See examples below on how to use them.
### ImplicitlyAnimatedList
`ImplicitlyAnimatedList` is based on `AnimatedList` and uses the `MyersDiff` algorithm to calculate the difference between two lists and calls `insertItem` and `removeItem` on the `AnimatedListState` for you.
#### Example
```dart
// Specify the generic type of the data in the list.
ImplicitlyAnimatedList<MyGenericType>(
// The current items in the list.
items: items,
// Called by the DiffUtil to decide whether two object represent the same item.
// For example, if your items have unique ids, this method should check their id equality.
areItemsTheSame: (a, b) => a.id == b.id,
// Called, as needed, to build list item widgets.
// List items are only built when they're scrolled into view.
itemBuilder: (context, animation, item, index) {
// Specifiy a transition to be used by the ImplicitlyAnimatedList.
// See the Transitions section on how to import this transition.
return SizeFadeTransition(
sizeFraction: 0.7,
curve: Curves.easeInOut,
animation: animation,
child: Text(item.name),
);
},
// An optional builder when an item was removed from the list.
// If not specified, the List uses the itemBuilder with
// the animation reversed.
removeItemBuilder: (context, animation, oldItem) {
return FadeTransition(
opacity: animation,
child: Text(oldItem.name),
);
},
);
```
> If you have a `CustomScrollView` you can use the `SliverImplicitlyAnimatedList`.
> As `AnimatedList` doesn't support item moves, a move is handled by removing the item from the old index and inserting it at the new index.
### ImplicitlyAnimatedReorderableList
`ImplicitlyAnimatedReorderableList` is based on `ImplicitlyAnimatedList` and adds reordering support to the list.
#### Example
```dart
ImplicitlyAnimatedReorderableList<MyGenericType>(
items: items,
areItemsTheSame: (oldItem, newItem) => oldItem.id == newItem.id,
onReorderFinished: (item, from, to, newItems) {
// Remember to update the underlying data when the list has been
// reordered.
setState(() {
items
..clear()
..addAll(newItems);
});
},
itemBuilder: (context, itemAnimation, item, index) {
// Each item must be wrapped in a Reorderable widget.
return Reorderable(
// Each item must have an unique key.
key: ValueKey(item),
// The animation of the Reorderable builder can be used to
// change to appearance of the item between dragged and normal
// state. For example to add elevation when the item is being dragged.
// This is not to be confused with the animation of the itemBuilder.
// Implicit animations (like AnimatedContainer) are sadly not yet supported.
builder: (context, dragAnimation, inDrag) {
final t = dragAnimation.value;
final elevation = lerpDouble(0, 8, t);
final color = Color.lerp(Colors.white, Colors.white.withOpacity(0.8), t);
return SizeFadeTransition(
sizeFraction: 0.7,
curve: Curves.easeInOut,
animation: itemAnimation,
child: Material(
color: color,
elevation: elevation,
type: MaterialType.transparency,
child: ListTile(
title: Text(item.name),
// The child of a Handle can initialize a drag/reorder.
// This could for example be an Icon or the whole item itself. You can
// use the delay parameter to specify the duration for how long a pointer
// must press the child, until it can be dragged.
trailing: Handle(
delay: const Duration(milliseconds: 100),
child: Icon(
Icons.list,
color: Colors.grey,
),
),
),
),
);
},
);
},
// Since version 0.2.0 you can also display a widget
// before the reorderable items...
header: Container(
height: 200,
color: Colors.red,
),
// ...and after. Note that this feature - as the list itself - is still in beta!
footer: Container(
height: 200,
color: Colors.green,
),
// If you want to use headers or footers, you should set shrinkWrap to true
shrinkWrap: true,
);
```
> For a more in depth example click [here](https://github.com/bxqm/implicitly_animated_reorderable_list_2/blob/master/example/lib/ui/lang_page.dart).
### Transitions
You can use some custom transitions (such as the `SizeFadeTransition`) for item animations by importing the transitions pack:
```dart
import 'package:implicitly_animated_reorderable_list_2/transitions.dart';
```
If you want to contribute your own custom transitions, feel free to make a pull request.
### Caveats
Note that this package is still in its very early phase and not enough testing has been done to guarantee stability.
Also note that computing the diff between two very large lists may take a significant amount of time (the computation is done on a background isolate though unless `spawnIsolate` is set to `false`).
### Acknowledgements
The diff algorithm that `ImplicitlyAnimatedList` uses was written by [Dawid Bota](https://gitlab.com/otsoaUnLoco) at [GitLab](https://gitlab.com/otsoaUnLoco/animated-stream-list).
### Roadmap
You can take a look at the [Roadmap](https://github.com/bxqm/implicitly_animated_reorderable_list_2/blob/master/roadmap.md) to see which featues I am working on or plan to implement in future versions.
##
# Lint rules to be used for apps without developer facing API. i.e. command line tools and Flutter applications
##
analyzer:
strong-mode:
# Will become the default once non-nullable types land
# https://github.com/dart-lang/sdk/issues/31410#issuecomment-510683629
implicit-casts: true
errors:
# treat missing required parameters as a warning (not a hint)
missing_required_param: warning
# treat missing returns as a warning (not a hint)
missing_return: warning
# allow having TODOs in the code
todo: ignore
# Rules are in the same order (alphabetically) as documented at http://dart-lang.github.io/linter/lints
# and https://github.com/dart-lang/linter/blob/master/example/all.yaml
linter:
rules:
# Prevents accidental return type changes which results in a breaking API change.
# Enforcing return type makes API changes visible in a diff
# pedantic: enabled
# http://dart-lang.github.io/linter/lints/always_declare_return_types.html
- always_declare_return_types
# Single line `if`s are fine as recommended in Effective Dart "DO format your code using dartfmt"
# pedantic: disabled
# http://dart-lang.github.io/linter/lints/always_put_control_body_on_new_line.html
# - always_put_control_body_on_new_line
# Flutter widgets always put a Key as first optional parameter which breaks this rule.
# Also violates other orderings like matching the class fields or alphabetically.
# pedantic: disabled
# http://dart-lang.github.io/linter/lints/always_declare_return_types.html
# - always_put_required_named_parameters_first
# All non nullable named parameters should be and annotated with @required.
# This allows API consumers to get warnings via lint rather than a crash a runtime.
# Might become obsolete with Non-Nullable types
# http://dart-lang.github.io/linter/lints/always_require_non_null_named_parameters.html
- always_require_non_null_named_parameters
# Since dart 2.0 dart is a sound language, specifying types is not required anymore.
# `var foo = 10;` is enough information for the compiler to make foo a int.
# Violates Effective Dart "AVOID type annotating initialized local variables".
# Makes code unnecessarily complex https://github.com/dart-lang/linter/issues/1620
# pedantic: disabled
# http://dart-lang.github.io/linter/lints/always_specify_types.html
# - always_specify_types
# Protect against unintentionally overriding superclass members
# pedantic: enabled
# http://dart-lang.github.io/linter/lints/annotate_overrides.html
- annotate_overrides
# All methods should define a return type. dynamic is no exception.
# Violates Effective Dart "PREFER annotating with dynamic instead of letting inference fail"
# pedantic: disabled
# http://dart-lang.github.io/linter/lints/avoid_annotating_with_dynamic.html
# - avoid_annotating_with_dynamic
# A leftover from dart1, should be deprecated
# pedantic: disabled
# - https://github.com/dart-lang/linter/issues/1401
# http://dart-lang.github.io/linter/lints/avoid_as.html
# - avoid_as
# Highlights boolean expressions which can be simplified
# http://dart-lang.github.io/linter/lints/avoid_bool_literals_in_conditional_expressions.html
- avoid_bool_literals_in_conditional_expressions
# There are no strong arguments to enable this rule because it is very strict. Catching anything is useful
# and common even if not always the most correct thing to do.
# pedantic: disabled
# http://dart-lang.github.io/linter/lints/avoid_catches_without_on_clauses.html
# - avoid_catches_without_on_clauses
# Errors aren't for catching but to prevent prior to runtime
# pedantic: disabled
# http://dart-lang.github.io/linter/lints/avoid_catching_errors.html
- avoid_catching_errors
# Can usually be replaced with an extension
# pedantic: disabled
# http://dart-lang.github.io/linter/lints/avoid_classes_with_only_static_members.html
# - avoid_classes_with_only_static_members
# Only useful when targeting JS
# pedantic: disabled
# http://dart-lang.github.io/linter/lints/avoid_double_and_int_checks.html
# - avoid_double_and_int_checks
# Prevents accidental empty else cases. See samples in documentation
# pedantic: enabled
# http://dart-lang.github.io/linter/lints/avoid_empty_else.html
- avoid_empty_else
# It is expected that mutable objects which override hash & equals shouldn't be used as keys for hashmaps.
# This one use case doesn't make all hash & equals implementations for mutable classes bad.
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/avoid_equals_and_hash_code_on_mutable_classes.html
# - avoid_equals_and_hash_code_on_mutable_classes
# Prevents unnecessary allocation of a field
# pedantic: disabled
# http://dart-lang.github.io/linter/lints/avoid_field_initializers_in_const_classes.html
- avoid_field_initializers_in_const_classes
# Prevents allocating a lambda and allows return/break/continue control flow statements inside the loop
# http://dart-lang.github.io/linter/lints/avoid_function_literals_in_foreach_calls.html
- avoid_function_literals_in_foreach_calls
# Don't break value types by implementing them
# http://dart-lang.github.io/linter/lints/avoid_implementing_value_types.html
- avoid_implementing_value_types
# Removes redundant `= null;`
# https://dart-lang.github.io/linter/lints/avoid_init_to_null.html
- avoid_init_to_null
# Only useful when targeting JS
# Warns about too large integers when compiling to JS
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/avoid_js_rounded_ints.html
# - avoid_js_rounded_ints
# Null checks aren't required in ==() operators
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/avoid_null_checks_in_equality_operators.html
- avoid_null_checks_in_equality_operators
# Good APIs don't use ambiguous boolean parameters. Instead use named parameters
# https://dart-lang.github.io/linter/lints/avoid_positional_boolean_parameters.html
- avoid_positional_boolean_parameters
# Don't call print in production code
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/avoid_print.html
- avoid_print
# Always prefer function references over typedefs.
# Jumping twice in code to see the signature of a lambda sucks. This is different from the flutter analysis_options
# https://dart-lang.github.io/linter/lints/avoid_private_typedef_functions.html
- avoid_private_typedef_functions
# package or relative? Let's end the discussion and use package everywhere.
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/avoid_relative_lib_imports.html
- avoid_relative_lib_imports
# Not recommended to break dartdoc but besides that there is no reason to continue with bad naming
# https://dart-lang.github.io/linter/lints/avoid_renaming_method_parameters.html
# - avoid_renaming_method_parameters
# Setters always return void, therefore defining void is redundant
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/avoid_return_types_on_setters.html
- avoid_return_types_on_setters
# Especially with Non-Nullable types on the horizon, `int?` is fine.
# There are plenty of valid reasons to return null.
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/avoid_returning_null.html
# - avoid_returning_null
# Don't use `Future?`, therefore never return null instead of a Future.
# Will become obsolete one Non-Nullable types land
# https://dart-lang.github.io/linter/lints/avoid_returning_null_for_future.html
- avoid_returning_null_for_future
# Use empty returns, don't show off with you knowledge about dart internals.
# https://dart-lang.github.io/linter/lints/avoid_returning_null_for_void.html
- avoid_returning_null_for_void
# Hinting you forgot about the cascade operator. But too often you did this on purpose.
# There are plenty of valid reasons to return this.
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/avoid_returning_this.html
# - avoid_returning_this
# Prevents logical inconsistencies. It's good practice to define getters for all existing setters.
# https://dart-lang.github.io/linter/lints/avoid_setters_without_getters.html
- avoid_setters_without_getters
# Don't reuse a type parameter when on with the same name already exists in the same scope
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/avoid_shadowing_type_parameters.html
- avoid_shadowing_type_parameters
# A single cascade operator can be replaced with a normal method call
# https://dart-lang.github.io/linter/lints/avoid_single_cascade_in_expression_statements.html
- avoid_single_cascade_in_expression_statements
# Might cause frame drops because of synchronous file access on mobile, especially on older phones with slow storage.
# There are no known measurements sync access does *not* drop frames.
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/avoid_slow_async_io.html
# - avoid_slow_async_io
# Don't use a parameter names which can be confused with a types (i.e. int, bool, num, ...)
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/avoid_types_as_parameter_names.html
- avoid_types_as_parameter_names
# Adding the type is not required, but sometimes improves readability. Therefore removing it doesn't always help
# https://dart-lang.github.io/linter/lints/avoid_types_on_closure_parameters.html
# - avoid_types_on_closure_parameters
# Containers without parameters have no effect and can be removed
# https://dart-lang.github.io/linter/lints/avoid_unnecessary_containers.html
- avoid_unnecessary_containers
# Unused parameters should be removed
# https://dart-lang.github.io/linter/lints/avoid_unused_constructor_parameters.html
- avoid_unused_constructor_parameters
# TODO double check
# For async functions use `Future<void>` as return value, not `void`
# This allows usage of the await keyword and prevents operations from running in parallel.
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/avoid_void_async.html
- avoid_void_async
# Flutter mobile only: Web packages aren't available in mobile flutter apps
# https://dart-lang.github.io/linter/lints/avoid_web_libraries_in_flutter.html
- avoid_web_libraries_in_flutter
# Use the await keyword only for futures. There is nothing to await in synchronous code
# https://dart-lang.github.io/linter/lints/await_only_futures.html
- await_only_futures
# Follow the style guide and use UpperCamelCase for extensions
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/camel_case_extensions.html
- camel_case_extensions
# Follow the style guide and use UpperCamelCase for class names and typedefs
# https://dart-lang.github.io/linter/lints/camel_case_types.html
- camel_case_types
# Prevents leaks and code executing after their lifecycle.
# Discussion https://github.com/passsy/dart-lint/issues/4
#
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/cancel_subscriptions.html
- cancel_subscriptions
# The cascade syntax is weird and you shouldn't be forced to use it.
# False positives:
# https://github.com/dart-lang/linter/issues/1589
#
# https://dart-lang.github.io/linter/lints/cascade_invocations.html
# - cascade_invocations
# False positives, not reliable enough
# - https://github.com/dart-lang/linter/issues/1381
#
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/close_sinks.html
# - close_sinks
# False positives:
# - https://github.com/dart-lang/linter/issues/1142
#
# https://dart-lang.github.io/linter/lints/comment_references.html
# - comment_references
# Follow standard dart naming style.
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/constant_identifier_names.html
# - constant_identifier_names
# Prevents hard to debug code
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/control_flow_in_finally.html
- control_flow_in_finally
# Single line `if`s are fine, but when a new line splits the bool expression and body curly braces
# are recommended. It prevents the danging else problem and easily allows the addition of more lines inside
# the if body
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/curly_braces_in_flow_control_structures.html
- curly_braces_in_flow_control_structures
# Still experimental and pretty much work when enforced
# https://dart-lang.github.io/linter/lints/diagnostic_describe_all_properties.html
# - diagnostic_describe_all_properties
# Follows dart style. Fully supported by IDEs and no manual effort for a consistent style
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/directives_ordering.html
- directives_ordering
# Add a comment why no further error handling is required
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/empty_catches.html
- empty_catches
# Removed empty constructor bodies
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/empty_constructor_bodies.html
- empty_constructor_bodies
# Don't allow empty if bodies. Works together with curly_braces_in_flow_control_structures
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/empty_statements.html
- empty_statements
# Follow dart file naming schema
# https://dart-lang.github.io/linter/lints/file_names.html
- file_names
# Very flutter specific, not applicable for all projects
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/flutter_style_todos.html
# - flutter_style_todos # not all todos require a ticket
# hashCode and equals need to be consistent. One can't live without another.
# https://dart-lang.github.io/linter/lints/hash_and_equals.html
- hash_and_equals
# DON'T import implementation files from another package.
# If you need access to some internal code, create an issue
# https://dart-lang.github.io/linter/lints/implementation_imports.html
- implementation_imports
# Although there are some false positives, this lint generally catches unnecessary checks
# - https://github.com/dart-lang/linter/issues/811
#
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/invariant_booleans.html
- invariant_booleans
# Type check for Iterable<T>.contains(other) where other is! T
# otherwise contains will always report false. Those errors are usually very hard to catch.
# https://dart-lang.github.io/linter/lints/iterable_contains_unrelated_type.html
- iterable_contains_unrelated_type
# Hint to join return and assignment.
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/join_return_with_assignment.html
- join_return_with_assignment
# Makes sure a library name is a valid dart identifier.
# This comes in handy for test files combining multiple tests where the file name can be used as identifier
#
# ```
# import src/some_test.dart as some_test;
#
# main() {
# some_test.main();
# }
# ```
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/library_names.html
- library_names
# Follow dart style
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/library_prefixes.html
- library_prefixes
# Nobody wants to manually wrap lines when changing a few words. This rule is too hard to be a "general" rule
# https://dart-lang.github.io/linter/lints/lines_longer_than_80_chars.html
# - lines_longer_than_80_chars
# Type check for List<T>.remove(item) where item is! T
# The list can't contain item. Those errors are not directly obvious especially when refactoring.
# https://dart-lang.github.io/linter/lints/list_remove_unrelated_type.html
- list_remove_unrelated_type
# Good for libraries to prevent unnecessary code paths.
# False positives may occur for applications when boolean properties are generated by external programs
# producing auto-generated source code
#
# Known issue: while(true) loops https://github.com/dart-lang/linter/issues/453
#
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/literal_only_boolean_expressions.html
# - literal_only_boolean_expressions
# Concat Strings obviously with `+` inside a list.
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/no_adjacent_strings_in_list.html
- no_adjacent_strings_in_list
# Second case is basically dead code which will never be reached.
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/no_duplicate_case_values.html
- no_duplicate_case_values
# Flutter only: `createState` shouldn't pass information into the state
# https://dart-lang.github.io/linter/lints/no_logic_in_create_state.html
- no_logic_in_create_state
# Follow dart style naming conventions
# https://dart-lang.github.io/linter/lints/non_constant_identifier_names.html
- non_constant_identifier_names
# Might become irrelevant when non-nullable types land in dart. Until then use this lint check which checks for
# non null arguments for specific dart sdk methods.
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/null_closures.html
- null_closures
# Types for local variables may improve readability.
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/omit_local_variable_types.html
# - omit_local_variable_types
# Defining interfaces (abstract classes), with only one method, makes sense architecture wise
# Discussion: https://github.com/passsy/dart-lint/issues/2
#
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/one_member_abstracts.html
# - one_member_abstracts
# Since Errors aren't intended to be caught (see avoid_catching_errors), throwing anything
# doesn't cause trouble.
# https://dart-lang.github.io/linter/lints/only_throw_errors.html
# - only_throw_errors
# Highlights unintentionally overridden fields.
# https://dart-lang.github.io/linter/lints/overridden_fields.html
- overridden_fields
# Only relevant for packages, not applications or general dart code
# https://dart-lang.github.io/linter/lints/package_api_docs.html
# - package_api_docs
# Follow dart style package naming convention
# https://dart-lang.github.io/linter/lints/package_names.html
- package_names
# Seems very rare, especially for applications.
# https://dart-lang.github.io/linter/lints/package_prefixed_library_names.html
- package_prefixed_library_names
# Most likely a mistake, if not: bad practice
# Not compatible with NNBD
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/parameter_assignments.html
- parameter_assignments
# Is contradictory to `no_adjacent_strings_in_list`
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/prefer_adjacent_string_concatenation.html
# - prefer_adjacent_string_concatenation
# Makes it easier to migrate to const constructors and to have final fields
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/prefer_asserts_in_initializer_lists.html
- prefer_asserts_in_initializer_lists
# Assertions blocks don't require a message because they throw simple to understand errors
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/prefer_asserts_with_message.html
# - prefer_asserts_with_message
# Collection literals are shorter. They exists, use them.
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/prefer_collection_literals.html
- prefer_collection_literals
# Use the ??= operator when possible
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/prefer_conditional_assignment.html
- prefer_conditional_assignment
# Always use const when possible, make runtime faster
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/prefer_const_constructors.html
- prefer_const_constructors
# Add a const constructor when possible
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/prefer_const_constructors_in_immutables.html
- prefer_const_constructors_in_immutables
# final is good, const is better
# https://dart-lang.github.io/linter/lints/prefer_const_declarations.html
- prefer_const_declarations
# Always use const when possible, make runtime faster
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/prefer_const_literals_to_create_immutables.html
- prefer_const_literals_to_create_immutables
# Dart has named constructors. Static methods in other languages (java) are a workaround which don't have
# named constructors.
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/prefer_constructors_over_static_methods.html
- prefer_constructors_over_static_methods
# Contains may be faster and is easier to read
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/prefer_contains.html
- prefer_contains
# Use whatever makes you happy. lint doesn't define a style
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/prefer_double_quotes.html
# - prefer_double_quotes
# Prevent confusion with call-side when using named parameters
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/prefer_equal_for_default_values.html
- prefer_equal_for_default_values
# Single line methods + implementation makes it hard to write comments for that line.
# Dense code isn't necessarily better code.
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/prefer_expression_function_bodies.html
# - prefer_expression_function_bodies
# Avoid accidental reassignments and allows the compiler to do optimizations.
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/prefer_final_fields.html
- prefer_final_fields
# Helps avoid accidental reassignments and allows the compiler to do optimizations.
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/prefer_final_in_for_each.html
- prefer_final_in_for_each
# Helps avoid accidental reassignments and allows the compiler to do optimizations.
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/prefer_final_locals.html
- prefer_final_locals
# Saves lot of code
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/prefer_for_elements_to_map_fromIterable.html
- prefer_for_elements_to_map_fromIterable
# Dense code isn't necessarily better code
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/prefer_foreach.html
# - prefer_foreach
# As Dart allows local function declarations, it is a good practice to use them in the place of function literals.
# https://dart-lang.github.io/linter/lints/prefer_function_declarations_over_variables.html
- prefer_function_declarations_over_variables
# For consistency
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/prefer_generic_function_type_aliases.html
- prefer_generic_function_type_aliases
# Allows potential usage of const
# https://dart-lang.github.io/linter/lints/prefer_if_elements_to_conditional_expressions.html
- prefer_if_elements_to_conditional_expressions
# Dart has a special operator for this, use it
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/prefer_if_null_operators.html
- prefer_if_null_operators
# Terser code
# https://dart-lang.github.io/linter/lints/prefer_initializing_formals.html
- prefer_initializing_formals
# Easier move towards const, and way easier to read
# https://dart-lang.github.io/linter/lints/prefer_inlined_adds.html
- prefer_inlined_adds
# There is no argument which makes int literals better than double literals for doubles.
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/prefer_int_literals.html
# - prefer_int_literals
# Interpolate, use less "", '' and +
# https://dart-lang.github.io/linter/lints/prefer_interpolation_to_compose_strings.html
- prefer_interpolation_to_compose_strings
# Iterables do not necessary know their length
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/prefer_is_empty.html
- prefer_is_empty
# Easier to read
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/prefer_is_not_empty.html
- prefer_is_not_empty
# Use the `foo is! Foo` instead of `!(foo is Foo)`
# https://dart-lang.github.io/linter/lints/prefer_is_not_operator.html
- prefer_is_not_operator
# Easier to read
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/prefer_iterable_whereType.html
- prefer_iterable_whereType
# Users of a 3rd party mixins can't change 3rd party code to use the mixin syntax.
# This makes the rule useless
# https://dart-lang.github.io/linter/lints/prefer_mixin.html
# - prefer_mixin
# Makes expressions with null checks easier to read.
# https://github.com/flutter/flutter/pull/32711#issuecomment-492930932
- prefer_null_aware_operators
# Conflicting with `avoid_relative_lib_imports` which is enforced
# https://dart-lang.github.io/linter/lints/prefer_relative_imports.html
# - prefer_relative_imports
# Use whatever makes you happy. noexcuse doesn't define a style
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/prefer_single_quotes.html
# - prefer_single_quotes
# Allows potential usage of const
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/prefer_spread_collections.html
- prefer_spread_collections
# Define types
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/prefer_typing_uninitialized_variables.html
- prefer_typing_uninitialized_variables
# Null is not a type, use void
# https://dart-lang.github.io/linter/lints/prefer_void_to_null.html
- prefer_void_to_null
# Document the replacement API
# https://dart-lang.github.io/linter/lints/provide_deprecation_message.html
- provide_deprecation_message
# Definitely not a rule for standard dart code. Maybe relevant for packages
# https://dart-lang.github.io/linter/lints/public_member_api_docs.html
# - public_member_api_docs
# Hints accidental recursions
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/recursive_getters.html
- recursive_getters
# Follow dart style use triple slashes
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/slash_for_doc_comments.html
- slash_for_doc_comments
# Flutter only, always put child last
# https://dart-lang.github.io/linter/lints/sort_child_properties_last.html
# - sort_child_properties_last
# Working, results in consistent code. But too opinionated
# Discussion: https://github.com/passsy/dart-lint/issues/1
#
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/sort_constructors_first.html
# - sort_constructors_first
# Any sorting is better than no sorting
# https://dart-lang.github.io/linter/lints/sort_pub_dependencies.html
- sort_pub_dependencies
# Default constructor comes first.
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/sort_unnamed_constructors_first.html
- sort_unnamed_constructors_first
# First test, then cast
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/test_types_in_equals.html
- test_types_in_equals
# Hard to debug and bad style
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/throw_in_finally.html
- throw_in_finally
# Type annotations make the compiler intelligent, use them
# https://dart-lang.github.io/linter/lints/type_annotate_public_apis.html
- type_annotate_public_apis
# Don't add types for already typed constructor parameters.
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/type_init_formals.html
- type_init_formals
# Too many false positives.
# Using the pedantic package for the unawaited function doesn't make code better readable
#
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/unawaited_futures.html
# - unawaited_futures
# Remove async/await clutter when not required
# https://dart-lang.github.io/linter/lints/unnecessary_await_in_return.html
- unnecessary_await_in_return
# Remove unnecessary braces
# https://dart-lang.github.io/linter/lints/unnecessary_brace_in_string_interps.html
- unnecessary_brace_in_string_interps
# Yes, const everywhere. But not in an already const scope
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/unnecessary_const.html
- unnecessary_const
# Disabled because `final` prevents accidental reassignment
# https://dart-lang.github.io/linter/lints/unnecessary_final.html
# - unnecessary_final
# Getter/setters can be added later on in a non API breaking manner
# https://dart-lang.github.io/linter/lints/unnecessary_getters_setters.html
- unnecessary_getters_setters
# Flutter setState is a good example where a lambda should always be used.
# https://github.com/dart-lang/linter/issues/498
#
# Some generic code sometimes requires lambdas, otherwise the generic type isn't forwarded correctly.
#
# https://dart-lang.github.io/linter/lints/unnecessary_lambdas.html
# - unnecessary_lambdas
# Remove the optional `new` keyword
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/unnecessary_new.html
- unnecessary_new
# Don't assign `null` when value is already `null`.
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/unnecessary_null_aware_assignments.html
- unnecessary_null_aware_assignments
# Don't assign `null` when value is already `null`.
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/unnecessary_null_in_if_null_operators.html
- unnecessary_null_in_if_null_operators
# Remove overrides which simply call super
# https://dart-lang.github.io/linter/lints/unnecessary_overrides.html
- unnecessary_overrides
# Remove clutter where possible
# https://dart-lang.github.io/linter/lints/unnecessary_parenthesis.html
- unnecessary_parenthesis
# Avoid magic overloads of + operators
# https://dart-lang.github.io/linter/lints/unnecessary_statements.html
- unnecessary_statements
# The variable is clear, remove clutter
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/unnecessary_this.html
- unnecessary_this
# Highlights potential bugs where unrelated types are compared with another. (always *not* equal).
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/unrelated_type_equality_checks.html
- unrelated_type_equality_checks
# Web only
# https://dart-lang.github.io/linter/lints/unsafe_html.html
- unsafe_html
# Always use hex syntax Color(0x00000001), never Color(1)
# https://dart-lang.github.io/linter/lints/use_full_hex_values_for_flutter_colors.html
- use_full_hex_values_for_flutter_colors
# Always use generic function type syntax, don't mix styles
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/use_function_type_syntax_for_parameters.html
- use_function_type_syntax_for_parameters
# Use rethrow to preserve the original stacktrace.
# https://dart.dev/guides/language/effective-dart/usage#do-use-rethrow-to-rethrow-a-caught-exception
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/use_rethrow_when_possible.html
- use_rethrow_when_possible
# Use the setter syntax
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/use_setters_to_change_properties.html
- use_setters_to_change_properties
# In most cases, using a string buffer is preferred for composing strings due to its improved performance.
# https://dart-lang.github.io/linter/lints/use_string_buffers.html
- use_string_buffers
# Naming is hard, strict rules don't help
# pedantic: disabled
# https://dart-lang.github.io/linter/lints/use_to_and_as_if_applicable.html
# - use_to_and_as_if_applicable
# Catches invalid regular expressions.
# pedantic: enabled
# https://dart-lang.github.io/linter/lints/valid_regexps.html
- valid_regexps
# Don't assign anything to void
# https://dart-lang.github.io/linter/lints/void_checks.html
- void_checks
\ No newline at end of file
import 'package:implicitly_animated_reorderable_list_2/src/diff/myers_diff.dart';
// ignore_for_file: avoid_print
class Item {
Item(this.id, this.value);
int id;
int value;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Item && runtimeType == other.runtimeType && id == other.id;
@override
int get hashCode => id.hashCode;
}
Future<void> main() async {
final List<Item> list = List.generate(1000, (index) => Item(index, index));
final List<Item> newList = List.from(list)..shuffle();
final start = DateTime.now();
await MyersDiff.diff<Item>(
newList,
list,
areItemsTheSame: (a, b) => a.id == b.id,
);
final millis = DateTime.now().difference(start).inMilliseconds;
print('Diffing ${newList.length} elements took $millis milliseconds.');
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 28
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.example"
minSdkVersion 16
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.example">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:name="io.flutter.app.FlutterApplication"
android:label="example"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
package io.flutter.plugins;
import io.flutter.plugin.common.PluginRegistry;
/**
* Generated file. Do not edit.
*/
public final class GeneratedPluginRegistrant {
public static void registerWith(PluginRegistry registry) {
if (alreadyRegisteredWith(registry)) {
return;
}
}
private static boolean alreadyRegisteredWith(PluginRegistry registry) {
final String key = GeneratedPluginRegistrant.class.getCanonicalName();
if (registry.hasPlugin(key)) {
return true;
}
registry.registrarFor(key);
return false;
}
}
package com.example.example
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.GeneratedPluginRegistrant
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
}
}
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
</resources>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
buildscript {
ext.kotlin_version = '1.3.50'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
jcenter()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
sdk.dir=/Users/david/Library/Android/sdk
flutter.sdk=/Users/david/prog/flutter
\ No newline at end of file
include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":$name"
project(":$name").projectDir = pluginDirectory
}
[{"family":"MaterialIcons","fonts":[{"asset":"fonts/MaterialIcons-Regular.otf"}]}]
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>8.0</string>
</dict>
</plist>
#include "Generated.xcconfig"
// This is a generated file; do not edit or check into version control.
FLUTTER_ROOT=/Users/david/prog/flutter
FLUTTER_APPLICATION_PATH=/Users/david/Github/implicitly_animated_reorderable_list_2/example
COCOAPODS_PARALLEL_CODE_SIGN=true
FLUTTER_TARGET=lib/main.dart
FLUTTER_BUILD_DIR=build
FLUTTER_BUILD_NAME=1.0.0
FLUTTER_BUILD_NUMBER=1
EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386
EXCLUDED_ARCHS[sdk=iphoneos*]=armv7
DART_OBFUSCATION=false
TRACK_WIDGET_CREATION=true
TREE_SHAKE_ICONS=false
PACKAGE_CONFIG=.dart_tool/package_config.json
#include "Generated.xcconfig"
#!/bin/sh
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=/Users/david/prog/flutter"
export "FLUTTER_APPLICATION_PATH=/Users/david/Github/implicitly_animated_reorderable_list_2/example"
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
export "FLUTTER_TARGET=lib/main.dart"
export "FLUTTER_BUILD_DIR=build"
export "FLUTTER_BUILD_NAME=1.0.0"
export "FLUTTER_BUILD_NUMBER=1"
export "DART_OBFUSCATION=false"
export "TRACK_WIDGET_CREATION=true"
export "TREE_SHAKE_ICONS=false"
export "PACKAGE_CONFIG=.dart_tool/package_config.json"
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B80C3931E831B6300D905FE /* App.framework */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEBA1CF902C7004384FC /* Flutter.framework */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
97C146F11CF9000F007C117D /* Supporting Files */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
97C146F11CF9000F007C117D /* Supporting Files */ = {
isa = PBXGroup;
children = (
);
name = "Supporting Files";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1020;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GeneratedPluginRegistrant_h
#define GeneratedPluginRegistrant_h
#import <Flutter/Flutter.h>
NS_ASSUME_NONNULL_BEGIN
@interface GeneratedPluginRegistrant : NSObject
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry;
@end
NS_ASSUME_NONNULL_END
#endif /* GeneratedPluginRegistrant_h */
//
// Generated file. Do not edit.
//
// clang-format off
#import "GeneratedPluginRegistrant.h"
@implementation GeneratedPluginRegistrant
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
}
@end
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>example</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>
#import "GeneratedPluginRegistrant.h"
/* import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:implicitly_animated_reorderable_list_2/implicitly_animated_reorderable_list_2.dart';
import 'util/box.dart';
class Examples extends StatelessWidget {
const Examples({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
// * ImplicitlyAnimatedList
// Specify the generic type of the data in the list.
ImplicitlyAnimatedList<Language>(
// The current items in the list.
data: items,
// Called by the DiffUtil to decide whether two object represent the same item.
// For example, if your items have unique ids, this method should check their id equality.
areItemsTheSame: (a, b) => a.id == b.id,
// Called, as needed, to build list item widgets.
// List items are only built when they're scrolled into view.
itemBuilder: (context, animation, item, index) {
// Specifiy a transition to be used by the ImplicitlyAnimatedList.
// In this case a custom transition.
return SizeFadeTranstion(
sizeFraction: 0.7,
curve: Curves.easeInOut,
animation: animation,
child: Text(item.name),
);
},
// An optional builder when an item was removed from the list.
// If not specified, the List uses the itemBuilder with
// the animation reversed.
removedItemBuilder: (context, animation, oldItem) {
return FadeTransition(
opacity: animation,
child: Text(oldItem.name),
);
},
);
// * ImplicitlyAnimatedReorderableList
ImplicitlyAnimatedReorderableList<Language>(
data: items,
areItemsTheSame: (a, b) => a.id == b.id,
itemBuilder: (context, itemAnimation, item, index) {
// Each item must be wrapped in a Reorderable widget.
return Reorderable(
// Each item must have a unique key.
key: ValueKey(item),
// The animation of the Reorderable builder can be used to
// change to appearance of the item between dragged and normal
// state. For example to add elevation. Implicit animation are
// not yet supported.
builder: (context, dragAnimation, inDrag) {
final t = dragAnimation.value;
final elevation = lerpDouble(0, 8, t);
final color = Color.lerp(Colors.white, Colors.white.withOpacity(0.8), t);
return SizeFadeTranstion(
sizeFraction: 0.7,
curve: Curves.easeInOut,
animation: itemAnimation,
child: Box(
color: color,
elevation: elevation,
child: ListTile(
title: Text(item.name),
// The child of a Handle can initialize a drag/reorder.
// This could for example be an Icon or the whole item itself. You can
// use the delay parameter to specify the duration for how long a pointer
// must press the child, until it can be dragged.
trailing: Handle(
delay: const Duration(milliseconds: 100),
child: Icon(
Icons.list,
color: Colors.grey,
),
),
),
),
);
},
);
},
);
}
}
*/
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:example/ui/ui.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
),
);
return MaterialApp(
title: 'Implicitly Animated Reorderable List Example',
theme: ThemeData.light().copyWith(
accentColor: Colors.amber.shade800,
dividerTheme: DividerThemeData(
thickness: 1,
color: Colors.grey.shade300,
),
),
debugShowCheckedModeBanner: false,
home: const LanguagePage(),
);
}
}
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:implicitly_animated_reorderable_list_2/implicitly_animated_reorderable_list_2.dart';
import 'package:implicitly_animated_reorderable_list_2/transitions.dart';
import '../util/util.dart';
import 'ui.dart';
class LanguagePage extends StatefulWidget {
const LanguagePage({
Key? key,
}) : super(key: key);
@override
_LanguagePageState createState() => _LanguagePageState();
}
class _LanguagePageState extends State<LanguagePage>
with SingleTickerProviderStateMixin {
static const double _horizontalHeight = 96;
static const List<String> options = [
'Shuffle',
'Test',
];
final List<Language> selectedLanguages = [
english,
german,
spanish,
french,
];
bool inReorder = false;
ScrollController scrollController = ScrollController();
void onReorderFinished(List<Language> newItems) {
scrollController.jumpTo(scrollController.offset);
setState(() {
inReorder = false;
selectedLanguages
..clear()
..addAll(newItems);
});
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final textTheme = theme.textTheme;
return Scaffold(
appBar: AppBar(
title: const Text('Examples'),
backgroundColor: theme.accentColor,
actions: <Widget>[
_buildPopupMenuButton(textTheme),
],
),
body: ListView(
controller: scrollController,
// Prevent the ListView from scrolling when an item is
// currently being dragged.
padding: const EdgeInsets.only(bottom: 24),
children: <Widget>[
_buildHeadline('Vertically'),
const Divider(height: 0),
_buildVerticalLanguageList(),
_buildHeadline('Horizontally'),
_buildHorizontalLanguageList(),
const SizedBox(height: 500),
],
),
);
}
// * An example of a vertically reorderable list.
Widget _buildVerticalLanguageList() {
final theme = Theme.of(context);
Reorderable buildReorderable(
Language lang,
Widget Function(Widget tile) transition,
) {
return Reorderable(
key: ValueKey(lang),
builder: (context, dragAnimation, inDrag) {
final tile = Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
_buildTile(lang),
const Divider(height: 0),
],
);
return AnimatedBuilder(
animation: dragAnimation,
builder: (context, _) {
final t = dragAnimation.value;
final color = Color.lerp(Colors.white, Colors.grey.shade100, t);
return Material(
color: color,
elevation: lerpDouble(0, 8, t)!,
child: transition(tile),
);
},
);
},
);
}
return ImplicitlyAnimatedReorderableList<Language>(
items: selectedLanguages,
shrinkWrap: true,
reorderDuration: const Duration(milliseconds: 200),
liftDuration: const Duration(milliseconds: 300),
physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
areItemsTheSame: (oldItem, newItem) => oldItem == newItem,
onReorderStarted: (item, index) => setState(() => inReorder = true),
onReorderFinished: (movedLanguage, from, to, newItems) {
// Update the underlying data when the item has been reordered!
onReorderFinished(newItems);
},
itemBuilder: (context, itemAnimation, lang, index) {
return buildReorderable(lang, (tile) {
return SizeFadeTransition(
sizeFraction: 0.7,
curve: Curves.easeInOut,
animation: itemAnimation,
child: tile,
);
});
},
updateItemBuilder: (context, itemAnimation, lang) {
return buildReorderable(lang, (tile) {
return FadeTransition(
opacity: itemAnimation,
child: tile,
);
});
},
footer: _buildFooter(context, theme.textTheme),
);
}
Widget _buildHorizontalLanguageList() {
return Container(
height: _horizontalHeight,
alignment: Alignment.center,
child: ImplicitlyAnimatedReorderableList<Language>(
items: selectedLanguages,
padding: const EdgeInsets.symmetric(horizontal: 24),
scrollDirection: Axis.horizontal,
areItemsTheSame: (oldItem, newItem) => oldItem == newItem,
onReorderStarted: (item, index) => setState(() => inReorder = true),
onReorderFinished: (item, from, to, newItems) =>
onReorderFinished(newItems),
itemBuilder: (context, itemAnimation, item, index) {
return Reorderable(
key: ValueKey(item.toString()),
builder: (context, dragAnimation, inDrag) {
final t = dragAnimation.value;
final box = _buildBox(item, t);
return SizeFadeTransition(
animation: itemAnimation,
axis: Axis.horizontal,
axisAlignment: 1.0,
curve: Curves.ease,
child: box,
);
},
);
},
updateItemBuilder: (context, itemAnimation, item) {
return Reorderable(
key: ValueKey(item.toString()),
child: FadeTransition(
opacity: itemAnimation,
child: _buildBox(item, 0),
),
);
},
),
);
}
Widget _buildTile(Language lang) {
final theme = Theme.of(context);
final textTheme = theme.textTheme;
final List<Widget> actions = [
SlideAction(
closeOnTap: true,
color: Colors.redAccent,
onTap: () => setState(() => selectedLanguages.remove(lang)),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Icon(
Icons.delete,
color: Colors.white,
),
const SizedBox(height: 4),
Text(
'Delete',
style: textTheme.bodyText2?.copyWith(
color: Colors.white,
),
),
],
),
),
),
];
return Slidable(
actionPane: const SlidableBehindActionPane(),
actions: actions,
secondaryActions: actions,
child: Container(
alignment: Alignment.center,
// For testing different size item. You can comment this line
padding: lang.englishName == 'English'
? const EdgeInsets.symmetric(vertical: 16.0)
: EdgeInsets.zero,
child: ListTile(
title: Text(
lang.nativeName,
style: textTheme.bodyText2?.copyWith(
fontSize: 16,
),
),
subtitle: Text(
lang.englishName,
style: textTheme.bodyText1?.copyWith(
fontSize: 15,
),
),
leading: SizedBox(
width: 36,
height: 36,
child: Center(
child: Text(
'${selectedLanguages.indexOf(lang) + 1}',
style: textTheme.bodyText2?.copyWith(
color: theme.accentColor,
fontSize: 16,
),
),
),
),
trailing: const Handle(
delay: Duration(milliseconds: 0),
capturePointer: true,
child: Icon(
Icons.drag_handle,
color: Colors.grey,
),
),
),
),
);
}
Widget _buildBox(Language item, double t) {
final theme = Theme.of(context);
final textTheme = theme.textTheme;
final elevation = lerpDouble(0, 8, t)!;
return Handle(
delay: const Duration(milliseconds: 500),
child: Box(
height: _horizontalHeight,
borderRadius: 8,
border: Border.all(
color: Colors.grey.shade300,
width: 2,
),
elevation: elevation,
padding: const EdgeInsets.all(16),
color: Colors.white,
margin: const EdgeInsets.only(right: 8),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
item.nativeName,
style: textTheme.bodyText2,
),
const SizedBox(height: 8),
Text(
item.englishName,
style: textTheme.bodyText1,
),
],
),
),
),
);
}
Widget _buildFooter(BuildContext context, TextTheme textTheme) {
return Box(
color: Colors.white,
onTap: () async {
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const LanguageSearchPage(),
),
);
if (result != null && !selectedLanguages.contains(result)) {
setState(() {
selectedLanguages.add(result);
});
}
},
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
leading: const SizedBox(
height: 36,
width: 36,
child: Center(
child: Icon(
Icons.add,
color: Colors.grey,
),
),
),
title: Text(
'Add a language',
style: textTheme.bodyText1?.copyWith(
fontSize: 16,
),
),
),
const Divider(height: 0),
],
),
);
}
Widget _buildHeadline(String headline) {
final theme = Theme.of(context);
final textTheme = theme.textTheme;
Widget buildDivider() => Container(
height: 2,
color: Colors.grey.shade300,
);
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const SizedBox(height: 16),
buildDivider(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
child: Text(
headline,
style: textTheme.bodyText1?.copyWith(
fontWeight: FontWeight.bold,
),
),
),
buildDivider(),
const SizedBox(height: 16),
],
);
}
Widget _buildPopupMenuButton(TextTheme textTheme) {
return PopupMenuButton<String>(
padding: const EdgeInsets.all(0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
onSelected: (value) {
switch (value) {
case 'Shuffle':
setState(selectedLanguages.shuffle);
break;
case 'Test':
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const TestPage(),
),
);
break;
}
},
itemBuilder: (context) => options.map((option) {
return PopupMenuItem(
value: option,
child: Text(
option,
style: textTheme.bodyText1,
),
);
}).toList(),
);
}
@override
void dispose() {
scrollController.dispose();
super.dispose();
}
}
class Pair<A, B> {
final A first;
final B second;
Pair(
this.first,
this.second,
);
}
import 'package:flutter/material.dart';
import 'package:implicitly_animated_reorderable_list_2/implicitly_animated_reorderable_list_2.dart';
import 'package:implicitly_animated_reorderable_list_2/transitions.dart';
import '../util/util.dart';
class LanguageSearchPage extends StatefulWidget {
const LanguageSearchPage({Key? key}) : super(key: key);
@override
_LanguageSearchPageState createState() => _LanguageSearchPageState();
}
class _LanguageSearchPageState extends State<LanguageSearchPage> {
final List<Language> filteredLanguages = List.from(languages);
late final _controller = TextEditingController()
..addListener(
_onQueryChanged,
);
String get text => _controller.text.trim();
void _onQueryChanged() {
filteredLanguages.clear();
if (text.isEmpty) {
filteredLanguages
..clear()
..addAll(languages);
setState(() {});
return;
}
final query = text.toLowerCase();
for (final lang in languages) {
final englishName = lang.englishName.toLowerCase();
final nativeName = lang.nativeName.toLowerCase();
final startsWith = englishName.startsWith(query) || nativeName.startsWith(query);
if (startsWith) {
filteredLanguages.add(lang);
}
}
for (final lang in languages) {
final englishName = lang.englishName.toLowerCase();
final nativeName = lang.nativeName.toLowerCase();
final contains = englishName.contains(query) || nativeName.contains(query);
if (contains && !filteredLanguages.contains(lang)) {
filteredLanguages.add(lang);
}
}
setState(() {});
}
Widget _buildItem(Language lang) {
final theme = Theme.of(context);
final textTheme = theme.textTheme;
return Box(
border: Border(
bottom: BorderSide(
color: Colors.grey.shade200,
),
),
color: Colors.white,
onTap: () => Navigator.pop(context, lang),
child: ListTile(
title: HighlightText(
query: text,
text: lang.nativeName,
style: textTheme.bodyText2?.copyWith(
fontSize: 16,
),
activeStyle: textTheme.bodyText2?.copyWith(
fontSize: 16,
fontWeight: FontWeight.w900,
),
),
subtitle: HighlightText(
query: text,
text: lang.englishName,
style: textTheme.bodyText1?.copyWith(
fontSize: 15,
),
activeStyle: textTheme.bodyText1?.copyWith(
fontSize: 15,
fontWeight: FontWeight.bold,
),
),
),
);
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final textTheme = theme.textTheme;
final padding = MediaQuery.of(context).viewPadding.top;
return Scaffold(
appBar: _buildAppBar(padding, theme, textTheme),
body: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child:
filteredLanguages.isNotEmpty ? _buildList() : _buildNoLanguagesPlaceholder(),
),
);
}
Widget _buildList() {
return ImplicitlyAnimatedList<Language>(
items: filteredLanguages,
updateDuration: const Duration(milliseconds: 400),
areItemsTheSame: (a, b) => a == b,
itemBuilder: (context, animation, lang, _) {
return SizeFadeTransition(
sizeFraction: 0.7,
curve: Curves.easeInOut,
animation: animation,
child: _buildItem(lang),
);
},
updateItemBuilder: (context, animation, lang) {
return FadeTransition(
opacity: animation,
child: _buildItem(lang),
);
},
);
}
PreferredSize _buildAppBar(double padding, ThemeData theme, TextTheme textTheme) {
return PreferredSize(
preferredSize: Size.fromHeight(56 + padding),
child: Box(
height: 56 + padding,
width: double.infinity,
color: theme.accentColor,
elevation: 4,
shadowColor: Colors.black.withOpacity(0.2),
child: Column(
children: <Widget>[
SizedBox(height: padding),
Expanded(
child: Row(
children: <Widget>[
const BackButton(
color: Colors.white,
),
Expanded(
child: TextField(
autofocus: true,
controller: _controller,
textInputAction: TextInputAction.search,
style: textTheme.bodyText2?.copyWith(
color: Colors.white,
fontSize: 18,
),
decoration: InputDecoration(
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
hintText: 'Search for a language',
hintStyle: textTheme.bodyText2?.copyWith(
color: Colors.grey.shade200,
fontSize: 16,
),
),
),
),
AnimatedOpacity(
duration: const Duration(milliseconds: 350),
opacity: text.isEmpty ? 0.0 : 1.0,
child: IconButton(
icon: const Icon(
Icons.clear,
color: Colors.white,
),
onPressed: () => _controller.text = '',
),
),
],
),
),
],
),
),
);
}
Widget _buildNoLanguagesPlaceholder() {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: const <Widget>[
Icon(
Icons.translate,
color: Colors.grey,
),
SizedBox(height: 16),
Text(
'No languages found!',
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
],
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:implicitly_animated_reorderable_list_2/implicitly_animated_reorderable_list_2.dart';
import 'package:implicitly_animated_reorderable_list_2/transitions.dart';
class TestPage extends StatefulWidget {
const TestPage();
@override
State<StatefulWidget> createState() => TestPageState();
}
class TestPageState extends State<TestPage> {
static const maxLength = 1000;
List<Test> nestedList = List.generate(maxLength, (i) => Test(i));
Timer? _timer;
@override
void initState() {
super.initState();
// crazyListOperationMadness();
}
void crazyListOperationMadness() {
void assignNewList() {
nestedList = List.generate(Random().nextInt(maxLength), (i) => Test(i))..shuffle();
setState(() {});
}
_timer = Timer.periodic(
const Duration(milliseconds: 10),
(_) async {
assignNewList();
assignNewList();
nestedList = List.generate(Random().nextInt(maxLength), (i) => Test(i))
..shuffle();
setState(() {});
},
);
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final textTheme = theme.textTheme;
return Scaffold(
appBar: AppBar(backgroundColor: Colors.amber),
body: Scrollbar(
child: ImplicitlyAnimatedReorderableList<Test>(
padding: const EdgeInsets.all(24),
items: nestedList,
areItemsTheSame: (oldItem, newItem) => oldItem == newItem,
onReorderFinished: (item, from, to, newList) {
setState(() {
nestedList
..clear()
..addAll(newList);
});
},
header: InkWell(
onTap: () {
if (_timer == null) {
crazyListOperationMadness();
} else {
_timer?.cancel();
_timer = null;
}
},
child: Container(
height: 120,
color: _timer == null ? Colors.red : Colors.yellow,
child: Center(
child: Text(
'Header',
style: textTheme.headline6?.copyWith(color: Colors.white),
),
),
),
),
footer: Container(
height: 120,
color: Colors.red,
child: Center(
child: Text(
'Footer',
style: textTheme.headline6?.copyWith(color: Colors.white),
),
),
),
itemBuilder: (context, itemAnimation, item, index) {
return Reorderable(
key: ValueKey(item),
builder: (context, dragAnimation, inDrag) => AnimatedBuilder(
animation: dragAnimation,
builder: (context, child) => Card(
elevation: 4,
// SizeFadeTransition clips, so use the
// Card as a parent to avoid the box shadow
// to be clipped.
child: SizeFadeTransition(
animation: itemAnimation,
child: Handle(
delay: const Duration(milliseconds: 600),
child: Container(
height: 120,
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text('${item.key}'),
const Icon(Icons.menu),
],
),
),
),
),
),
),
);
},
),
),
);
}
}
class Test {
final int key;
Test(this.key);
@override
bool operator ==(Object o) {
if (identical(this, o)) return true;
return o is Test && o.key == key;
}
@override
int get hashCode => key.hashCode;
}
export 'lang_page.dart';
export 'search_page.dart';
export 'test_page.dart';
import 'dart:math';
import 'package:flutter/material.dart';
enum ShadowDirection {
topLeft,
top,
topRight,
right,
bottomRight,
bottom,
bottomLeft,
left,
center,
}
class Box extends StatelessWidget {
final double borderRadius;
final double elevation;
final double? height;
final double? width;
final Border? border;
final BorderRadius? customBorders;
final EdgeInsets? margin;
final EdgeInsets? padding;
final Widget? child;
final Color color;
final Color shadowColor;
final List<BoxShadow>? boxShadows;
final VoidCallback? onTap;
final VoidCallback? onLongPress;
final VoidCallback? onDoubleTap;
final BoxShape boxShape;
final AlignmentGeometry? alignment;
final ShadowDirection shadowDirection;
final Color? splashColor;
final Duration? duration;
final BoxConstraints? constraints;
const Box({
Key? key,
this.child,
this.border,
this.color = Colors.transparent,
this.borderRadius = 0.0,
this.elevation = 0.0,
this.splashColor,
this.shadowColor = Colors.black12,
this.onTap,
this.onDoubleTap,
this.onLongPress,
this.height,
this.width,
this.margin,
this.customBorders,
this.alignment,
this.boxShadows,
this.constraints,
this.duration,
this.boxShape = BoxShape.rectangle,
this.shadowDirection = ShadowDirection.bottomRight,
this.padding = const EdgeInsets.all(0),
}) : super(key: key);
static const wrap = -1;
bool get circle => boxShape == BoxShape.circle;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final w = width;
final h = height;
final br = customBorders ??
BorderRadius.circular(
boxShape == BoxShape.rectangle
? borderRadius
: w != null
? w / 2.0
: h != null
? h / 2.0
: 0,
);
Widget content = Padding(
padding: padding ?? EdgeInsets.zero,
child: child,
);
if (boxShape == BoxShape.circle || (customBorders != null || borderRadius > 0.0)) {
content = ClipRRect(
borderRadius: br,
child: content,
);
}
if (onTap != null || onLongPress != null || onDoubleTap != null) {
content = Material(
color: Colors.transparent,
type: MaterialType.transparency,
shape: circle ? const CircleBorder() : RoundedRectangleBorder(borderRadius: br),
child: InkWell(
splashColor: splashColor ?? theme.splashColor,
highlightColor: theme.highlightColor,
hoverColor: theme.hoverColor,
focusColor: theme.focusColor,
customBorder:
circle ? const CircleBorder() : RoundedRectangleBorder(borderRadius: br),
onTap: onTap,
onLongPress: onLongPress,
onDoubleTap: onDoubleTap,
child: content,
),
);
}
final List<BoxShadow>? boxShadow = boxShadows ??
((elevation > 0 && (shadowColor.opacity > 0))
? [
BoxShadow(
color: shadowColor,
offset: _getShadowOffset(min(elevation / 5.0, 1.0)),
blurRadius: elevation,
spreadRadius: 0,
),
]
: null);
final boxDecoration = BoxDecoration(
color: color,
borderRadius: circle || br == BorderRadius.zero ? null : br,
shape: boxShape,
boxShadow: boxShadow,
border: border,
);
return duration != null
? AnimatedContainer(
height: h,
width: w,
margin: margin,
alignment: alignment,
duration: duration!,
decoration: boxDecoration,
constraints: constraints,
child: content,
)
: Container(
height: h,
width: w,
margin: margin,
alignment: alignment,
decoration: boxDecoration,
constraints: constraints,
child: content,
);
}
Offset _getShadowOffset(double ele) {
final ym = 5 * ele;
final xm = 2 * ele;
switch (shadowDirection) {
case ShadowDirection.topLeft:
return Offset(-1 * xm, -1 * ym);
case ShadowDirection.top:
return Offset(0, -1 * ym);
case ShadowDirection.topRight:
return Offset(xm, -1 * ym);
case ShadowDirection.right:
return Offset(xm, 0);
case ShadowDirection.bottomRight:
return Offset(xm, ym);
case ShadowDirection.bottom:
return Offset(0, ym);
case ShadowDirection.bottomLeft:
return Offset(-1 * xm, ym);
case ShadowDirection.left:
return Offset(-1 * xm, 0);
default:
return Offset.zero;
}
}
}
import 'package:flutter/material.dart';
class HighlightText extends StatefulWidget {
final TextStyle? activeStyle;
final TextStyle? style;
final String query;
final String text;
final TextAlign textAlign;
final TextDirection? textDirection;
final bool softWrap;
final TextOverflow overflow;
final double textScaleFactor;
final int? maxLines;
const HighlightText({
Key? key,
this.activeStyle,
this.style,
this.query = '',
this.text = '',
this.textAlign = TextAlign.start,
this.textDirection,
this.softWrap = true,
this.overflow = TextOverflow.ellipsis,
this.textScaleFactor = 1.0,
this.maxLines,
}) : super(key: key);
@override
_HighlightTextState createState() => _HighlightTextState();
}
class _HighlightTextState extends State<HighlightText> {
TextStyle get style => widget.style ?? Theme.of(context).textTheme.bodyText2!;
TextStyle get activeStyle =>
widget.activeStyle ?? style.copyWith(fontWeight: FontWeight.bold);
@override
Widget build(BuildContext context) {
final idxs = getQueryHighlights(widget.text, widget.query);
return RichText(
textAlign: widget.textAlign,
maxLines: widget.maxLines,
overflow: widget.overflow,
softWrap: widget.softWrap,
textDirection: widget.textDirection,
textScaleFactor: widget.textScaleFactor,
text: TextSpan(
children: idxs.map((idx) {
return TextSpan(
text: widget.text.substring(idx.first, idx.second),
style: idx.third ? activeStyle : style,
);
}).toList(),
),
);
}
}
String replaceLast(String source, String matcher, String replacement) {
final index = source.lastIndexOf(matcher);
return source.replaceRange(index, index + matcher.length, replacement);
}
List<Triplet<int, int, bool>> getQueryHighlights(String text, String query) {
final t = text.toLowerCase();
final q = query.toLowerCase();
if (t.isEmpty || q.isEmpty || !t.contains(q)) return [Triplet(0, t.length, false)];
List<Triplet<int, int, bool>> idxs = [];
var w = t;
do {
final i = w.lastIndexOf(q);
final e = i + q.length;
if (i != -1) {
w = replaceLast(w, q, '');
idxs.insert(0, Triplet(i, e, true));
}
} while (w.contains(q));
if (idxs.isEmpty) {
idxs.add(Triplet(0, t.length, false));
} else {
final List<Triplet<int, int, bool>> result = [];
Triplet<int, int, bool>? last;
for (final idx in idxs) {
final isLast = idx == idxs.last;
if (last == null) {
if (idx.first == 0) {
result.add(idx);
} else {
result..add(Triplet(0, idx.first, false))..add(idx);
}
} else if (last.second == idx.first) {
result.add(idx);
} else {
result..add(Triplet(last.second, idx.first, false))..add(idx);
}
if (isLast && idx.second != t.length) {
result.add(Triplet(idx.second, t.length, false));
}
last = idx;
}
idxs = result;
}
return idxs;
}
class Triplet<A, B, C> {
A first;
B second;
C third;
Triplet(
this.first,
this.second,
this.third,
);
Triplet copyWith({
A? first,
B? second,
C? third,
}) {
return Triplet(
first ?? this.first,
second ?? this.second,
third ?? this.third,
);
}
@override
String toString() => 'Triple first: $first, second: $second, third: $third';
@override
bool operator ==(Object o) {
return o is Triplet && o.first == first && o.second == second && o.third == third;
}
@override
int get hashCode {
return hashList([
first,
second,
third,
]);
}
}
const english = Language(
englishName: 'English',
nativeName: 'English',
);
const french = Language(
englishName: 'French',
nativeName: 'Français',
);
const german = Language(
englishName: 'German',
nativeName: 'Deutsch',
);
const spanish = Language(
englishName: 'Spanish',
nativeName: 'Español',
);
const chinese = Language(
englishName: 'Chinese',
nativeName: '中文',
);
const danish = Language(
englishName: 'Danish',
nativeName: 'Dansk',
);
const hindi = Language(
englishName: 'Hindi',
nativeName: 'हिंदी',
);
const afrikaans = Language(
englishName: 'Afrikaans',
nativeName: 'Afrikaans',
);
const portuguese = Language(
englishName: 'Portuguese',
nativeName: 'Português',
);
const List<Language> languages = [
english,
french,
german,
spanish,
chinese,
danish,
hindi,
afrikaans,
portuguese,
];
class Language {
final String englishName;
final String nativeName;
const Language({
required this.englishName,
required this.nativeName,
});
@override
String toString() => 'Language englishName: $englishName, nativeName: $nativeName';
@override
bool operator ==(Object o) {
if (identical(this, o)) return true;
return o is Language && o.englishName == englishName && o.nativeName == nativeName;
}
@override
int get hashCode => englishName.hashCode ^ nativeName.hashCode;
}
export 'box.dart';
export 'highlight_text.dart';
export 'languages.dart';
\ No newline at end of file
name: example
description: Example for how to use an ImplicitlyAnimatedReorderableList
publish_to: none
version: 1.0.0+1
environment:
sdk: ">=2.12.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
flutter_slidable: ^0.6.0-nullsafety.0
implicitly_animated_reorderable_list_2:
path: ../../implicitly_animated_reorderable_list_2
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
// 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_test/flutter_test.dart';
import 'package:example/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
Fore more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
-->
<base href="/">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="example">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>example</title>
<link rel="manifest" href="manifest.json">
</head>
<body>
<!-- This script installs service_worker.js to provide PWA functionality to
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('flutter-first-frame', function () {
navigator.serviceWorker.register('flutter_service_worker.js');
});
}
</script>
<script src="main.dart.js" type="application/javascript"></script>
</body>
</html>
{
"name": "example",
"short_name": "example",
"start_url": ".",
"display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "A new Flutter project.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
cmake_minimum_required(VERSION 3.15)
project(example LANGUAGES CXX)
set(BINARY_NAME "example")
cmake_policy(SET CMP0063 NEW)
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
# Configure build options.
get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(IS_MULTICONFIG)
set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
CACHE STRING "" FORCE)
else()
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
endif()
set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
# Use Unicode for all projects.
add_definitions(-DUNICODE -D_UNICODE)
# Compilation settings that should be applied to most targets.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_17)
target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
target_compile_options(${TARGET} PRIVATE /EHsc)
target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
endfunction()
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
# Flutter library and tool build rules.
add_subdirectory(${FLUTTER_MANAGED_DIR})
# Application build
add_subdirectory("runner")
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# Support files are copied into place next to the executable, so that it can
# run in place. This is done instead of making a separate bundle (as on Linux)
# so that building and running from within Visual Studio will work.
set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
# Make the "install" step default, as it's required to run.
set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
if(PLUGIN_BUNDLED_LIBRARIES)
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
CONFIGURATIONS Profile;Release
COMPONENT Runtime)
cmake_minimum_required(VERSION 3.15)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146.
set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
# === Flutter Library ===
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"flutter_export.h"
"flutter_windows.h"
"flutter_messenger.h"
"flutter_plugin_registrar.h"
"flutter_texture_registrar.h"
)
list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
add_dependencies(flutter flutter_assemble)
# === Wrapper ===
list(APPEND CPP_WRAPPER_SOURCES_CORE
"core_implementations.cc"
"standard_codec.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
"plugin_registrar.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_APP
"flutter_engine.cc"
"flutter_view_controller.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
# Wrapper sources needed for a plugin.
add_library(flutter_wrapper_plugin STATIC
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
)
apply_standard_settings(flutter_wrapper_plugin)
set_target_properties(flutter_wrapper_plugin PROPERTIES
POSITION_INDEPENDENT_CODE ON)
set_target_properties(flutter_wrapper_plugin PROPERTIES
CXX_VISIBILITY_PRESET hidden)
target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
target_include_directories(flutter_wrapper_plugin PUBLIC
"${WRAPPER_ROOT}/include"
)
add_dependencies(flutter_wrapper_plugin flutter_assemble)
# Wrapper sources needed for the runner.
add_library(flutter_wrapper_app STATIC
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_APP}
)
apply_standard_settings(flutter_wrapper_app)
target_link_libraries(flutter_wrapper_app PUBLIC flutter)
target_include_directories(flutter_wrapper_app PUBLIC
"${WRAPPER_ROOT}/include"
)
add_dependencies(flutter_wrapper_app flutter_assemble)
# === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time,
# since currently there's no way to get a full input/output list from the
# flutter tool.
set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP}
${PHONY_OUTPUT}
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
windows-x64 $<CONFIG>
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP}
)
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
void RegisterPlugins(flutter::PluginRegistry* registry) {
}
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter/plugin_registry.h>
// Registers Flutter plugins.
void RegisterPlugins(flutter::PluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)
cmake_minimum_required(VERSION 3.15)
project(runner LANGUAGES CXX)
add_executable(${BINARY_NAME} WIN32
"flutter_window.cpp"
"main.cpp"
"run_loop.cpp"
"utils.cpp"
"win32_window.cpp"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
"Runner.rc"
"runner.exe.manifest"
)
apply_standard_settings(${BINARY_NAME})
target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
add_dependencies(${BINARY_NAME} flutter_assemble)
// Microsoft Visual C++ generated resource script.
//
#pragma code_page(65001)
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_APP_ICON ICON "resources\\app_icon.ico"
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
#ifdef FLUTTER_BUILD_NUMBER
#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
#else
#define VERSION_AS_NUMBER 1,0,0
#endif
#ifdef FLUTTER_BUILD_NAME
#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
#else
#define VERSION_AS_STRING "1.0.0"
#endif
VS_VERSION_INFO VERSIONINFO
FILEVERSION VERSION_AS_NUMBER
PRODUCTVERSION VERSION_AS_NUMBER
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS__WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904e4"
BEGIN
VALUE "CompanyName", "com.example" "\0"
VALUE "FileDescription", "A new Flutter project." "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0"
VALUE "InternalName", "example" "\0"
VALUE "LegalCopyright", "Copyright (C) 2021 com.example. All rights reserved." "\0"
VALUE "OriginalFilename", "example.exe" "\0"
VALUE "ProductName", "example" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1252
END
END
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED
#include "flutter_window.h"
#include <optional>
#include "flutter/generated_plugin_registrant.h"
FlutterWindow::FlutterWindow(RunLoop* run_loop,
const flutter::DartProject& project)
: run_loop_(run_loop), project_(project) {}
FlutterWindow::~FlutterWindow() {}
bool FlutterWindow::OnCreate() {
if (!Win32Window::OnCreate()) {
return false;
}
RECT frame = GetClientArea();
// The size here must match the window dimensions to avoid unnecessary surface
// creation / destruction in the startup path.
flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
frame.right - frame.left, frame.bottom - frame.top, project_);
// Ensure that basic setup of the controller was successful.
if (!flutter_controller_->engine() || !flutter_controller_->view()) {
return false;
}
RegisterPlugins(flutter_controller_->engine());
run_loop_->RegisterFlutterInstance(flutter_controller_->engine());
SetChildContent(flutter_controller_->view()->GetNativeWindow());
return true;
}
void FlutterWindow::OnDestroy() {
if (flutter_controller_) {
run_loop_->UnregisterFlutterInstance(flutter_controller_->engine());
flutter_controller_ = nullptr;
}
Win32Window::OnDestroy();
}
LRESULT
FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
// Give Flutter, including plugins, an opporutunity to handle window messages.
if (flutter_controller_) {
std::optional<LRESULT> result =
flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
lparam);
if (result) {
return *result;
}
}
switch (message) {
case WM_FONTCHANGE:
flutter_controller_->engine()->ReloadSystemFonts();
break;
}
return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
}
#ifndef RUNNER_FLUTTER_WINDOW_H_
#define RUNNER_FLUTTER_WINDOW_H_
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <memory>
#include "run_loop.h"
#include "win32_window.h"
// A window that does nothing but host a Flutter view.
class FlutterWindow : public Win32Window {
public:
// Creates a new FlutterWindow driven by the |run_loop|, hosting a
// Flutter view running |project|.
explicit FlutterWindow(RunLoop* run_loop,
const flutter::DartProject& project);
virtual ~FlutterWindow();
protected:
// Win32Window:
bool OnCreate() override;
void OnDestroy() override;
LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
LPARAM const lparam) noexcept override;
private:
// The run loop driving events for this window.
RunLoop* run_loop_;
// The project to run.
flutter::DartProject project_;
// The Flutter instance hosted by this window.
std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
};
#endif // RUNNER_FLUTTER_WINDOW_H_
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <windows.h>
#include "flutter_window.h"
#include "run_loop.h"
#include "utils.h"
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
_In_ wchar_t *command_line, _In_ int show_command) {
// Attach to console when present (e.g., 'flutter run') or create a
// new console when running with a debugger.
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
CreateAndAttachConsole();
}
// Initialize COM, so that it is available for use in the library and/or
// plugins.
::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
RunLoop run_loop;
flutter::DartProject project(L"data");
std::vector<std::string> command_line_arguments =
GetCommandLineArguments();
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
FlutterWindow window(&run_loop, project);
Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720);
if (!window.CreateAndShow(L"example", origin, size)) {
return EXIT_FAILURE;
}
window.SetQuitOnClose(true);
run_loop.Run();
::CoUninitialize();
return EXIT_SUCCESS;
}
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by Runner.rc
//
#define IDI_APP_ICON 101
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
#include "run_loop.h"
#include <windows.h>
#include <algorithm>
RunLoop::RunLoop() {}
RunLoop::~RunLoop() {}
void RunLoop::Run() {
bool keep_running = true;
TimePoint next_flutter_event_time = TimePoint::clock::now();
while (keep_running) {
std::chrono::nanoseconds wait_duration =
std::max(std::chrono::nanoseconds(0),
next_flutter_event_time - TimePoint::clock::now());
::MsgWaitForMultipleObjects(
0, nullptr, FALSE, static_cast<DWORD>(wait_duration.count() / 1000),
QS_ALLINPUT);
bool processed_events = false;
MSG message;
// All pending Windows messages must be processed; MsgWaitForMultipleObjects
// won't return again for items left in the queue after PeekMessage.
while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) {
processed_events = true;
if (message.message == WM_QUIT) {
keep_running = false;
break;
}
::TranslateMessage(&message);
::DispatchMessage(&message);
// Allow Flutter to process messages each time a Windows message is
// processed, to prevent starvation.
next_flutter_event_time =
std::min(next_flutter_event_time, ProcessFlutterMessages());
}
// If the PeekMessage loop didn't run, process Flutter messages.
if (!processed_events) {
next_flutter_event_time =
std::min(next_flutter_event_time, ProcessFlutterMessages());
}
}
}
void RunLoop::RegisterFlutterInstance(
flutter::FlutterEngine* flutter_instance) {
flutter_instances_.insert(flutter_instance);
}
void RunLoop::UnregisterFlutterInstance(
flutter::FlutterEngine* flutter_instance) {
flutter_instances_.erase(flutter_instance);
}
RunLoop::TimePoint RunLoop::ProcessFlutterMessages() {
TimePoint next_event_time = TimePoint::max();
for (auto instance : flutter_instances_) {
std::chrono::nanoseconds wait_duration = instance->ProcessMessages();
if (wait_duration != std::chrono::nanoseconds::max()) {
next_event_time =
std::min(next_event_time, TimePoint::clock::now() + wait_duration);
}
}
return next_event_time;
}
#ifndef RUNNER_RUN_LOOP_H_
#define RUNNER_RUN_LOOP_H_
#include <flutter/flutter_engine.h>
#include <chrono>
#include <set>
// A runloop that will service events for Flutter instances as well
// as native messages.
class RunLoop {
public:
RunLoop();
~RunLoop();
// Prevent copying
RunLoop(RunLoop const&) = delete;
RunLoop& operator=(RunLoop const&) = delete;
// Runs the run loop until the application quits.
void Run();
// Registers the given Flutter instance for event servicing.
void RegisterFlutterInstance(
flutter::FlutterEngine* flutter_instance);
// Unregisters the given Flutter instance from event servicing.
void UnregisterFlutterInstance(
flutter::FlutterEngine* flutter_instance);
private:
using TimePoint = std::chrono::steady_clock::time_point;
// Processes all currently pending messages for registered Flutter instances.
TimePoint ProcessFlutterMessages();
std::set<flutter::FlutterEngine*> flutter_instances_;
};
#endif // RUNNER_RUN_LOOP_H_
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
</application>
</compatibility>
</assembly>
#include "utils.h"
#include <flutter_windows.h>
#include <io.h>
#include <stdio.h>
#include <windows.h>
#include <iostream>
void CreateAndAttachConsole() {
if (::AllocConsole()) {
FILE *unused;
if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
_dup2(_fileno(stdout), 1);
}
if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
_dup2(_fileno(stdout), 2);
}
std::ios::sync_with_stdio();
FlutterDesktopResyncOutputStreams();
}
}
std::vector<std::string> GetCommandLineArguments() {
// Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
int argc;
wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
if (argv == nullptr) {
return std::vector<std::string>();
}
std::vector<std::string> command_line_arguments;
// Skip the first argument as it's the binary name.
for (int i = 1; i < argc; i++) {
command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
}
::LocalFree(argv);
return command_line_arguments;
}
std::string Utf8FromUtf16(const wchar_t* utf16_string) {
if (utf16_string == nullptr) {
return std::string();
}
int target_length = ::WideCharToMultiByte(
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
-1, nullptr, 0, nullptr, nullptr);
if (target_length == 0) {
return std::string();
}
std::string utf8_string;
utf8_string.resize(target_length);
int converted_length = ::WideCharToMultiByte(
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
-1, utf8_string.data(),
target_length, nullptr, nullptr);
if (converted_length == 0) {
return std::string();
}
return utf8_string;
}
#ifndef RUNNER_UTILS_H_
#define RUNNER_UTILS_H_
#include <string>
#include <vector>
// Creates a console for the process, and redirects stdout and stderr to
// it for both the runner and the Flutter library.
void CreateAndAttachConsole();
// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
// encoded in UTF-8. Returns an empty std::string on failure.
std::string Utf8FromUtf16(const wchar_t* utf16_string);
// Gets the command line arguments passed in as a std::vector<std::string>,
// encoded in UTF-8. Returns an empty std::vector<std::string> on failure.
std::vector<std::string> GetCommandLineArguments();
#endif // RUNNER_UTILS_H_
#include "win32_window.h"
#include <flutter_windows.h>
#include "resource.h"
namespace {
constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
// The number of Win32Window objects that currently exist.
static int g_active_window_count = 0;
using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
// Scale helper to convert logical scaler values to physical using passed in
// scale factor
int Scale(int source, double scale_factor) {
return static_cast<int>(source * scale_factor);
}
// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
// This API is only needed for PerMonitor V1 awareness mode.
void EnableFullDpiSupportIfAvailable(HWND hwnd) {
HMODULE user32_module = LoadLibraryA("User32.dll");
if (!user32_module) {
return;
}
auto enable_non_client_dpi_scaling =
reinterpret_cast<EnableNonClientDpiScaling*>(
GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
if (enable_non_client_dpi_scaling != nullptr) {
enable_non_client_dpi_scaling(hwnd);
FreeLibrary(user32_module);
}
}
} // namespace
// Manages the Win32Window's window class registration.
class WindowClassRegistrar {
public:
~WindowClassRegistrar() = default;
// Returns the singleton registar instance.
static WindowClassRegistrar* GetInstance() {
if (!instance_) {
instance_ = new WindowClassRegistrar();
}
return instance_;
}
// Returns the name of the window class, registering the class if it hasn't
// previously been registered.
const wchar_t* GetWindowClass();
// Unregisters the window class. Should only be called if there are no
// instances of the window.
void UnregisterWindowClass();
private:
WindowClassRegistrar() = default;
static WindowClassRegistrar* instance_;
bool class_registered_ = false;
};
WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
const wchar_t* WindowClassRegistrar::GetWindowClass() {
if (!class_registered_) {
WNDCLASS window_class{};
window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
window_class.lpszClassName = kWindowClassName;
window_class.style = CS_HREDRAW | CS_VREDRAW;
window_class.cbClsExtra = 0;
window_class.cbWndExtra = 0;
window_class.hInstance = GetModuleHandle(nullptr);
window_class.hIcon =
LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
window_class.hbrBackground = 0;
window_class.lpszMenuName = nullptr;
window_class.lpfnWndProc = Win32Window::WndProc;
RegisterClass(&window_class);
class_registered_ = true;
}
return kWindowClassName;
}
void WindowClassRegistrar::UnregisterWindowClass() {
UnregisterClass(kWindowClassName, nullptr);
class_registered_ = false;
}
Win32Window::Win32Window() {
++g_active_window_count;
}
Win32Window::~Win32Window() {
--g_active_window_count;
Destroy();
}
bool Win32Window::CreateAndShow(const std::wstring& title,
const Point& origin,
const Size& size) {
Destroy();
const wchar_t* window_class =
WindowClassRegistrar::GetInstance()->GetWindowClass();
const POINT target_point = {static_cast<LONG>(origin.x),
static_cast<LONG>(origin.y)};
HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
double scale_factor = dpi / 96.0;
HWND window = CreateWindow(
window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
Scale(size.width, scale_factor), Scale(size.height, scale_factor),
nullptr, nullptr, GetModuleHandle(nullptr), this);
if (!window) {
return false;
}
return OnCreate();
}
// static
LRESULT CALLBACK Win32Window::WndProc(HWND const window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
if (message == WM_NCCREATE) {
auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
SetWindowLongPtr(window, GWLP_USERDATA,
reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
EnableFullDpiSupportIfAvailable(window);
that->window_handle_ = window;
} else if (Win32Window* that = GetThisFromHandle(window)) {
return that->MessageHandler(window, message, wparam, lparam);
}
return DefWindowProc(window, message, wparam, lparam);
}
LRESULT
Win32Window::MessageHandler(HWND hwnd,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
switch (message) {
case WM_DESTROY:
window_handle_ = nullptr;
Destroy();
if (quit_on_close_) {
PostQuitMessage(0);
}
return 0;
case WM_DPICHANGED: {
auto newRectSize = reinterpret_cast<RECT*>(lparam);
LONG newWidth = newRectSize->right - newRectSize->left;
LONG newHeight = newRectSize->bottom - newRectSize->top;
SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
return 0;
}
case WM_SIZE: {
RECT rect = GetClientArea();
if (child_content_ != nullptr) {
// Size and position the child window.
MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
rect.bottom - rect.top, TRUE);
}
return 0;
}
case WM_ACTIVATE:
if (child_content_ != nullptr) {
SetFocus(child_content_);
}
return 0;
}
return DefWindowProc(window_handle_, message, wparam, lparam);
}
void Win32Window::Destroy() {
OnDestroy();
if (window_handle_) {
DestroyWindow(window_handle_);
window_handle_ = nullptr;
}
if (g_active_window_count == 0) {
WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
}
}
Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
return reinterpret_cast<Win32Window*>(
GetWindowLongPtr(window, GWLP_USERDATA));
}
void Win32Window::SetChildContent(HWND content) {
child_content_ = content;
SetParent(content, window_handle_);
RECT frame = GetClientArea();
MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
frame.bottom - frame.top, true);
SetFocus(child_content_);
}
RECT Win32Window::GetClientArea() {
RECT frame;
GetClientRect(window_handle_, &frame);
return frame;
}
HWND Win32Window::GetHandle() {
return window_handle_;
}
void Win32Window::SetQuitOnClose(bool quit_on_close) {
quit_on_close_ = quit_on_close;
}
bool Win32Window::OnCreate() {
// No-op; provided for subclasses.
return true;
}
void Win32Window::OnDestroy() {
// No-op; provided for subclasses.
}
#ifndef RUNNER_WIN32_WINDOW_H_
#define RUNNER_WIN32_WINDOW_H_
#include <windows.h>
#include <functional>
#include <memory>
#include <string>
// A class abstraction for a high DPI-aware Win32 Window. Intended to be
// inherited from by classes that wish to specialize with custom
// rendering and input handling
class Win32Window {
public:
struct Point {
unsigned int x;
unsigned int y;
Point(unsigned int x, unsigned int y) : x(x), y(y) {}
};
struct Size {
unsigned int width;
unsigned int height;
Size(unsigned int width, unsigned int height)
: width(width), height(height) {}
};
Win32Window();
virtual ~Win32Window();
// Creates and shows a win32 window with |title| and position and size using
// |origin| and |size|. New windows are created on the default monitor. Window
// sizes are specified to the OS in physical pixels, hence to ensure a
// consistent size to will treat the width height passed in to this function
// as logical pixels and scale to appropriate for the default monitor. Returns
// true if the window was created successfully.
bool CreateAndShow(const std::wstring& title,
const Point& origin,
const Size& size);
// Release OS resources associated with window.
void Destroy();
// Inserts |content| into the window tree.
void SetChildContent(HWND content);
// Returns the backing Window handle to enable clients to set icon and other
// window properties. Returns nullptr if the window has been destroyed.
HWND GetHandle();
// If true, closing this window will quit the application.
void SetQuitOnClose(bool quit_on_close);
// Return a RECT representing the bounds of the current client area.
RECT GetClientArea();
protected:
// Processes and route salient window messages for mouse handling,
// size change and DPI. Delegates handling of these to member overloads that
// inheriting classes can handle.
virtual LRESULT MessageHandler(HWND window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept;
// Called when CreateAndShow is called, allowing subclass window-related
// setup. Subclasses should return false if setup fails.
virtual bool OnCreate();
// Called when Destroy is called.
virtual void OnDestroy();
private:
friend class WindowClassRegistrar;
// OS callback called by message pump. Handles the WM_NCCREATE message which
// is passed when the non-client area is being created and enables automatic
// non-client DPI scaling so that the non-client area automatically
// responsponds to changes in DPI. All other messages are handled by
// MessageHandler.
static LRESULT CALLBACK WndProc(HWND const window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept;
// Retrieves a class instance pointer for |window|
static Win32Window* GetThisFromHandle(HWND const window) noexcept;
bool quit_on_close_ = false;
// window handle for top level window.
HWND window_handle_ = nullptr;
// window handle for hosted content.
HWND child_content_ = nullptr;
};
#endif // RUNNER_WIN32_WINDOW_H_
library implicitly_animated_reorderable_list_2;
export 'src/diff/myers_diff.dart';
export 'src/handle.dart';
export 'src/implicitly_animated_list.dart';
export 'src/implicitly_animated_reorderable_list_2.dart';
export 'src/reorderable.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
const Duration _kDuration = Duration(milliseconds: 300);
class CustomSliverAnimatedList extends StatefulWidget {
/// Creates a sliver that animates items when they are inserted or removed.
const CustomSliverAnimatedList({
Key? key,
required this.itemBuilder,
this.initialItemCount = 0,
}) : assert(initialItemCount >= 0),
super(key: key);
/// Called, as needed, to build list item widgets.
///
/// List items are only built when they're scrolled into view.
///
/// The [AnimatedListItemBuilder] index parameter indicates the item's
/// position in the list. The value of the index parameter will be between 0
/// and [initialItemCount] plus the total number of items that have been
/// inserted with [CustomSliverAnimatedListState.insertItem] and less the total
/// number of items that have been removed with
/// [CustomSliverAnimatedListState.removeItem].
///
/// Implementations of this callback should assume that
/// [CustomSliverAnimatedListState.removeItem] removes an item immediately.
final AnimatedListItemBuilder itemBuilder;
/// {@macro flutter.widgets.animatedList.initialItemCount}
final int initialItemCount;
@override
CustomSliverAnimatedListState createState() =>
CustomSliverAnimatedListState();
/// The state from the closest instance of this class that encloses the given
/// context.
///
/// This method is typically used by [CustomSliverAnimatedList] item widgets that
/// insert or remove items in response to user input.
///
/// If no [CustomSliverAnimatedList] surrounds the context given, then this function
/// will assert in debug mode and throw an exception in release mode.
///
/// See also:
///
/// * [maybeOf], a similar function that will return null if no
/// [SliverAnimatedList] ancestor is found.
static CustomSliverAnimatedListState of(BuildContext context) {
final CustomSliverAnimatedListState? result =
context.findAncestorStateOfType<CustomSliverAnimatedListState>();
assert(() {
if (result == null) {
throw FlutterError(
'SliverAnimatedList.of() called with a context that does not contain a SliverAnimatedList.\n'
'No SliverAnimatedListState ancestor could be found starting from the '
'context that was passed to SliverAnimatedListState.of(). This can '
'happen when the context provided is from the same StatefulWidget that '
'built the AnimatedList. Please see the SliverAnimatedList documentation '
'for examples of how to refer to an AnimatedListState object: '
'https://api.flutter.dev/flutter/widgets/SliverAnimatedListState-class.html\n'
'The context used was:\n'
' $context',
);
}
return true;
}());
return result!;
}
/// The state from the closest instance of this class that encloses the given
/// context.
///
/// This method is typically used by [CustomSliverAnimatedList] item widgets that
/// insert or remove items in response to user input.
///
/// If no [CustomSliverAnimatedList] surrounds the context given, then this function
/// will return null.
///
/// See also:
///
/// * [of], a similar function that will throw if no [CustomSliverAnimatedList]
/// ancestor is found.
static CustomSliverAnimatedListState? maybeOf(BuildContext context) {
return context.findAncestorStateOfType<CustomSliverAnimatedListState>();
}
}
class CustomSliverAnimatedListState extends State<CustomSliverAnimatedList>
with TickerProviderStateMixin {
final List<_ActiveItem> _incomingItems = <_ActiveItem>[];
final List<_ActiveItem> _outgoingItems = <_ActiveItem>[];
int _itemsCount = 0;
@override
void initState() {
super.initState();
_itemsCount = widget.initialItemCount;
}
@override
void dispose() {
for (final _ActiveItem item in _incomingItems.followedBy(_outgoingItems)) {
item.controller!.dispose();
}
super.dispose();
}
_ActiveItem? _removeActiveItemAt(List<_ActiveItem> items, int itemIndex) {
final int i = binarySearch(items, _ActiveItem.index(itemIndex));
return i == -1 ? null : items.removeAt(i);
}
_ActiveItem? _activeItemAt(List<_ActiveItem> items, int itemIndex) {
final int i = binarySearch(items, _ActiveItem.index(itemIndex));
return i == -1 ? null : items[i];
}
// The insertItem() and removeItem() index parameters are defined as if the
// removeItem() operation removed the corresponding list entry immediately.
// The entry is only actually removed from the ListView when the remove animation
// finishes. The entry is added to _outgoingItems when removeItem is called
// and removed from _outgoingItems when the remove animation finishes.
int _indexToItemIndex(int index) {
int itemIndex = index;
for (final _ActiveItem item in _outgoingItems) {
if (item.itemIndex <= itemIndex) {
itemIndex += 1;
} else {
break;
}
}
return itemIndex;
}
int _itemIndexToIndex(int itemIndex) {
int index = itemIndex;
for (final _ActiveItem item in _outgoingItems) {
assert(item.itemIndex != itemIndex);
if (item.itemIndex < itemIndex) {
index -= 1;
} else {
break;
}
}
return index;
}
SliverChildDelegate _createDelegate() {
return SliverChildBuilderDelegate(_itemBuilder, childCount: _itemsCount);
}
/// Insert an item at [index] and start an animation that will be passed to
/// [CustomSliverAnimatedList.itemBuilder] when the item is visible.
///
/// This method's semantics are the same as Dart's [List.insert] method:
/// it increases the length of the list by one and shifts all items at or
/// after [index] towards the end of the list.
void insertItem(int index, {Duration duration = _kDuration}) {
assert(index >= 0);
final int itemIndex = _indexToItemIndex(index);
if (itemIndex < 0 || itemIndex > _itemsCount) {
return;
}
// Increment the incoming and outgoing item indices to account
// for the insertion.
for (final _ActiveItem item in _incomingItems) {
if (item.itemIndex >= itemIndex) item.itemIndex += 1;
}
for (final _ActiveItem item in _outgoingItems) {
if (item.itemIndex >= itemIndex) item.itemIndex += 1;
}
final AnimationController controller = AnimationController(
duration: duration,
vsync: this,
);
final _ActiveItem incomingItem = _ActiveItem.incoming(
controller,
itemIndex,
);
setState(() {
_incomingItems
..add(incomingItem)
..sort();
_itemsCount += 1;
});
controller.forward().then<void>((_) {
_removeActiveItemAt(_incomingItems, incomingItem.itemIndex)!
.controller!
.dispose();
});
}
/// Remove the item at [index] and start an animation that will be passed
/// to [builder] when the item is visible.
///
/// Items are removed immediately. After an item has been removed, its index
/// will no longer be passed to the [CustomSliverAnimatedList.itemBuilder]. However
/// the item will still appear in the list for [duration] and during that time
/// [builder] must construct its widget as needed.
///
/// This method's semantics are the same as Dart's [List.remove] method:
/// it decreases the length of the list by one and shifts all items at or
/// before [index] towards the beginning of the list.
void removeItem(int index, AnimatedListRemovedItemBuilder builder,
{Duration duration = _kDuration}) {
assert(index >= 0);
final int itemIndex = _indexToItemIndex(index);
if (itemIndex < 0 || itemIndex >= _itemsCount) {
return;
}
assert(_activeItemAt(_outgoingItems, itemIndex) == null);
final _ActiveItem? incomingItem =
_removeActiveItemAt(_incomingItems, itemIndex);
final AnimationController controller = incomingItem?.controller ??
AnimationController(duration: duration, value: 1.0, vsync: this);
final _ActiveItem outgoingItem =
_ActiveItem.outgoing(controller, itemIndex, builder);
setState(() {
_outgoingItems
..add(outgoingItem)
..sort();
});
controller.reverse().then<void>((void value) {
_removeActiveItemAt(_outgoingItems, outgoingItem.itemIndex)!
.controller!
.dispose();
// Decrement the incoming and outgoing item indices to account
// for the removal.
for (final _ActiveItem item in _incomingItems) {
if (item.itemIndex > outgoingItem.itemIndex) item.itemIndex -= 1;
}
for (final _ActiveItem item in _outgoingItems) {
if (item.itemIndex > outgoingItem.itemIndex) item.itemIndex -= 1;
}
setState(() => _itemsCount -= 1);
});
}
Widget _itemBuilder(BuildContext context, int itemIndex) {
final _ActiveItem? outgoingItem = _activeItemAt(_outgoingItems, itemIndex);
if (outgoingItem != null) {
return outgoingItem.removedItemBuilder!(
context,
outgoingItem.controller!.view,
);
}
final _ActiveItem? incomingItem = _activeItemAt(_incomingItems, itemIndex);
final Animation<double> animation =
incomingItem?.controller?.view ?? kAlwaysCompleteAnimation;
return widget.itemBuilder(
context,
_itemIndexToIndex(itemIndex),
animation,
);
}
@override
Widget build(BuildContext context) {
return SliverList(
delegate: _createDelegate(),
);
}
}
class _ActiveItem implements Comparable<_ActiveItem> {
_ActiveItem.incoming(this.controller, this.itemIndex)
: removedItemBuilder = null;
_ActiveItem.outgoing(
this.controller, this.itemIndex, this.removedItemBuilder);
_ActiveItem.index(this.itemIndex)
: controller = null,
removedItemBuilder = null;
final AnimationController? controller;
final AnimatedListRemovedItemBuilder? removedItemBuilder;
int itemIndex;
@override
int compareTo(_ActiveItem other) => itemIndex - other.itemIndex;
}
export 'diff_callback.dart';
export 'diff_delegate.dart';
export 'diff_model.dart';
export 'myers_diff.dart';
export 'path_node.dart';
typedef ItemDiffUtil<E> = bool Function(E oldItem, E newItem);
/// A Callback class used by DiffUtil while calculating the diff between two lists.
mixin DiffCallback<E> {
/// The list containing the new data.
List<E>? get newList;
/// The list containing the old data.
List<E>? get oldList;
/// Returns the size of the old list.
int get oldListSize => oldList!.length;
/// Returns the size of the list.
int get newListSize => newList!.length;
/// Called by the DiffUtil to decide whether two object represent the same Item.
/// For example, if your items have unique ids, this method should check their id equality.
bool areItemsTheSame(E oldItem, E newItem);
/// Called by the DiffUtil to decide whether two object represent the same Item.
/// For example, if your items have unique ids, this method should check their id equality.
bool areContentsTheSame(E oldItem, E newItem);
dynamic getChangePayload(E oldItem, E newItem) => null;
/// Called when an item was inserted into the list.
void onInserted(int index, E item);
/// Called when an item was removed from the list.
void onRemoved(int index);
/// Called when an item in the list changed but not its position in the list.
void onChanged(int startIndex, List<E> itemsChanged);
}
import 'diff.dart';
class DiffDelegate<E> {
final DiffCallback<E> _callback;
const DiffDelegate(this._callback);
void applyDiffs(List<Diff> diffs) {
for (final diff in diffs) {
if (diff is Insertion) {
_applyInsertion(diff as Insertion<E>);
} else if (diff is Deletion) {
_applyDeletion(diff);
} else if (diff is Modification) {
_applyModification(diff as Modification<E>);
}
}
}
void _applyModification(Modification<E> diff) {
final diffLength = diff.items.length;
if (diff.size != diffLength) {
if (diff.size > diffLength) {
var sizeDifference = diff.size - diffLength;
while (sizeDifference > 0) {
_callback.onRemoved(diff.index + sizeDifference);
sizeDifference--;
}
} else {
var insertIndex = diff.size;
while (insertIndex < diffLength) {
_callback.onInserted(
insertIndex + diff.index,
diff.items[insertIndex],
);
insertIndex++;
}
}
}
final changedItems = diff.items.take(diff.size).toList();
_callback.onChanged(diff.index, changedItems);
}
void _applyDeletion(Deletion diff) {
for (var i = diff.size - 1; i >= 0; i--) {
_callback.onRemoved(diff.index + i);
}
}
void _applyInsertion(Insertion<E> diff) {
for (var i = 0; i < diff.size; i++) {
_callback.onInserted(diff.index + i, diff.items[i]);
}
}
}
abstract class Diff implements Comparable<Diff> {
final int index;
final int size;
const Diff(
this.index,
this.size,
);
@override
String toString() => '${runtimeType.toString()}(index: $index, size: $size)';
@override
int compareTo(Diff other) => index - other.index;
}
class Insertion<E> extends Diff {
final List<E> items;
const Insertion(
int index,
int size,
this.items,
) : super(index, size);
}
class Deletion extends Diff {
const Deletion(
int index,
int size,
) : super(index, size);
}
class Modification<E> extends Diff {
final List<E> items;
const Modification(
int index,
int size,
this.items,
) : super(index, size);
@override
String toString() {
return '${runtimeType.toString()}(index: $index, size: $size, items: $items)';
}
}
import 'package:flutter/foundation.dart';
import '../src.dart';
// This implementation of the MyersDiff algorithm was originally written by David Bota
// over here https://gitlab.com/otsoaUnLoco/animated-stream-list.
class _DiffArguments<E> {
final List<E> oldList;
final List<E> newList;
_DiffArguments(this.oldList, this.newList);
}
class MyersDiff<E> {
static ItemDiffUtil? eq;
static ItemDiffUtil? cq;
static int isolateThreshold = 1500;
static Future<List<Diff>> withCallback<E>(
DiffCallback<E?> cb, {
bool? spawnIsolate,
}) {
return diff<E?>(
cb.newList!,
cb.oldList!,
areItemsTheSame: cb.areItemsTheSame,
spawnIsolate: spawnIsolate,
);
}
static Future<List<Diff>> diff<E>(
List<E> newList,
List<E> oldList, {
ItemDiffUtil<E>? areItemsTheSame,
bool? spawnIsolate,
}) {
eq = (a, b) => areItemsTheSame?.call(a, b) ?? a == b;
cq = (a, b) => false;
final args = _DiffArguments<E>(oldList, newList);
// We can significantly improve the performance by not spawning a new
// isolate for shorter lists.
spawnIsolate ??= (newList.length * oldList.length) > isolateThreshold;
if (spawnIsolate) {
return compute(_myersDiff, args);
}
return Future.value(_myersDiff(args));
}
}
List<Diff> _myersDiff<E>(_DiffArguments<E> args) {
final List<E> oldList = args.oldList;
final List<E> newList = args.newList;
if (oldList == newList) return [];
final oldSize = oldList.length;
final newSize = newList.length;
if (oldSize == 0) {
return [Insertion(0, newSize, newList)];
}
if (newSize == 0) {
return [Deletion(0, oldSize)];
}
final equals = MyersDiff.eq ?? (a, b) => a == b;
final path = _buildPath(oldList, newList, equals)!;
final diffs = _buildPatch(path, oldList, newList)..sort();
return diffs.reversed.toList(growable: true);
}
PathNode? _buildPath<E>(
List<E> oldList, List<E> newList, ItemDiffUtil<E> equals) {
final oldSize = oldList.length;
final newSize = newList.length;
final int max = oldSize + newSize + 1;
final int size = (2 * max) + 1;
final int middle = size ~/ 2;
final List<PathNode?> diagonal = List.filled(size, null);
diagonal[middle + 1] = Snake(0, -1, null);
for (int d = 0; d < max; d++) {
for (int k = -d; k <= d; k += 2) {
final int kmiddle = middle + k;
final int kplus = kmiddle + 1;
final int kminus = kmiddle - 1;
PathNode? prev;
int i;
if ((k == -d) ||
(k != d &&
diagonal[kminus]!.originIndex < diagonal[kplus]!.originIndex)) {
i = diagonal[kplus]!.originIndex;
prev = diagonal[kplus];
} else {
i = diagonal[kminus]!.originIndex + 1;
prev = diagonal[kminus];
}
diagonal[kminus] = null;
int j = i - k;
PathNode node = DiffNode(i, j, prev);
while (i < oldSize && j < newSize && equals(oldList[i], newList[j])) {
i++;
j++;
}
if (i > node.originIndex) {
node = Snake(i, j, node);
}
diagonal[kmiddle] = node;
if (i >= oldSize && j >= newSize) {
return diagonal[kmiddle];
}
}
diagonal[middle + d - 1] = null;
}
throw Exception();
}
List<Diff> _buildPatch<E>(PathNode path, List<E> oldList, List<E> newList) {
final List<Diff> diffs = [];
if (path.isSnake) {
// ignore: parameter_assignments
path = path.previousNode!;
}
while (path.previousNode != null && path.previousNode!.revisedIndex >= 0) {
assert(!path.isSnake);
final i = path.originIndex;
final j = path.revisedIndex;
// ignore: parameter_assignments
path = path.previousNode!;
final iAnchor = path.originIndex;
final jAnchor = path.revisedIndex;
final List<E> original = oldList.sublist(iAnchor, i);
final List<E> revised = newList.sublist(jAnchor, j);
if (original.isEmpty && revised.isNotEmpty) {
diffs.add(Insertion(iAnchor, revised.length, revised));
} else if (original.isNotEmpty && revised.isEmpty) {
diffs.add(Deletion(iAnchor, original.length));
} else {
diffs.add(Modification(iAnchor, original.length, revised));
}
if (path.isSnake) {
// ignore: parameter_assignments
path = path.previousNode!;
}
}
return diffs;
}
abstract class PathNode {
final int originIndex;
final int revisedIndex;
final PathNode? previousNode;
PathNode(
this.originIndex,
this.revisedIndex,
this.previousNode,
);
bool get isSnake;
bool get isBootStrap => originIndex < 0 || revisedIndex < 0;
PathNode? previousSnake() {
if (isBootStrap) return null;
if (!isSnake && previousNode != null) return previousNode!.previousSnake();
return this;
}
@override
String toString() {
final buffer = StringBuffer()..write('[');
PathNode? node = this;
while (node != null) {
buffer
..write('(')
..write('${node.originIndex.toString()}')
..write(',')
..write('${node.revisedIndex.toString()}')
..write(')');
node = node.previousNode;
}
buffer.write(']');
return buffer.toString();
}
}
class Snake extends PathNode {
Snake(
int originIndex,
int revisedIndex,
PathNode? previousNode,
) : super(
originIndex,
revisedIndex,
previousNode,
);
@override
bool get isSnake => true;
}
class DiffNode extends PathNode {
DiffNode(
int originIndex,
int revisedIndex,
PathNode? previousNode,
) : super(
originIndex,
revisedIndex,
previousNode?.previousSnake(),
);
@override
bool get isSnake => false;
}
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'src.dart';
/// A `Widget` that is used to initiate a drag/reorder of a [Reorderable] inside an
/// [ImplicitlyAnimatedReorderableList].
///
/// A `Handle` must have a [Reorderable] and an [ImplicitlyAnimatedReorderableList]
/// as its ancestor.
class Handle extends StatefulWidget {
/// The child of this Handle that can initiate a reorder.
///
/// This might for instance be an [Icon] or a [ListTile].
final Widget child;
/// The delay between when a pointer touched the [child] and
/// when the drag is initiated.
///
/// If the Handle wraps the whole item, the delay should be greater
/// than the default `Duration.zero` as otherwise the list might become unscrollable.
///
/// When the [ImplicitlyAnimatedReorderableList] was scrolled in the mean time,
/// the reorder will be canceled.
/// If the [ImplicitlyAnimatedReorderableList] uses a `NeverScrollableScrollPhysics`
/// the Handle will instead use a parent `Scrollable` if there is one.
final Duration delay;
/// Whether to vibrate when a drag has been initiated.
final bool vibrate;
/// Whether the handle should capture the pointer event of the drag.
///
/// When this is set to `true`, the `Hanlde` is not allowed to change
/// the parent between normal and dragged state.
final bool capturePointer;
final bool enabled;
/// Creates a widget that can initiate a drag/reorder of an item inside an
/// [ImplicitlyAnimatedReorderableList].
///
/// A Handle must have a [Reorderable] and an [ImplicitlyAnimatedReorderableList]
/// as its ancestor.
const Handle({
Key? key,
required this.child,
this.delay = Duration.zero,
this.capturePointer = true,
this.vibrate = true,
this.enabled = true,
}) : super(key: key);
@override
_HandleState createState() => _HandleState();
}
class _HandleState extends State<Handle> {
ScrollableState? _parent;
// A custom handler used to cancel the pending onDragStart callbacks.
Handler? _handler;
// The parent Reorderable item.
ReorderableState? _reorderable;
// The parent list.
ImplicitlyAnimatedReorderableListState? _list;
// Whether the ImplicitlyAnimatedReorderableList has a
// scrollDirection of Axis.vertical.
bool get _isVertical => _list?.isVertical ?? true;
Offset? _pointer;
late double _downOffset;
double? _startOffset;
double? _currentOffset;
double get _delta => (_currentOffset ?? 0) - (_startOffset ?? 0);
// Use flags from the list as this State object is being
// recreated between dragged and normal state.
bool get _inDrag => _list!.inDrag;
bool get _inReorder => _list!.inReorder;
// The pixel offset of a possible parent Scrollable
// used to capture it.
double _parentPixels = 0.0;
void _onDragStarted() {
// If the list is already in drag we dont want to
// initiate a new reorder.
if (_inReorder) return;
final moveDelta = (_downOffset - _currentOffset!).abs();
if (moveDelta > 10.0) {
return;
}
_parentPixels = _parent?.position.pixels ?? 0.0;
_captureParentList();
_startOffset = _currentOffset;
_list?.onDragStarted(_reorderable?.key);
_reorderable!.rebuild();
_vibrate();
}
void _onDragUpdated(Offset pointer) {
_list?.onDragUpdated(_delta);
_captureParentList();
}
void _onDragEnded() {
_handler?.cancel();
_list?.onDragEnded();
_captureParentList();
}
void _vibrate() {
if (widget.vibrate) HapticFeedback.mediumImpact();
}
void _captureParentList() {
// Listener does not capture the drag of this Handle
// however we also cannot use GestureDetector to capture
// the drag on the Handle, as this might make the whole
// list unscrollable (e.g. when the Handle wraps a whole ListTile).
//
// This seems to be the only working solution to this problem.
_parent?.position.jumpTo(_parentPixels);
}
@override
Widget build(BuildContext context) {
if (!widget.enabled) return widget.child;
_list = ImplicitlyAnimatedReorderableList.of(context);
assert(_list != null,
'No ancestor ImplicitlyAnimatedReorderableList was found in the hierarchy!');
_reorderable = Reorderable.of(context);
assert(_reorderable != null,
'No ancestor Reorderable was found in the hierarchy!');
_parent = Scrollable.of(_list!.context);
// _parent = Scrollable.maybeOf(_list!.context);
// Sometimes the cancel callbacks of the GestureDetector
// are erroneously invoked. Use a plain Listener instead
// for now.
return Listener(
behavior: HitTestBehavior.translucent,
onPointerDown: (event) => _onDown(event.localPosition),
onPointerMove: (event) => _onUpdate(event.localPosition),
onPointerUp: (_) => _onUp(),
onPointerCancel: (_) => _onUp(),
child: widget.child,
);
}
void _onDown(Offset pointer) {
_pointer = pointer;
_currentOffset = _offset(_pointer);
_downOffset = _offset(_pointer);
// Ensure the list is not already in a reordering
// state when initiating a new reorder operation.
if (!_inDrag) {
_onUp();
_handler = postDuration(
widget.delay,
_onDragStarted,
);
}
}
void _onUpdate(Offset pointer) {
_pointer = pointer;
_currentOffset = _offset(_pointer);
if (_inDrag && _inReorder) {
_onDragUpdated(pointer);
}
}
void _onUp() {
_handler?.cancel();
if (_inDrag) _onDragEnded();
}
double _offset(Offset? offset) => _isVertical ? offset!.dy : offset!.dx;
}
import 'package:flutter/material.dart';
import 'package:implicitly_animated_reorderable_list_2/src/custom_sliver_animated_list.dart';
import 'src.dart';
/// A Flutter ListView that implicitly animates between the changes of two lists.
class ImplicitlyAnimatedList<E extends Object> extends StatelessWidget {
/// The current data that this [ImplicitlyAnimatedList] should represent.
final List<E> items;
/// Called, as needed, to build list item widgets.
///
/// List items are only built when they're scrolled into view.
final ImplicitlyAnimatedItemBuilder<Widget, E> itemBuilder;
/// An optional builder when an item was removed from the list.
///
/// If not specified, the [ImplicitlyAnimatedList] uses the [itemBuilder] with
/// the animation reversed.
final RemovedItemBuilder<Widget, E>? removeItemBuilder;
/// An optional builder when an item in the list was changed but not its position.
///
/// The [UpdatedItemBuilder] animation will run from 1 to 0 and back to 1 again, while
/// the item parameter will be the old item in the first half of the animation and the new item
/// in the latter half of the animation. This allows you for example to fade between the old and
/// the new item.
///
/// If not specified, changes will appear instantaneously.
final UpdatedItemBuilder<Widget, E>? updateItemBuilder;
/// Called by the DiffUtil to decide whether two object represent the same Item.
/// For example, if your items have unique ids, this method should check their id equality.
final ItemDiffUtil<E> areItemsTheSame;
/// The duration of the animation when an item was inserted into the list.
final Duration insertDuration;
/// The duration of the animation when an item was removed from the list.
final Duration removeDuration;
/// The duration of the animation when an item changed in the list.
final Duration updateDuration;
/// Whether to spawn a new isolate on which to calculate the diff on.
///
/// Usually you wont have to specify this value as the MyersDiff implementation will
/// use its own metrics to decide, whether a new isolate has to be spawned or not for
/// optimal performance.
final bool? spawnIsolate;
/// The axis along which the scroll view scrolls.
///
/// Defaults to [Axis.vertical].
final Axis scrollDirection;
/// Whether the scroll view scrolls in the reading direction.
///
/// For example, if the reading direction is left-to-right and
/// [scrollDirection] is [Axis.horizontal], then the scroll view scrolls from
/// left to right when [reverse] is false and from right to left when
/// [reverse] is true.
///
/// Similarly, if [scrollDirection] is [Axis.vertical], then the scroll view
/// scrolls from top to bottom when [reverse] is false and from bottom to top
/// when [reverse] is true.
///
/// Defaults to false.
final bool reverse;
/// An object that can be used to control the position to which this scroll
/// view is scrolled.
///
/// Must be null if [primary] is true.
///
/// A [ScrollController] serves several purposes. It can be used to control
/// the initial scroll position (see [ScrollController.initialScrollOffset]).
/// It can be used to control whether the scroll view should automatically
/// save and restore its scroll position in the [PageStorage] (see
/// [ScrollController.keepScrollOffset]). It can be used to read the current
/// scroll position (see [ScrollController.offset]), or change it (see
/// [ScrollController.animateTo]).
final ScrollController? controller;
/// Whether this is the primary scroll view associated with the parent
/// [PrimaryScrollController].
///
/// On iOS, this identifies the scroll view that will scroll to top in
/// response to a tap in the status bar.
///
/// Defaults to true when [scrollDirection] is [Axis.vertical] and
/// [controller] is null.
final bool? primary;
/// How the scroll view should respond to user input.
///
/// For example, determines how the scroll view continues to animate after the
/// user stops dragging the scroll view.
///
/// Defaults to matching platform conventions.
final ScrollPhysics? physics;
/// Whether the extent of the scroll view in the [scrollDirection] should be
/// determined by the contents being viewed.
///
/// If the scroll view does not shrink wrap, then the scroll view will expand
/// to the maximum allowed size in the [scrollDirection]. If the scroll view
/// has unbounded constraints in the [scrollDirection], then [shrinkWrap] must
/// be true.
///
/// Shrink wrapping the content of the scroll view is significantly more
/// expensive than expanding to the maximum allowed size because the content
/// can expand and contract during scrolling, which means the size of the
/// scroll view needs to be recomputed whenever the scroll position changes.
///
/// Defaults to false.
final bool shrinkWrap;
/// The amount of space by which to inset the children.
final EdgeInsetsGeometry? padding;
/// Creates a Flutter ListView that implicitly animates between the changes
/// of two lists.
const ImplicitlyAnimatedList({
Key? key,
required this.items,
required this.itemBuilder,
required this.areItemsTheSame,
this.removeItemBuilder,
this.updateItemBuilder,
this.insertDuration = const Duration(milliseconds: 500),
this.removeDuration = const Duration(milliseconds: 500),
this.updateDuration = const Duration(milliseconds: 500),
this.spawnIsolate,
this.scrollDirection = Axis.vertical,
this.reverse = false,
this.controller,
this.primary,
this.physics,
this.shrinkWrap = false,
this.padding,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return CustomScrollView(
scrollDirection: scrollDirection,
reverse: reverse,
controller: controller,
primary: primary,
physics: physics,
shrinkWrap: shrinkWrap,
slivers: <Widget>[
SliverPadding(
padding: padding ?? const EdgeInsets.all(0),
sliver: SliverImplicitlyAnimatedList<E>(
items: items,
itemBuilder: itemBuilder,
areItemsTheSame: areItemsTheSame,
updateItemBuilder: updateItemBuilder,
removeItemBuilder: removeItemBuilder,
insertDuration: insertDuration,
removeDuration: removeDuration,
updateDuration: updateDuration,
spawnIsolate: spawnIsolate,
),
),
],
);
}
}
/// A Flutter Sliver that implicitly animates between the changes of two lists.
class SliverImplicitlyAnimatedList<E extends Object>
extends ImplicitlyAnimatedListBase<Widget, E> {
/// Creates a Flutter Sliver that implicitly animates between the changes of two lists.
///
/// {@template implicitly_animated_reorderable_list_2.constructor}
/// The [items] parameter represents the current items that should be displayed in
/// the list.
///
/// The [itemBuilder] callback is used to build each child as needed. The parent must
/// be a [Reorderable] widget.
///
/// The [areItemsTheSame] callback is called by the DiffUtil to decide whether two objects
/// represent the same item. For example, if your items have unique ids, this method should
/// check their id equality.
///
/// The [onReorderFinished] callback is called in response to when the dragged item has
/// been released and animated to its final destination. Here you should update
/// the underlying data in your model/bloc/database etc.
///
/// The [spawnIsolate] flag indicates whether to spawn a new isolate on which to
/// calculate the diff between the lists. Usually you wont have to specify this
/// value as the MyersDiff implementation will use its own metrics to decide, whether
/// a new isolate has to be spawned or not for optimal performance.
/// {@endtemplate}
const SliverImplicitlyAnimatedList({
Key? key,
required List<E> items,
required ImplicitlyAnimatedItemBuilder<Widget, E> itemBuilder,
required ItemDiffUtil<E> areItemsTheSame,
RemovedItemBuilder<Widget, E>? removeItemBuilder,
UpdatedItemBuilder<Widget, E>? updateItemBuilder,
Duration insertDuration = const Duration(milliseconds: 500),
Duration removeDuration = const Duration(milliseconds: 500),
Duration updateDuration = const Duration(milliseconds: 500),
bool? spawnIsolate,
}) : super(
key: key,
items: items,
itemBuilder: itemBuilder,
areItemsTheSame: areItemsTheSame,
removeItemBuilder: removeItemBuilder,
updateItemBuilder: updateItemBuilder,
insertDuration: insertDuration,
removeDuration: removeDuration,
updateDuration: updateDuration,
spawnIsolate: spawnIsolate,
);
@override
_SliverImplicitlyAnimatedListState<E> createState() =>
_SliverImplicitlyAnimatedListState<E>();
}
class _SliverImplicitlyAnimatedListState<E extends Object>
extends ImplicitlyAnimatedListBaseState<Widget,
SliverImplicitlyAnimatedList<E>, E> {
@override
Widget build(BuildContext context) {
return CustomSliverAnimatedList(
key: animatedListKey,
initialItemCount: newList.length,
itemBuilder: (context, index, animation) {
final E? item = data.getOrNull(index) ??
newList.getOrNull(index) ??
oldList.getOrNull(index);
final didChange = changes[item] != null;
if (item == null) {
return Container();
} else if (updateItemBuilder != null && didChange) {
return buildUpdatedItemWidget(item);
} else {
return itemBuilder(context, animation, item, index);
}
},
);
}
}
import 'dart:async';
import 'package:async/async.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:implicitly_animated_reorderable_list_2/src/custom_sliver_animated_list.dart';
import 'src.dart';
typedef ImplicitlyAnimatedItemBuilder<W extends Widget, E> = W Function(
BuildContext context, Animation<double> animation, E item, int i);
typedef RemovedItemBuilder<W extends Widget, E> = W Function(
BuildContext context, Animation<double> animation, E item);
typedef UpdatedItemBuilder<W extends Widget, E> = W Function(
BuildContext context, Animation<double> animation, E item);
abstract class ImplicitlyAnimatedListBase<W extends Widget, E extends Object>
extends StatefulWidget {
/// Called, as needed, to build list item widgets.
///
/// List items are only built when they're scrolled into view.
final ImplicitlyAnimatedItemBuilder<W, E> itemBuilder;
/// An optional builder when an item was removed from the list.
///
/// If not specified, the [ImplicitlyAnimatedList] uses the [itemBuilder] with
/// the animation reversed.
final RemovedItemBuilder<W, E>? removeItemBuilder;
/// An optional builder when an item in the list was changed but not its position.
///
/// The [UpdatedItemBuilder] animation will run from 1 to 0 and back to 1 again, while
/// the item parameter will be the old item in the first half of the animation and the new item
/// in the latter half of the animation. This allows you for example to fade between the old and
/// the new item.
///
/// If not specified, changes will appear instantaneously.
final UpdatedItemBuilder<W, E>? updateItemBuilder;
/// The data that this [ImplicitlyAnimatedList] should represent.
final List<E> items;
/// Called by the DiffUtil to decide whether two object represent the same Item.
/// For example, if your items have unique ids, this method should check their id equality.
final ItemDiffUtil<E> areItemsTheSame;
/// The duration of the animation when an item was inserted into the list.
final Duration insertDuration;
/// The duration of the animation when an item was removed from the list.
final Duration removeDuration;
/// The duration of the animation when an item changed in the list.
final Duration updateDuration;
/// Whether to spawn a new isolate on which to calculate the diff on.
///
/// Usually you wont have to specify this value as the MyersDiff implementation will
/// use its own metrics to decide, whether a new isolate has to be spawned or not for
/// optimal performance.
final bool? spawnIsolate;
const ImplicitlyAnimatedListBase({
Key? key,
required this.items,
required this.areItemsTheSame,
required this.itemBuilder,
required this.removeItemBuilder,
required this.updateItemBuilder,
required this.insertDuration,
required this.removeDuration,
required this.updateDuration,
required this.spawnIsolate,
}) : super(key: key);
}
abstract class ImplicitlyAnimatedListBaseState<W extends Widget,
B extends ImplicitlyAnimatedListBase<W, E>, E extends Object>
extends State<B> with DiffCallback<E>, TickerProviderStateMixin {
@protected
GlobalKey<CustomSliverAnimatedListState> animatedListKey = GlobalKey();
@nonVirtual
@protected
CustomSliverAnimatedListState get list => animatedListKey.currentState!;
late final DiffDelegate _delegate = DiffDelegate(this);
CancelableOperation? _diffOperation;
// Animation controller for custom animation that are not supported
// by the [AnimatedList], like updates.
late final updateAnimController = AnimationController(vsync: this);
late final Animation<double> updateAnimation = TweenSequence([
TweenSequenceItem(
tween: Tween(begin: 1.0, end: 0.0),
weight: 0.5,
),
TweenSequenceItem(
tween: Tween(begin: 0.0, end: 1.0),
weight: 0.5,
),
]).animate(updateAnimController);
// The currently active items.
late List<E> _data = List<E>.from(widget.items);
List<E> get data => _data;
// The items that have newly come in that
// will get diffed into the dataset.
late List<E> _newItems = List<E>.from(widget.items);
// The previous dataSet.
late List<E> _oldItems = List<E>.from(data);
//
Completer<int>? _mutex;
@nonVirtual
@override
List<E> get newList => _newItems;
@nonVirtual
@override
List<E> get oldList => _oldItems;
final Map<E, E> _changes = {};
@nonVirtual
@protected
Map<E, E> get changes => _changes;
@nonVirtual
@protected
ImplicitlyAnimatedItemBuilder<W, E> get itemBuilder => widget.itemBuilder;
@nonVirtual
@protected
RemovedItemBuilder<W, E>? get removeItemBuilder => widget.removeItemBuilder;
@nonVirtual
@protected
UpdatedItemBuilder<W, E>? get updateItemBuilder => widget.updateItemBuilder;
@override
void initState() {
super.initState();
didUpdateWidget(widget);
}
@override
void didUpdateWidget(ImplicitlyAnimatedListBase oldWidget) {
super.didUpdateWidget(oldWidget as B);
updateAnimController.duration = widget.updateDuration;
_updateList();
}
Future<void> _updateList() async {
await _mutex?.future;
_newItems = List<E>.from(widget.items);
_oldItems = List<E>.from(data);
_calcDiffs();
}
Future<void> _calcDiffs() async {
if (!mounted) return;
// Don't check for too long lists the list equality as
// this would begin to take longer than the diff
// algorithm itself.
final areListsShortEnoughForEqualityCheck =
_oldItems.length < 100 && _newItems.length < 100;
final areListsEqual =
areListsShortEnoughForEqualityCheck && listEquals(_oldItems, _newItems);
if (!areListsEqual) {
_changes.clear();
await _diffOperation?.cancel();
_diffOperation = CancelableOperation.fromFuture(
MyersDiff.withCallback<E>(this, spawnIsolate: widget.spawnIsolate),
);
_diffOperation?.then((diffs) {
// diffs is null when the operation
// gets canceled.
if (diffs == null || !mounted) {
return;
}
_delegate.applyDiffs(diffs);
_data = List<E>.from(_newItems);
updateAnimController
..reset()
..forward();
setState(() {});
});
} else {
// Always update the list with the newest data,
// even if the lists have the same value equality.
_data = List<E>.from(_newItems);
}
}
@nonVirtual
@protected
@override
bool areContentsTheSame(E oldItem, E newItem) => true;
@nonVirtual
@protected
@override
bool areItemsTheSame(E oldItem, E newItem) =>
widget.areItemsTheSame(oldItem, newItem);
@mustCallSuper
@protected
@override
void onInserted(int index, E item) =>
list.insertItem(index, duration: widget.insertDuration);
@mustCallSuper
@protected
@override
void onRemoved(int index) {
if (index >= oldList.length) return;
final item = oldList[index];
list.removeItem(
index,
(context, animation) =>
removeItemBuilder?.call(context, animation, item) ??
itemBuilder(context, animation, item, index),
duration: widget.removeDuration,
);
}
@mustCallSuper
@protected
@override
void onChanged(int startIndex, List<E> itemsChanged) {
int i = 0;
for (final item in itemsChanged) {
final index = startIndex + i;
if (index >= data.length) continue;
_changes[item] = data[index];
i++;
}
}
@nonVirtual
@protected
Widget buildItem(
BuildContext context, Animation<double> animation, E item, int index) {
if (updateItemBuilder != null && changes[item] != null) {
return buildUpdatedItemWidget(item);
}
return itemBuilder(context, animation, item, index);
}
@protected
Widget buildUpdatedItemWidget(E newItem) {
final oldItem = _changes[newItem];
return AnimatedBuilder(
animation: updateAnimation,
builder: (context, _) {
final value = updateAnimController.value;
final item = value < 0.5 ? oldItem : newItem;
return updateItemBuilder!(context, updateAnimation, item!);
},
);
}
@override
void dispose() {
updateAnimController.dispose();
super.dispose();
}
}
import 'dart:async';
import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:implicitly_animated_reorderable_list_2/src/custom_sliver_animated_list.dart';
import 'src.dart';
typedef ReorderStartedCallback<E> = void Function(E item, int index);
typedef ReorderFinishedCallback<E> = void Function(
E item, int from, int to, List<E> newItems);
/// A Flutter ListView that implicitly animates between the changes of two lists with
/// the support to reorder its items.
class ImplicitlyAnimatedReorderableList<E extends Object>
extends ImplicitlyAnimatedListBase<Reorderable, E> {
/// Whether the scroll view scrolls in the reading direction.
///
/// Defaults to false.
final bool reverse;
/// The axis along which the scroll view scrolls.
///
/// Defaults to [Axis.vertical].
final Axis scrollDirection;
/// An object that can be used to control the position to which this scroll
/// view is scrolled.
///
/// Must be null if [primary] is true.
///
/// A [ScrollController] serves several purposes. It can be used to control
/// the initial scroll position (see [ScrollController.initialScrollOffset]).
/// It can be used to control whether the scroll view should automatically
/// save and restore its scroll position in the [PageStorage] (see
/// [ScrollController.keepScrollOffset]). It can be used to read the current
/// scroll position (see [ScrollController.offset]), or change it (see
/// [ScrollController.animateTo]).
final ScrollController? controller;
/// Whether this is the primary scroll view associated with the parent
/// [PrimaryScrollController].
///
/// On iOS, this identifies the scroll view that will scroll to top in
/// response to a tap in the status bar.
///
/// Defaults to true when [scrollDirection] is [Axis.vertical] and
/// [controller] is null.
final bool? primary;
/// How the scroll view should respond to user input.
///
/// For example, determines how the scroll view continues to animate after the
/// user stops dragging the scroll view.
///
/// Defaults to matching platform conventions.
final ScrollPhysics? physics;
/// Whether the extent of the scroll view in the [scrollDirection] should be
/// determined by the contents being viewed.
///
/// If the scroll view does not shrink wrap, then the scroll view will expand
/// to the maximum allowed size in the [scrollDirection]. If the scroll view
/// has unbounded constraints in the [scrollDirection], then [shrinkWrap] must
/// be true.
///
/// Shrink wrapping the content of the scroll view is significantly more
/// expensive than expanding to the maximum allowed size because the content
/// can expand and contract during scrolling, which means the size of the
/// scroll view needs to be recomputed whenever the scroll position changes.
///
/// Defaults to false.
final bool shrinkWrap;
/// The amount of space by which to inset the children.
final EdgeInsetsGeometry? padding;
/// The duration of the animation when an item is being translated
/// to a new position in the list, i.e. when the item is reordered.
final Duration reorderDuration;
/// The duration of the animation when a reorder has been initiated
/// by the [Handle] of the item.
///
/// Defaults to [reorderDuration].
final Duration liftDuration;
/// The duration of the animation when an item is released and settles
/// to its final destination in the list.
///
/// Defaults to [liftDuration].
final Duration settleDuration;
/// Called in response to when an item changed from normal to dragged
/// state and may be reordered.
final ReorderStartedCallback<E>? onReorderStarted;
/// Called in response to when the dragged item has been released
/// and animated to its final destination. Here you should update
/// the underlying data in your model/bloc/database etc.
///
/// The `item` parameter of the callback is the item that has been reordered
/// `from` index `to` index. The `data` parameter represents the new data with
/// the item already being correctly reordered.
///
/// Note that this will also be called when the item didn't change its index in
/// the list (i.e. the user canceled the reorder).
///
/// This parameter should not be null.
final ReorderFinishedCallback<E> onReorderFinished;
/// A non-reorderable widget displayed at the top.
///
/// This can be useful if you want to show content before
/// the reorderable items without needing to nest the
/// list in another `Scrollable` and thereby loose out
/// on performance and autoscrolling.
final Widget? header;
/// A non-reorderable widget displayed at the bottom.
///
/// This can be useful if you want to show content after
/// the reorderable items without needing to nest the
/// list in another `Scrollable` and thereby loose out
/// on performance and autoscrolling.
final Widget? footer;
/// Creates a Flutter ListView that implicitly animates between the changes of two lists with
/// the support to reorder its items.
///
/// The [items] parameter represents the current items that should be displayed in
/// the list.
///
/// The [itemBuilder] callback is used to build each child as needed. The parent must
/// be a [Reorderable] widget.
///
/// The [areItemsTheSame] callback is called by the DiffUtil to decide whether two objects
/// represent the same item. For example, if your items have unique ids, this method should
/// check their id equality.
///
/// The [onReorderFinished] callback is called in response to when the dragged item has
/// been released and animated to its final destination. Here you should update
/// the underlying data in your model/bloc/database etc.
///
/// The [spawnIsolate] flag indicates whether to spawn a new isolate on which to
/// calculate the diff between the lists. Usually you wont have to specify this
/// value as the MyersDiff implementation will use its own metrics to decide, whether
/// a new isolate has to be spawned or not for optimal performance.
const ImplicitlyAnimatedReorderableList({
Key? key,
required List<E> items,
required ImplicitlyAnimatedItemBuilder<Reorderable, E> itemBuilder,
required ItemDiffUtil<E> areItemsTheSame,
RemovedItemBuilder<Reorderable, E>? removeItemBuilder,
UpdatedItemBuilder<Reorderable, E>? updateItemBuilder,
Duration insertDuration = const Duration(milliseconds: 500),
Duration removeDuration = const Duration(milliseconds: 500),
Duration updateDuration = const Duration(milliseconds: 500),
Duration? liftDuration,
Duration? settleDuration,
bool? spawnIsolate,
this.reverse = false,
this.scrollDirection = Axis.vertical,
this.controller,
this.primary,
this.physics,
this.shrinkWrap = false,
this.padding,
this.reorderDuration = const Duration(milliseconds: 300),
this.onReorderStarted,
required this.onReorderFinished,
this.header,
this.footer,
}) : liftDuration = liftDuration ?? reorderDuration,
settleDuration = settleDuration ?? liftDuration ?? reorderDuration,
assert(
reorderDuration <= const Duration(milliseconds: 1500),
'The drag duration should not be longer than 1500 milliseconds.',
),
super(
key: key,
items: items,
itemBuilder: itemBuilder,
areItemsTheSame: areItemsTheSame,
removeItemBuilder: removeItemBuilder,
updateItemBuilder: updateItemBuilder,
insertDuration: insertDuration,
removeDuration: removeDuration,
updateDuration: updateDuration,
spawnIsolate: spawnIsolate,
);
@override
ImplicitlyAnimatedReorderableListState<E> createState() =>
ImplicitlyAnimatedReorderableListState<E>();
static ImplicitlyAnimatedReorderableListState? of(BuildContext context) {
return context
.findAncestorStateOfType<ImplicitlyAnimatedReorderableListState>();
}
}
class ImplicitlyAnimatedReorderableListState<E extends Object>
extends ImplicitlyAnimatedListBaseState<Reorderable,
ImplicitlyAnimatedReorderableList<E>, E> {
// The key of the custom scroll view.
final GlobalKey _listKey = GlobalKey(debugLabel: 'list_key');
// The key of the draggedItem.
final GlobalKey _dragKey = GlobalKey(debugLabel: 'drag_key');
// The key of the header.
final GlobalKey _headerKey = GlobalKey(debugLabel: 'header_key');
bool get hasHeader => widget.header != null;
double _headerHeight = 0.0;
// The key of the footer.
final GlobalKey _footerKey = GlobalKey(debugLabel: 'footer_key');
bool get hasFooter => widget.footer != null;
double _footerHeight = 0.0;
Timer? _scrollAdjuster;
ScrollController? _controller;
ScrollController? get scrollController => _controller;
_Item? dragItem;
Widget? _dragWidget;
VoidCallback? _onDragEnd;
bool get isVertical => widget.scrollDirection != Axis.horizontal;
double _listSize = 0.0;
double get scrollOffset => _canScroll ? _controller!.offset : 0.0;
double get _maxScrollOffset => _controller?.position.maxScrollExtent ?? 0.0;
double get _scrollDelta => scrollOffset - _dragStartScrollOffset;
bool get _canScroll => _maxScrollOffset > 0.0;
bool get _up => _dragDelta.isNegative;
// Whether there is an item in the list that is currently being
// dragged/reordered.
bool _inDrag = false;
bool get inDrag => _inDrag;
// Whether there is an item in the list that is currently being
// reordered or moving towards its destination position.
bool _inReorder = false;
bool get inReorder => _inReorder;
double _dragStartOffset = 0.0;
double _dragStartScrollOffset = 0.0;
Key? get dragKey => dragItem?.key;
int? get _dragIndex => dragItem?.index;
double get _dragStart => dragItem!.start + _dragDelta;
double get _dragEnd => dragItem!.end + _dragDelta;
// double get _dragCenter => dragItem.middle + _dragDelta;
double get _dragSize => isVertical ? dragItem!.height : dragItem!.width;
final ValueNotifier<double> _dragDeltaNotifier = ValueNotifier(0.0);
double get _dragDelta => _dragDeltaNotifier.value;
set _dragDelta(double value) => _dragDeltaNotifier.value = value;
final ValueNotifier<double> _pointerDeltaNotifier = ValueNotifier(0.0);
double get _pointerDelta => _pointerDeltaNotifier.value;
set _pointerDelta(double value) => _pointerDeltaNotifier.value = value;
final Map<Key?, GlobalKey> _keys = {};
final Map<Key?, ReorderableState> _items = {};
final Map<Key?, AnimationController> _itemTranslations = {};
final Map<Key?, _Item> _itemBoxes = {};
@override
void initState() {
super.initState();
// The list must have a ScrollController in order to adjust the
// scroll position when the user drags an item outside the
// current viewport.
_controller = widget.controller ?? ScrollController();
_addReorderableUpdateAnimationSupport();
}
@override
void didUpdateWidget(ImplicitlyAnimatedReorderableList<E> oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.controller != null && widget.controller != _controller) {
_controller = widget.controller;
}
}
void onDragStarted(Key? key) {
_onDragEnd?.call();
_measureChild(key);
dragItem = _itemBoxes[key];
if (_dragIndex != null) {
final offset = _itemOffset(key);
_dragStartOffset = isVertical ? offset!.dy : offset!.dx;
_dragStartScrollOffset = scrollOffset;
_items[key]?.duration = widget.liftDuration;
setState(() {
_inDrag = true;
_inReorder = true;
});
widget.onReorderStarted?.call(data[_dragIndex!], _dragIndex!);
_adjustScrollPositionWhenNecessary();
}
}
void onDragUpdated(double delta) {
if (dragKey == null || dragItem == null) return;
// Allow the dragged item to be overscrolled to allow for
// continous scrolling while in drag.
final overscrollBound =
_canScroll && !(hasHeader || hasFooter) ? _dragSize : 0;
// Constrain the dragged item to the bounds of the list.
const epsilon = 2.0;
final minDelta = (_headerHeight - (dragItem!.start + overscrollBound)) -
_scrollDelta -
epsilon;
final maxDelta = ((_maxScrollOffset + _listSize + overscrollBound) -
(dragItem!.bottom + _footerHeight)) -
_scrollDelta +
epsilon;
_pointerDelta = delta.clamp(minDelta, maxDelta);
_dragDelta = _pointerDelta + _scrollDelta;
_adjustItemTranslations();
}
/// Adjust translations for all items that are not being dragged.
///
/// All non-drag items can be in one of two states:
/// 1. Not translated (original position)
/// 2. Translated (to up or down)
///
/// If some items index is smaller than _dragIndex,
/// the position of the item can move to 1 space down or stay in place.
///
/// If some items index is bigger than _dragIndex,
/// the position of the item can move to 1 space up or stay in place.
void _adjustItemTranslations() {
for (final item in _itemBoxes.values) {
if (item == dragItem) continue;
final key = item.key;
if (_itemTranslations[key]?.isAnimating == true) continue;
final translation = getTranslation(key);
final index = item.index!;
final itemStart = item.start + translation;
final itemEnd = item.end + translation;
if (index < _dragIndex!) {
if (itemStart >= _dragStart && translation == 0) {
_dispatchMove(key, _dragSize);
} else if (itemEnd <= _dragEnd && translation != 0) {
_dispatchMove(key, 0);
}
} else if (index > _dragIndex!) {
if (itemStart >= _dragStart && translation != 0) {
_dispatchMove(key, 0);
} else if (itemEnd <= _dragEnd && translation == 0) {
_dispatchMove(key, -_dragSize);
}
}
}
}
void _dispatchMove(Key? key, double delta,
{VoidCallback? onEnd, Duration? duration}) {
double value = 0.0;
// Remove and stop the old controller if there was one
// and start from the value where it left off.
final oldController = _itemTranslations.remove(key);
if (oldController != null) {
value = oldController.value;
oldController
..stop()
..dispose();
}
final start = min(value, delta);
final end = max(value, delta);
final controller = AnimationController(
vsync: this,
value: value,
lowerBound: start,
upperBound: end,
duration: duration ?? widget.reorderDuration,
);
if (controller.upperBound == controller.lowerBound) {
onEnd?.call();
return;
}
_items[key]?.setTranslation(controller);
_itemTranslations[key] = controller;
// ignore: avoid_single_cascade_in_expression_statements
controller.animateTo(
delta,
curve: Curves.easeInOut,
)..whenCompleteOrCancel(
() => onEnd?.call(),
);
}
void _adjustScrollPositionWhenNecessary() {
if (!_canScroll) return;
_scrollAdjuster?.cancel();
_scrollAdjuster = Timer.periodic(const Duration(milliseconds: 16), (_) {
final start = _headerHeight;
final end = _maxScrollOffset - _footerHeight;
final isAtStart = _up && scrollOffset < start;
final isAtEnd = !_up && scrollOffset > end;
if (isAtStart || isAtEnd) {
return;
}
final dragBox = _dragKey.renderBox;
if (dragBox == null) return;
final dragOffset =
dragBox.localToGlobal(Offset.zero, ancestor: context.renderBox);
final dragItemStart = isVertical ? dragOffset.dy : dragOffset.dx;
final dragItemEnd = dragItemStart + _dragSize;
double? delta;
if (dragItemStart <= 0) {
delta = dragItemStart;
} else if (dragItemEnd >= _listSize) {
delta = dragItemEnd - _listSize;
}
if (delta != null) {
final atLowerBound = dragItemStart <= 0;
delta = (delta.abs() / _dragSize).clamp(0.1, 1.0);
const maxSpeed = 20;
final max = atLowerBound ? -maxSpeed : maxSpeed;
var newOffset = scrollOffset + (max * delta);
if (!(scrollOffset < start) && !(scrollOffset > end)) {
newOffset = newOffset.clamp(start, end);
}
_controller!.jumpTo(newOffset);
onDragUpdated(_pointerDelta);
}
});
}
void onDragEnded() {
if (dragKey == null) return;
final target = findDropTargetItem();
_onDragEnd = () {
if (_dragIndex != null) {
if (!_itemBoxes.containsKey(target!.key)) {
_measureChild(target.key);
}
final toIndex = _itemBoxes[target.key]?.index;
if (toIndex != null) {
final E item = data.removeAt(_dragIndex!);
data.insert(toIndex, item);
widget.onReorderFinished(
item,
_dragIndex!,
toIndex,
List<E>.from(data),
);
}
}
_cancelReorder();
};
_items[dragKey]?.duration = widget.settleDuration;
final delta = () {
if (target == dragItem) {
return -_pointerDelta;
} else if (_up) {
return target!.start - _dragStart;
} else {
return target!.end - _dragEnd;
}
}();
_dispatchMove(
dragKey,
// Make sure not to pass a zero delta (i.e. the item didn't move)
// as this would lead to the same upper and lower bound on the animation
// controller, which is not allowed.
delta != 0.0 ? delta : 0.5,
onEnd: _onDragEnd,
duration: widget.settleDuration,
);
avoidConflictingMoves(target);
_scrollAdjuster?.cancel();
setState(() => _inDrag = false);
}
_Item? findDropTargetItem() {
_Item? target = dragItem;
// Boxes are in the order in which they are build, not
// necessarily index based.
final boxes = _itemBoxes.values.toList()
..sort((a, b) => a.index!.compareTo(b.index!));
for (final box in boxes) {
// Dont apply any translation to the currently dragged
// item (#56)
final t = box == dragItem ? 0.0 : getTranslation(box.key);
if (_up) {
if (_dragStart <= (box.start + t)) {
return box;
}
} else {
if (_dragEnd >= (box.end + t)) {
target = box;
}
}
}
return target;
}
void avoidConflictingMoves(_Item? target) {
_itemTranslations.forEach((key, controller) {
final item = _itemBoxes[key];
if (item != dragItem && item != target) {
if (item!.index! < target!.index!) {
controller.reverse();
} else {
controller.forward();
}
}
});
}
@override
void onRemoved(int index) {
super.onRemoved(index);
// When the item that is being reordered is removed,
// cancel the reorder operation.
if (index == _dragIndex) {
_cancelReorder();
}
}
void _cancelReorder() {
setState(() {
_inDrag = false;
_inReorder = false;
dragItem = null;
_onDragEnd = null;
_dragWidget = null;
_dragDelta = 0.0;
_pointerDelta = 0.0;
_scrollAdjuster?.cancel();
for (final key in _itemTranslations.keys) {
_items[key]?.setTranslation(null);
}
_itemTranslations.clear();
_disposeDrag();
});
}
void _disposeDrag() {
_controller!.jumpTo(_controller!.position.pixels);
}
double getTranslation(Key? key) =>
key == dragKey ? _dragDelta : _itemTranslations[key]?.value ?? 0.0;
void registerItem(ReorderableState item) {
_items[item.key] = item;
}
Offset? _itemOffset(Key? key) {
return _items[key]?.context.renderBox?.localToGlobal(
Offset.zero,
ancestor: context.renderBox,
);
}
bool _prevInDrag = false;
void _onRebuild() {
_itemBoxes.clear();
final needsRebuild = _listSize == 0 || inDrag != _prevInDrag;
_prevInDrag = inDrag;
double getSizeOfKey(GlobalKey key) =>
(isVertical ? key.height : key.width) ?? 0.0;
postFrame(() {
_listSize = getSizeOfKey(_listKey);
_headerHeight = hasHeader ? getSizeOfKey(_headerKey) : 0.0;
_footerHeight = hasFooter ? getSizeOfKey(_footerKey) : 0.0;
if (needsRebuild && mounted) {
setState(() {});
}
});
}
void _measureChild(Key? key, [int? index]) {
final item = _items[key];
if (item == null || !mounted || !item.mounted) {
return;
}
final box = item.context.renderBox;
final offset = _itemOffset(key)?.translate(
isVertical ? 0 : scrollOffset,
isVertical ? scrollOffset : 0,
);
if (box != null && offset != null) {
final i = index ?? _itemBoxes[key]?.index;
_itemBoxes[key] = _Item(key, box, i, offset, isVertical);
}
}
@override
Widget build(BuildContext context) {
_onRebuild();
final scrollView = CustomScrollView(
key: _listKey,
controller: _controller,
scrollDirection: widget.scrollDirection,
physics: inDrag ? const NeverScrollableScrollPhysics() : widget.physics,
primary: widget.primary,
reverse: widget.reverse,
shrinkWrap: widget.shrinkWrap,
slivers: <Widget>[
if (hasHeader)
SliverToBoxAdapter(
child: Container(
key: _headerKey,
child: widget.header,
),
),
SliverPadding(
padding: widget.padding ?? EdgeInsets.zero,
sliver: CustomSliverAnimatedList(
// Assign the animation key to the sliver
key: animatedListKey,
initialItemCount: newList.length,
itemBuilder: (context, index, animation) {
final Reorderable reorderable = buildItem(
context,
animation,
data[index],
index,
) as Reorderable;
postFrame(() => _measureChild(reorderable.key, index));
// Assign a new GlobalKey to the Reorderable
// so we can move it in and out of the list
// without losing it's state.
if (!_keys.containsKey(reorderable.key)) {
_keys[reorderable.key] = GlobalKey(
debugLabel: reorderable.key.toString(),
);
}
final child = KeyedSubtree(
key: _keys[reorderable.key],
child: reorderable,
);
if (dragKey != null && index == _dragIndex) {
final size = dragItem?.size;
// Determine if the dragged widget should be hidden
// immidiately, or with on frame delay in order to
// avoid item flash.
final mustRebuild = _dragWidget == null;
_dragWidget = child;
if (mustRebuild) postFrame(() => setState(() {}));
// The placeholder of the dragged item.
//
// Make sure not to use the actual widget but only its size
// when they have been determined, as a widget is only allowed
// to be laid out once.
return Invisible(
invisible: !mustRebuild,
child: mustRebuild ? child : SizedBox.fromSize(size: size),
);
} else {
return child;
}
},
),
),
if (hasFooter)
SliverToBoxAdapter(
child: Container(
key: _footerKey,
child: widget.footer,
),
),
],
);
return Stack(
clipBehavior: Clip.none,
children: <Widget>[
scrollView,
if (_dragWidget != null) _buildDraggedItem(),
],
);
}
Widget _buildDraggedItem() {
final EdgeInsets listPadding =
widget.padding as EdgeInsets? ?? EdgeInsets.zero;
return ValueListenableBuilder<double>(
// ignore: sort_child_properties_last
child: _dragWidget,
valueListenable: _pointerDeltaNotifier,
builder: (context, pointer, dragWidget) {
final delta = _dragStartOffset + pointer;
final dx = isVertical ? 0.0 : delta;
final dy = isVertical ? delta : 0.0;
return Transform.translate(
offset: Offset(dx, dy),
child: Container(
key: _dragKey,
// Set a fixed width on the dragged item in horizontal
// lists to prevent it from expanding.
width: !isVertical ? dragItem?.width : null,
// Add the horizontal padding in a vertical list as
// a padding, to prevent the item from filling the lists insets.
padding: EdgeInsets.only(
left: isVertical ? listPadding.left : 0.0,
right: isVertical ? listPadding.right : 0.0,
),
// In horizontal lists, add the top padding as a margin
// to offset the item from the top edge.
margin: EdgeInsets.only(top: !isVertical ? listPadding.top : 0.0),
child: dragWidget,
),
);
},
);
}
@override
Widget buildUpdatedItemWidget(E newItem) {
assert(updateItemBuilder != null);
// We need to override this method, as AnimatedBuilder is not
// supported as a top-level item widget in reorderable lists.
final value = updateAnimController.value;
final oldItem = changes[newItem];
final item = value < 0.5 ? oldItem : newItem;
return updateItemBuilder!(context, updateAnimation, item!);
}
// A more complex and less efficient update animation support implementation.
void _addReorderableUpdateAnimationSupport() {
bool didUpdateList = false;
updateAnimController
..addListener(() {
if (updateAnimController.isAnimating) {
if (!didUpdateList && updateAnimController.value > 0.5) {
setState(() {});
didUpdateList = true;
}
if (updateItemBuilder != null) {
changes.keys.forEach(buildUpdatedItemWidget);
}
}
})
..addStatusListener((status) {
if (status == AnimationStatus.completed ||
status == AnimationStatus.dismissed) {
didUpdateList = false;
}
});
}
@override
void dispose() {
_scrollAdjuster?.cancel();
if (widget.controller == null) {
_controller?.dispose();
}
super.dispose();
}
}
// A class that holds meta information about items in the list such as position and size.
class _Item extends Rect implements Comparable<_Item> {
final RenderBox box;
final Key? key;
final int? index;
final Offset offset;
final bool _isVertical;
_Item(
this.key,
this.box,
this.index,
this.offset,
// ignore: avoid_positional_boolean_parameters
this._isVertical,
) : super.fromLTWH(
offset.dx,
offset.dy,
box.size.width,
box.size.height,
);
double get start => _isVertical ? top : left;
double get end => _isVertical ? bottom : right;
double get middle => _isVertical ? center.dy : center.dx;
double? distance;
@override
int compareTo(_Item other) => distance != null && other.distance != null
? distance!.compareTo(other.distance!)
: -1;
@override
String toString() => '_Item key: $key, index: $index';
@override
bool operator ==(Object o) {
if (identical(this, o)) return true;
return o is _Item && o.key == key;
}
@override
int get hashCode => key.hashCode;
}
import 'dart:ui';
import 'package:flutter/material.dart';
import 'src.dart';
typedef ReorderableBuilder = Widget Function(
BuildContext context,
Animation<double> animation,
bool inDrag,
);
/// The parent widget of every item in an [ImplicitlyAnimatedReorderableList].
class Reorderable extends StatefulWidget {
/// Called, as needed, to build the child this Reorderable.
///
/// The [ReorderableBuilder] `animation` parameter supplies you with an animation you can use to
/// transition between the normal and the dragged state of the item. The `inDrag` parameter
/// indicates whether this item is currently being dragged/reordered.
final ReorderableBuilder? builder;
/// Used, as needed, to build the child this Reorderable.
///
/// This can be used to show a child that should not have
/// any animation when being dragged or dropped.
final Widget? child;
/// Creates a reorderable widget that must be the parent of every
/// item in an [ImplicitlyAnimatedReorderableList].
///
/// A [key] must be specified that uniquely identifies this Reorderable
/// in the list. The value of the key should not change throughout
/// the lifecycle of the item. For example if your item has a unique id,
/// you could use that with a `ValueKey`.
///
/// Either [builder] or [child] must be specified. The [builder]
/// can be used for custom animations when the item should be animated
/// between dragged and normal state. If you don't want to show any
/// animation between dragged and normal state, you can also use the
/// [child] as a shorthand instead.
const Reorderable({
required Key key,
this.builder,
this.child,
}) : assert(builder != null || child != null),
super(key: key);
@override
ReorderableState createState() => ReorderableState();
static ReorderableState? of(BuildContext context) {
return context.findAncestorStateOfType<ReorderableState>();
}
}
class ReorderableState extends State<Reorderable>
with SingleTickerProviderStateMixin {
late Key key = widget.key ?? UniqueKey();
late final _dragController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
late final _dragAnimation = CurvedAnimation(
parent: _dragController,
curve: Curves.linear,
);
Animation<double>? _translation;
bool _isVertical = true;
bool _inDrag = false;
bool get inDrag => _inDrag;
set inDrag(bool value) {
if (value != inDrag) {
_inDrag = value;
value ? _dragController.animateTo(1.0) : _dragController.animateBack(0.0);
}
}
// ignore: avoid_setters_without_getters
set duration(Duration value) => _dragController.duration = value;
void setTranslation(Animation<double>? animation) {
if (mounted) {
setState(() {
_translation = animation;
});
}
}
void rebuild() {
if (mounted) {
setState(() {});
}
}
void _registerItem() {
final list = ImplicitlyAnimatedReorderableList.of(context)!;
list.registerItem(this);
_dragController.duration = list.widget.settleDuration;
inDrag = list.dragItem?.key == key && list.inDrag;
_isVertical = list.isVertical;
}
@override
Widget build(BuildContext context) {
_registerItem();
final child = () {
if (widget.child != null) {
return widget.child;
} else {
return widget.builder!(context, _dragAnimation, _inDrag);
}
}();
return AnimatedBuilder(
child: child,
animation: _translation ?? const AlwaysStoppedAnimation(0.0),
builder: (context, child) {
final offset = _translation?.value ?? 0.0;
return Transform.translate(
offset: Offset(
_isVertical ? 0.0 : offset,
_isVertical ? offset : 0.0,
),
child: child,
);
},
);
}
@override
void dispose() {
_dragController.dispose();
super.dispose();
}
}
export 'diff/diff.dart';
export 'handle.dart';
export 'implicitly_animated_list.dart';
export 'implicitly_animated_list_base.dart';
export 'implicitly_animated_reorderable_list_2.dart';
export 'reorderable.dart';
export 'util/util.dart';
import 'package:flutter/material.dart';
/// A transition that fades the `child` in or out before shrinking or expanding
/// to the `childs` size along the `axis`.
///
/// This can be used as a item transition in an [ImplicitlyAnimatedReorderableList].
class SizeFadeTransition extends StatefulWidget {
/// The animation to be used.
final Animation<double> animation;
/// The curve of the animation.
final Curve curve;
/// How long the [Interval] for the [SizeTransition] should be.
///
/// The value must be between 0 and 1.
///
/// For example a `sizeFraction` of `0.66` would result in `Interval(0.0, 0.66)`
/// for the size animation and `Interval(0.66, 1.0)` for the opacity animation.
final double sizeFraction;
/// [Axis.horizontal] modifies the width,
/// [Axis.vertical] modifies the height.
final Axis axis;
/// Describes how to align the child along the axis the [animation] is
/// modifying.
///
/// A value of -1.0 indicates the top when [axis] is [Axis.vertical], and the
/// start when [axis] is [Axis.horizontal]. The start is on the left when the
/// text direction in effect is [TextDirection.ltr] and on the right when it
/// is [TextDirection.rtl].
///
/// A value of 1.0 indicates the bottom or end, depending upon the [axis].
///
/// A value of 0.0 (the default) indicates the center for either [axis] value.
final double axisAlignment;
/// The child widget.
final Widget? child;
const SizeFadeTransition({
Key? key,
required this.animation,
this.sizeFraction = 2 / 3,
this.curve = Curves.linear,
this.axis = Axis.vertical,
this.axisAlignment = 0.0,
this.child,
}) : assert(sizeFraction >= 0.0 && sizeFraction <= 1.0),
super(key: key);
@override
_SizeFadeTransitionState createState() => _SizeFadeTransitionState();
}
class _SizeFadeTransitionState extends State<SizeFadeTransition> {
late Animation size;
late Animation opacity;
@override
void initState() {
super.initState();
didUpdateWidget(widget);
}
@override
void didUpdateWidget(SizeFadeTransition oldWidget) {
super.didUpdateWidget(oldWidget);
final curve =
CurvedAnimation(parent: widget.animation, curve: widget.curve);
size = CurvedAnimation(
curve: Interval(0.0, widget.sizeFraction), parent: curve);
opacity = CurvedAnimation(
curve: Interval(widget.sizeFraction, 1.0), parent: curve);
}
@override
Widget build(BuildContext context) {
return SizeTransition(
sizeFactor: size as Animation<double>,
axis: widget.axis,
axisAlignment: widget.axisAlignment,
child: FadeTransition(
opacity: opacity as Animation<double>,
child: widget.child,
),
);
}
}
export 'size_fade_transition.dart';
import 'package:flutter/rendering.dart';
/// A class that can schedule a function to run at a later time using Future.delayed.
/// Additionally it supports canceling the future by not invoking the callback function
/// if it was canceled before.
class Handler {
VoidCallback? _callback;
bool _canceled = false;
bool _finished = false;
bool _invoked = false;
void post(Duration delay, VoidCallback callback) {
if (!_invoked) {
_callback = callback;
_invoked = true;
Future.delayed(delay, () {
if (!isFinished) {
callback();
}
_finished = true;
});
}
}
void runNow() {
if (!isFinished) {
_callback?.call();
_finished = true;
}
}
bool get isFinished => _finished || _canceled;
bool get isCanceled => _canceled;
void cancel() => _canceled = true;
}
Handler post(int delay, VoidCallback callback) {
return Handler()..post(Duration(milliseconds: delay), callback);
}
Handler postDuration(Duration delay, VoidCallback callback) {
return Handler()..post(delay, callback);
}
import 'package:flutter/material.dart';
class Invisible extends StatelessWidget {
final bool invisible;
final Widget? child;
const Invisible({
Key? key,
this.child,
this.invisible = false,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Visibility(
visible: !invisible,
maintainSize: true,
maintainAnimation: true,
maintainState: true,
child: child!,
);
}
}
import 'package:flutter/material.dart';
extension GlobalKeyExtension on GlobalKey {
RenderBox? get renderBox => currentContext?.renderBox;
Size? get size => renderBox?.size;
double? get height => size?.height;
double? get width => size?.width;
Offset? get offset => renderBox?.offset;
}
extension BuildContextExtension on BuildContext {
RenderBox? get renderBox => findRenderObject() as RenderBox?;
Size? get size => renderBox?.size;
double? get height => size?.height;
double? get width => size?.width;
Offset? get offset => renderBox?.offset;
}
extension RenderBoxExtension on RenderBox {
Offset get offset => localToGlobal(Offset.zero);
}
import 'dart:math' as math;
import 'package:flutter/material.dart';
export 'handler.dart';
export 'invisible.dart';
export 'key_extensions.dart';
void postFrame(VoidCallback callback) =>
WidgetsBinding.instance!.addPostFrameCallback((_) => callback());
extension ListExtension<E> on List<E> {
E? getOrNull(int index) {
try {
return this[index];
// ignore: avoid_catching_errors
} on Error {
return null;
} on Exception {
return null;
}
}
E? get firstOrNull => isNotEmpty ? first : null;
}
extension NumExtension<T extends num> on T {
bool isBetween(T min, T max) => this >= min && this <= max;
T atLeast(T min) => math.max(this, min);
T atMost(T max) => math.min(this, max);
}
library transitions;
export 'src/transitions/transitions.dart';
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: "direct main"
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.8.1"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.15.0"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.10"
meta:
dependency: "direct main"
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
version: "1.10.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.2"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
sdks:
dart: ">=2.12.0 <3.0.0"
name: implicitly_animated_reorderable_list_2
description: A Flutter ListView that implicitly animates between the changes of
two lists with the support to reorder its items.
version: 0.5.1
homepage: https://github.com/theniceboy/implicitly_animated_reorderable_list_2/tree/master/example/lib/ui
environment:
sdk: '>=2.12.0 <3.0.0'
dependencies:
async: ^2.8.1
flutter:
sdk: flutter
meta: ^1.7.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter: null
import 'package:flutter_test/flutter_test.dart';
import 'package:implicitly_animated_reorderable_list_2/implicitly_animated_reorderable_list_2.dart';
// ignore_for_file: avoid_print
void main() {
test('Should detect deletions correctly', () async {
// arrange
final oldItems = List.generate(10, (index) => _Model(index.toString()));
// If you uncomment this, everything will work as expected.
// final newItems = List.from(oldItems);
final newItems = List.generate(10, (index) => _Model(index.toString()));
newItems.removeAt(1);
// act
final diff = await MyersDiff.diff<_Model>(
newItems,
oldItems,
areItemsTheSame: (oldItem, newItem) => oldItem.id == newItem.id,
// If you set this to false, everything will work as expected.
spawnIsolate: true,
);
// assert
print('old: ${oldItems.map((e) => e.id).toList()}');
print('new: ${newItems.map((e) => e.id).toList()}');
print('diff $diff');
});
}
class _Model {
final String id;
_Model(this.id);
@override
String toString() => id;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment