initial commit

This commit is contained in:
Joons Stuijvenberg, van 2023-03-15 12:36:53 +01:00
commit 5ca9f7ffa6
20 changed files with 1395 additions and 0 deletions

39
.gitignore vendored Normal file
View file

@ -0,0 +1,39 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.packages
build/
example/android
example/ios
example/web
example/linux
example/macos
example/windows
coverage/

45
.metadata Normal file
View file

@ -0,0 +1,45 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled.
version:
revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
channel: stable
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
- platform: android
create_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
- platform: ios
create_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
- platform: linux
create_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
- platform: macos
create_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
- platform: web
create_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
- platform: windows
create_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

3
CHANGELOG.md Normal file
View file

@ -0,0 +1,3 @@
## [0.0.1] 15 March 2023
- Initial Release

194
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,194 @@
# Contributing
First off, thanks for taking the time to contribute! ❤️
All types of contributions are encouraged and valued.
See the [Table of Contents](#table-of-contents) for different ways to help and details about how we handle them.
Please make sure to read the relevant section before making your contribution.
It will make it a lot easier for us maintainers and smooth out the experience for all involved.
Iconica looks forward to your contributions. 🎉
## Table of contents
- [Code of conduct](#code-of-conduct)
- [I Have a Question](#i-have-a-question)
- [I Want To Contribute](#i-want-to-contribute)
- [Reporting Bugs](#reporting-bugs)
- [Contributing code](#contributing-code)
## Code of conduct
### Legal notice
When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license.
All accepted pull requests and other additions to this project will be considered intellectual property of Iconica.
All repositories should be kept clean of jokes, easter eggs and other unnecessary additions.
## I have a question
If you want to ask a question, we assume that you have read the available documentation found within the code.
Before you ask a question, it is best to search for existing issues that might help you.
In case you have found a suitable issue but still need clarification, you can ask your question
It is also advisable to search the internet for answers first.
If you then still feel the need to ask a question and need clarification, we recommend the following:
- Open an issue.
- Provide as much context as you can about what you're running into.
We will then take care of the issue as soon as possible.
## I want to contribute
### Reporting bugs
<!-- omit in toc -->
**Before submitting a bug report**
A good bug report shouldn't leave others needing to chase you up for more information.
Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report.
Please complete the following steps in advance to help us fix any potential bug as fast as possible.
- Make sure that you are using the latest version.
- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (If you are looking for support, you might want to check [this section](#i-have-a-question)).
- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error.
- Also make sure to search the internet (including Stack Overflow) to see if users outside of Iconica have discussed the issue.
- Collect information about the bug:
- Stack trace (Traceback)
- OS, Platform and Version (Windows, Linux, macOS, x86, ARM)
- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant.
- Time and date of occurance
- Describe the expected result and actual result
- Can you reliably reproduce the issue? And can you also reproduce it with older versions? Describe all steps that lead to the bug.
Once it's filed:
- The project team will label the issue accordingly.
- A team member will try to reproduce the issue with your provided steps.
If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for additional information.
- If the team is able to reproduce the issue, it will be moved into the backlog, as well as marked with a priority, and the issue will be left to be [implemented by someone](#contributing-code).
### Contributing code
When you start working on your contribution, make sure you are aware of the relevant documentation and the functionality of the component you are working on.
When writing code, follow the style guidelines set by Dart: [Effective Dart](https://Dart.dev/guides/language/effective-Dart). This contains most information you will need to write clean Dart code that is well documented.
**Documentation**
As Effective Dart indicates, documenting your public methods with Dart doc comments is recommended.
Aside from Effective Dart, we require specific information in the documentation of a method:
At the very least, your documentation should first name what the code does, then followed below by requirements for calling the method, the result of the method.
Any references to internal variables or other methods should be done through [var] to indicate a reference.
If the method or class is complex enough (determined by the reviewers) an example is required.
If unsure, add an example in the docs using code blocks.
For classes and methods, document the individual parameters with their implications.
> Tip: Remember that the shortest documentation can be written by having good descriptive names in the first place.
An example:
```Dart
library iconica_utilities.bidirectional_sorter;
part 'sorter.Dart';
part 'enum.Dart';
/// Generic sort method, allow sorting of list with primitives or complex types.
/// Uses [SortDirection] to determine the direction, either Ascending or Descending,
/// Gets called on [List] toSort of type [T] which cannot be shorter than 2.
/// Optionally for complex types a [Comparable] [Function] can be given to compare complex types.
/// ```
/// List<TestObject> objects = [];
/// for (int i = 0; i < 10; i++) {
/// objects.add(TestObject(name: "name", id: i));
/// }
///
/// sort<TestObject>(
/// SortDirection.descending, objects, (object) => object.id);
///
/// ```
/// In the above example a list of TestObjects is created, and then sorted in descending order.
/// If the implementation of TestObject is as following:
/// ```
/// class TestObject {
/// final String name;
/// final int id;
///
/// TestObject({required this.name, required this.id});
/// }
/// ```
/// And the list is logged to the console, the following will appear:
/// ```
/// [name9, name8, name7, name6, name5, name4, name3, name2, name1, name0]
/// ```
void sort<T>(
/// Determines the sorting direction, can be either Ascending or Descending
SortDirection sortDirection,
/// Incoming list, which gets sorted
List<T> toSort, [
/// Optional comparable, which is only necessary for complex types
SortFieldGetter<T>? sortValueCallback,
]) {
if (toSort.length < 2) return;
assert(
toSort.whereType<Comparable>().isNotEmpty || sortValueCallback != null);
BidirectionalSorter<T>(
sortInstructions: <SortInstruction<T>>[
SortInstruction(
sortValueCallback ?? (t) => t as Comparable, sortDirection),
],
).sort(toSort);
}
/// same functionality as [sort] but with the added functionality
/// of sorting multiple values
void sortMulti<T>(
/// Incoming list, which gets sorted
List<T> toSort,
/// list of comparables to sort multiple values at once,
/// priority based on index
List<SortInstruction<T>> sortValueCallbacks,
) {
if (toSort.length < 2) return;
assert(sortValueCallbacks.isNotEmpty);
BidirectionalSorter<T>(
sortInstructions: sortValueCallbacks,
).sort(toSort);
}
```
**Tests**
For each public method that was created, excluding widgets, which contains any form of logic (e.g. Calculations, predicates or major side-effects) tests are required.
A set of tests is written for each method, covering at least each path within the method. For example:
```Dart
void foo() {
try {
var bar = doSomething();
if (bar) {
doSomethingElse();
} else {
doSomethingCool();
}
} catch (_) {
displayError();
}
}
```
The method above should result in 3 tests:
1. A test for the path leading to displayError by the cause of an exception
2. A test for if bar is true, resulting in doSomethingElse()
3. A test for if bar is false, resulting in the doSomethingCool() method being called.
This means that we require 100% coverage of each method you test.

24
README.md Normal file
View file

@ -0,0 +1,24 @@
# Grid to list
Grid to List that animates between grid and list when a child in either gets tapped
https://user-images.githubusercontent.com/57899901/225304113-28cac130-36e8-4c7b-8fed-50032972ad4f.mp4
## Usage
To use this package, add `flutter_grid_to_list` as a dependency in your pubspec.yaml file.
## How to use
See the [Example Code](example/lib/main.dart) for an example on how to use this package.
## Issues
Please file any issues, bugs or feature request as an issue on our [GitHub](https://github.com/Iconica-Development/to_do_list) page. Commercial support is available if you need help with integration with your app or services. You can contact us at [support@iconica.nl](mailto:support@iconica.nl).
## Want to contribute
If you would like to contribute to the plugin (e.g. by improving the documentation, solving a bug or adding a cool new feature), please carefully review our [contribution guide](./CONTRIBUTING.md) and send us your [pull request](https://github.com/Iconica-Development/to_do_list/pulls).
## Author
This `flutter_grid_to_list` for Flutter is developed by [Iconica](https://iconica.nl). You can contact us at <support@iconica.nl>

29
analysis_options.yaml Normal file
View file

@ -0,0 +1,29 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

44
example/.gitignore vendored Normal file
View file

@ -0,0 +1,44 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

45
example/.metadata Normal file
View file

@ -0,0 +1,45 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled.
version:
revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
channel: stable
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
- platform: android
create_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
- platform: ios
create_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
- platform: linux
create_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
- platform: macos
create_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
- platform: web
create_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
- platform: windows
create_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
base_revision: 12cb4eb7a009f52b347b62ade7cb4854b926af72
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

3
example/README.md Normal file
View file

@ -0,0 +1,3 @@
# example
This is an example project showing how to use the grid to list component.

View file

@ -0,0 +1,29 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

80
example/lib/main.dart Normal file
View file

@ -0,0 +1,80 @@
// SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/material.dart';
import 'dart:math';
import 'package:flutter_grid_to_list/flutter_grid_to_list.dart';
void main() {
runApp(const MaterialApp(
home: GridToList(),
));
}
class GridToList extends StatefulWidget {
const GridToList({super.key});
@override
State<GridToList> createState() => _GridToListState();
}
class _GridToListState extends State<GridToList> {
late AnimatedGridToListController controller;
@override
void initState() {
super.initState();
controller = AnimatedGridToListController();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
var containers = <Container>[
...List.generate(80, (index) => index)
.map((e) => Container(
width: 100,
height: 100,
color: Color((Random().nextDouble() * 0xFFFFFF).toInt())
.withOpacity(1.0),
))
.toList(),
];
@override
Widget build(BuildContext context) {
//get size
return Scaffold(
body: AnimatedGridToList(
controller: controller,
onTap: (i) {
if (!controller.isExpanded) {
controller.expand(i, const Duration(seconds: 2));
} else {
controller.shrink(const Duration(seconds: 2));
}
},
itemBuilder: AnimatedGridToListItemBuilder(
gridItemBuilder: (context, index) {
return containers[index];
},
listItemBuilder: (context, index) => Container(
height: MediaQuery.of(context).size.height * 0.6,
width: MediaQuery.of(context).size.width * 0.8,
color: containers[index].color,
padding: const EdgeInsets.all(8),
child: Text('Item $index'),
),
listItemSize: Size(
MediaQuery.of(context).size.width * 0.8,
MediaQuery.of(context).size.height * 0.6,
),
gridItemSize: const Size(50, 50),
itemCount: containers.length,
)));
}
}

195
example/pubspec.lock Normal file
View file

@ -0,0 +1,195 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0
url: "https://pub.dev"
source: hosted
version: "2.10.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
characters:
dependency: transitive
description:
name: characters
sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c
url: "https://pub.dev"
source: hosted
version: "1.2.1"
clock:
dependency: transitive
description:
name: clock
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
url: "https://pub.dev"
source: hosted
version: "1.1.1"
collection:
dependency: transitive
description:
name: collection
sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0
url: "https://pub.dev"
source: hosted
version: "1.17.0"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be
url: "https://pub.dev"
source: hosted
version: "1.0.5"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_grid_to_list:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
version: "1.0.0+1"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
url: "https://pub.dev"
source: hosted
version: "2.0.1"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
js:
dependency: transitive
description:
name: js
sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7"
url: "https://pub.dev"
source: hosted
version: "0.6.5"
lints:
dependency: transitive
description:
name: lints
sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
matcher:
dependency: transitive
description:
name: matcher
sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72"
url: "https://pub.dev"
source: hosted
version: "0.12.13"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
url: "https://pub.dev"
source: hosted
version: "0.2.0"
meta:
dependency: transitive
description:
name: meta
sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42"
url: "https://pub.dev"
source: hosted
version: "1.8.0"
path:
dependency: transitive
description:
name: path
sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b
url: "https://pub.dev"
source: hosted
version: "1.8.2"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
url: "https://pub.dev"
source: hosted
version: "1.9.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
url: "https://pub.dev"
source: hosted
version: "1.11.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.dev"
source: hosted
version: "1.2.1"
test_api:
dependency: transitive
description:
name: test_api
sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206
url: "https://pub.dev"
source: hosted
version: "0.4.16"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
sdks:
dart: ">=2.19.3 <3.0.0"

26
example/pubspec.yaml Normal file
View file

@ -0,0 +1,26 @@
name: example
description: A new Flutter project.
publish_to: "none"
version: 1.0.0+1
environment:
sdk: ">=2.19.3 <3.0.0"
dependencies:
flutter:
sdk: flutter
flutter_grid_to_list:
path: ..
cupertino_icons: ^1.0.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter:
uses-material-design: true

BIN
flutter_grid_to_list.gif Normal file

Binary file not shown.

View file

@ -0,0 +1,7 @@
// SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
library flutter_grid_to_list;
export 'src/animated_grid_to_list.dart';

View file

@ -0,0 +1,302 @@
// SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/material.dart';
import 'package:flutter_grid_to_list/src/animated_grid_to_list_item_builder.dart';
import 'package:flutter_grid_to_list/src/animated_grid_to_list_type.dart';
export 'animated_grid_to_list_item_builder.dart';
class AnimatedGridToList extends StatefulWidget {
const AnimatedGridToList({
Key? key,
required this.itemBuilder,
required this.controller,
this.startWithGrid = true,
this.onTap,
}) : super(key: key);
final AnimatedGridToListItemBuilder itemBuilder;
final AnimatedGridToListController controller;
final bool startWithGrid;
final Function(int)? onTap;
@override
State<AnimatedGridToList> createState() => _AnimatedGridToListState();
}
class _AnimatedGridToListState extends State<AnimatedGridToList>
with TickerProviderStateMixin {
@override
void initState() {
super.initState();
widget.controller._itemBuilder = widget.itemBuilder;
widget.controller._type =
widget.startWithGrid ? GridToListType.grid : GridToListType.list;
widget.controller._vsync = this;
widget.controller.addListener(listenerFunction);
}
void listenerFunction() {
setState(() {});
}
@override
void dispose() {
super.dispose();
widget.controller.removeListener(listenerFunction);
}
@override
Widget build(BuildContext context) {
widget.controller._context = context;
return ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
child: SingleChildScrollView(
// physics: MagnetScrollPhysics(
// itemSize: widget.controller._boxHeight ??
// widget.itemBuilder.gridItemSize.height,
// ),
controller: widget.controller._scrollController,
child: widget.controller._type.render(
spacing: widget.controller._spacing,
boxWidth: widget.controller._boxWidth,
boxHeight: widget.controller._boxHeight,
tappedItem: widget.controller._tappedItem,
isAnimating: widget.controller._isAnimating,
wrapAlignment: widget.controller._wrapAlignment,
onTap: widget.onTap,
context: context,
itemBuilder: widget.itemBuilder,
),
),
);
}
}
class AnimatedGridToListController extends ChangeNotifier {
final ScrollController _scrollController = ScrollController();
late AnimationController _controller;
late Animation<double> _widthAnimation;
late Animation<double> _heightAnimation;
late AnimatedGridToListItemBuilder _itemBuilder;
late BuildContext _context;
late TickerProvider _vsync;
late GridToListType _type;
double? _boxWidth;
double? _boxHeight;
double? _initalHeight;
double? _initialWidth;
double? _previousScrollOffset;
double? _previousScrollOffsetCopy;
double _spacing = 0;
bool _isAnimating = false;
bool _isExpanded = false;
int? _tappedItem;
int? _prevIndex;
WrapAlignment _wrapAlignment = WrapAlignment.center;
void _setAnimation() {
var endValueWidth = _itemBuilder.listItemSize.width;
var endValueHeight = _itemBuilder.listItemSize.height == double.infinity
? _itemBuilder.gridItemSize.height
: _itemBuilder.listItemSize.height;
_initialWidth ??= _itemBuilder.gridItemSize.width;
_initalHeight ??= _itemBuilder.gridItemSize.height;
if (_type == GridToListType.list) {
_widthAnimation =
Tween<double>(begin: endValueWidth, end: _initialWidth).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.easeInQuint,
),
);
_heightAnimation =
Tween<double>(begin: endValueHeight, end: _initalHeight).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.easeInQuint,
),
);
} else {
_widthAnimation =
Tween<double>(begin: _initialWidth, end: endValueWidth).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.easeOutQuart,
),
);
_heightAnimation =
Tween<double>(begin: _initalHeight, end: endValueHeight).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.easeOutQuart,
),
);
}
}
void _handleScroll(int index) {
switch (_type) {
case GridToListType.grid:
_scrollController.jumpTo((index * (_boxHeight ?? 0)));
if ([WrapAlignment.start, WrapAlignment.end].contains(_wrapAlignment) &&
_controller.value > 0.5) {
_wrapAlignment = WrapAlignment.center;
}
break;
case GridToListType.list:
_previousScrollOffset = null;
_scrollController.jumpTo(index * (_boxHeight ?? 0));
if ([WrapAlignment.start, WrapAlignment.end].contains(_wrapAlignment) &&
_controller.value > 0.5) {
_wrapAlignment = WrapAlignment.center;
}
break;
}
}
void _finalize(double prevScrollOffset) {
if (_controller.isCompleted) {
switch (_type) {
case GridToListType.grid:
_type = _type.next;
_handleScroll(_tappedItem!);
break;
case GridToListType.list:
_wrapAlignment = WrapAlignment.center;
_type = _type.next;
_handleScroll(_tappedItem!);
_scrollController.jumpTo(prevScrollOffset);
_spacing = 0;
break;
}
notifyListeners();
_controller.removeListener(_listenerFunction);
}
}
void _listenerFunction() {
if (_itemBuilder.listItemSize.height == double.infinity) {
_boxHeight = null;
} else {
_boxHeight = _heightAnimation.value;
}
_boxWidth = _widthAnimation.value;
if (_context.size != null) {
_spacing = _context.size!.width - _boxWidth!;
} else {
_spacing = 0;
}
_isAnimating = _controller.isAnimating;
notifyListeners();
_finalize(_previousScrollOffsetCopy!);
}
WrapAlignment _determineAlignment(int index, BuildContext context,
AnimatedGridToListItemBuilder itemBuilder) {
var amountOfItems =
((context.size?.width ?? 0) / itemBuilder.gridItemSize.width).floor();
var determineSeq = amountOfItems % 3;
var itemLowest = (amountOfItems / 3).floor();
late int itemIndex;
if (amountOfItems != 0) {
itemIndex = index % amountOfItems;
} else {
itemIndex = 0;
}
late int itemsSide;
late int itemsCenter;
switch (determineSeq) {
case 0:
itemsSide = itemLowest;
itemsCenter = itemLowest;
break;
case 1:
itemsSide = itemLowest;
itemsCenter = itemLowest + 1;
break;
case 2:
itemsSide = itemLowest + 1;
itemsCenter = itemLowest;
break;
}
if ((itemIndex + 1) <= itemsSide) {
return WrapAlignment.start;
} else if ((itemIndex + 1) <= itemsSide + itemsCenter) {
return WrapAlignment.center;
} else {
return WrapAlignment.end;
}
}
/// Can be used to retrieve the status of the widget. This is only useful if you use [shrink] and [expand] methods
bool get isExpanded => _isExpanded;
/// Can be called to shrink to widget to it's grid state. Only works when the current state is of type [GridToListType.list]
void shrink([Duration? duration]) {
_isExpanded = false;
if (_type == GridToListType.list) {
resize(_prevIndex!, duration);
}
}
/// Can be called to expand to widget to it's list state. Only works when the current state is of type [GridToListType.grid]
///
/// Gets an index to determine which item it needs to transform.
void expand(int index, [Duration? duration]) {
_isExpanded = true;
_prevIndex = index;
if (_type == GridToListType.grid) {
resize(index, duration);
}
}
/// Can be called to dynamically change the state of the widget. Works in either [GridToListType.grid] or [GridToListType.list]
///
/// Gets an index to determine which item it needs to transform.
void resize(int index, Duration? duration) {
if (!_isAnimating) {
_previousScrollOffset ??= _scrollController.offset;
_previousScrollOffsetCopy = _previousScrollOffset ?? 0;
_controller = AnimationController(
vsync: _vsync,
duration: duration ?? const Duration(milliseconds: 200),
);
_tappedItem = index;
_wrapAlignment = _determineAlignment(index, _context, _itemBuilder);
notifyListeners();
_setAnimation();
_controller.addListener(_listenerFunction);
_controller.forward();
}
}
}

View file

@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/material.dart';
/// Class required by [AnimatedGridToList] to build items.
/// Requires [gridItemBuilder] which is an [IndexedWidgetBuilder] to build items in [AnimatedGridToList] grid state.
///
/// Requires [listItemBuilder] which is an [IndexedWidgetBuilder] to build items in [AnimatedGridToList] list state.
///
/// Requires [gridItemSize] which is of type [Size] to build the items and handle the scrolling accordingly.
///
/// Requires [listItemSize] which is of type [Size] to build the items and handle the scrolling accordingly.
///
/// It also requires an [itemCount], which is of type [int].
class AnimatedGridToListItemBuilder {
AnimatedGridToListItemBuilder({
required this.gridItemBuilder,
required this.listItemBuilder,
required this.itemCount,
required this.gridItemSize,
required this.listItemSize,
});
/// [IndexedWidgetBuilder] which build the items in grid state.
final IndexedWidgetBuilder gridItemBuilder;
/// [IndexedWidgetBuilder] which build the items in list state.
final IndexedWidgetBuilder listItemBuilder;
/// A [Size] to build the items in the correct manner and handle scrolling in grid state.
final Size gridItemSize;
/// A [Size] to build the items in the correct manner and handle scrolling in list state.
final Size listItemSize;
/// A [int] of the amount of items to build.
int itemCount;
}

View file

@ -0,0 +1,81 @@
// SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/material.dart';
import 'package:flutter_grid_to_list/src/animated_grid_to_list_item_builder.dart';
/// An enum to which offers the possibilties the [AnimatedGridToList] can be rendered in.
enum GridToListType {
/// [grid] type of [GridToListType] which build a grid for [AnimatedGridToList]
grid,
/// [list] type of [GridToListType] which build a grid for [AnimatedGridToList]
list,
}
extension NextType on GridToListType {
/// Extenstion method on [GridToListType] which can be called to return the next value.
get next =>
this == GridToListType.grid ? GridToListType.list : GridToListType.grid;
}
extension Render on GridToListType {
Widget render({
required WrapAlignment wrapAlignment,
required double spacing,
required double? boxWidth,
required double? boxHeight,
required int? tappedItem,
required AnimatedGridToListItemBuilder itemBuilder,
required Function(int)? onTap,
required BuildContext context,
required bool isAnimating,
}) {
return !isAnimating
? Wrap(
alignment: WrapAlignment.center,
spacing: spacing,
children: [
for (int i = 0; i < itemBuilder.itemCount; i++) ...[
Opacity(
opacity: isAnimating && tappedItem == i
? 1
: isAnimating && tappedItem != i
? 0
: 1,
child: this == GridToListType.grid
? SizedBox(
width: boxWidth ?? itemBuilder.gridItemSize.width,
height: boxHeight ?? itemBuilder.gridItemSize.height,
child: GestureDetector(
onTap: () => onTap?.call(i),
child: itemBuilder.gridItemBuilder(context, i),
),
)
: SizedBox(
width: boxWidth ?? itemBuilder.listItemSize.width,
height: null,
child: GestureDetector(
onTap: () => onTap?.call(i),
child: itemBuilder.listItemBuilder(context, i),
),
),
),
],
],
)
: Align(
alignment: tappedItem != itemBuilder.itemCount - 1
? Alignment.topCenter
: Alignment.bottomCenter,
child: SizedBox(
width: boxWidth ?? itemBuilder.listItemSize.width,
height: boxHeight ?? itemBuilder.listItemSize.height,
child: GestureDetector(
child: itemBuilder.listItemBuilder(context, tappedItem!),
),
),
);
}
}

188
pubspec.lock Normal file
View file

@ -0,0 +1,188 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0
url: "https://pub.dev"
source: hosted
version: "2.10.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
characters:
dependency: transitive
description:
name: characters
sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c
url: "https://pub.dev"
source: hosted
version: "1.2.1"
clock:
dependency: transitive
description:
name: clock
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
url: "https://pub.dev"
source: hosted
version: "1.1.1"
collection:
dependency: transitive
description:
name: collection
sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0
url: "https://pub.dev"
source: hosted
version: "1.17.0"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be
url: "https://pub.dev"
source: hosted
version: "1.0.5"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
url: "https://pub.dev"
source: hosted
version: "2.0.1"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
js:
dependency: transitive
description:
name: js
sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7"
url: "https://pub.dev"
source: hosted
version: "0.6.5"
lints:
dependency: transitive
description:
name: lints
sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
matcher:
dependency: transitive
description:
name: matcher
sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72"
url: "https://pub.dev"
source: hosted
version: "0.12.13"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
url: "https://pub.dev"
source: hosted
version: "0.2.0"
meta:
dependency: transitive
description:
name: meta
sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42"
url: "https://pub.dev"
source: hosted
version: "1.8.0"
path:
dependency: transitive
description:
name: path
sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b
url: "https://pub.dev"
source: hosted
version: "1.8.2"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
url: "https://pub.dev"
source: hosted
version: "1.9.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
url: "https://pub.dev"
source: hosted
version: "1.11.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.dev"
source: hosted
version: "1.2.1"
test_api:
dependency: transitive
description:
name: test_api
sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206
url: "https://pub.dev"
source: hosted
version: "0.4.16"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
sdks:
dart: ">=2.19.3 <3.0.0"

21
pubspec.yaml Normal file
View file

@ -0,0 +1,21 @@
name: flutter_grid_to_list
description: A new Flutter project.
publish_to: "none"
version: 1.0.0+1
environment:
sdk: ">=2.19.3 <3.0.0"
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter:
uses-material-design: true