├── .gitignore
├── .metadata
├── .vscode
└── launch.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── analysis_options.yaml
├── documentation
├── after.png
├── before.png
├── move.gif
├── re-layout.gif
├── resize.gif
└── slots.png
├── example
├── .gitignore
├── .metadata
├── README.md
├── analysis_options.yaml
├── android
│ ├── .gitignore
│ ├── app
│ │ ├── build.gradle
│ │ └── src
│ │ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── java
│ │ │ │ └── com
│ │ │ │ │ └── example
│ │ │ │ │ └── example
│ │ │ │ │ └── MainActivity.java
│ │ │ └── res
│ │ │ │ ├── drawable-v21
│ │ │ │ └── launch_background.xml
│ │ │ │ ├── drawable
│ │ │ │ └── launch_background.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── values-night
│ │ │ │ └── styles.xml
│ │ │ │ └── values
│ │ │ │ └── styles.xml
│ │ │ └── profile
│ │ │ └── AndroidManifest.xml
│ ├── build.gradle
│ ├── gradle.properties
│ ├── gradle
│ │ └── wrapper
│ │ │ └── gradle-wrapper.properties
│ └── settings.gradle
├── assets
│ ├── github.png
│ ├── img.png
│ ├── linkedin.png
│ ├── pub_dev.png
│ └── twitter.png
├── ios
│ ├── .gitignore
│ ├── Flutter
│ │ ├── AppFrameworkInfo.plist
│ │ ├── Debug.xcconfig
│ │ └── Release.xcconfig
│ ├── Podfile
│ ├── Runner.xcodeproj
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ │ └── WorkspaceSettings.xcsettings
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── Runner.xcscheme
│ ├── Runner.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── WorkspaceSettings.xcsettings
│ └── Runner
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ ├── Icon-App-20x20@1x.png
│ │ │ ├── Icon-App-20x20@2x.png
│ │ │ ├── Icon-App-20x20@3x.png
│ │ │ ├── Icon-App-29x29@1x.png
│ │ │ ├── Icon-App-29x29@2x.png
│ │ │ ├── Icon-App-29x29@3x.png
│ │ │ ├── Icon-App-40x40@1x.png
│ │ │ ├── Icon-App-40x40@2x.png
│ │ │ ├── Icon-App-40x40@3x.png
│ │ │ ├── Icon-App-60x60@2x.png
│ │ │ ├── Icon-App-60x60@3x.png
│ │ │ ├── Icon-App-76x76@1x.png
│ │ │ ├── Icon-App-76x76@2x.png
│ │ │ └── Icon-App-83.5x83.5@2x.png
│ │ └── LaunchImage.imageset
│ │ │ ├── Contents.json
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ ├── LaunchImage@3x.png
│ │ │ └── README.md
│ │ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ └── Runner-Bridging-Header.h
├── lib
│ ├── add_dialog.dart
│ ├── data_widget.dart
│ ├── main.dart
│ ├── screens
│ │ ├── dashboard_page.dart
│ │ └── main_page.dart
│ └── storage.dart
├── pubspec.lock
├── pubspec.yaml
├── test
│ └── widget_test.dart
└── web
│ ├── favicon.png
│ ├── icons
│ ├── Icon-192.png
│ ├── Icon-512.png
│ ├── Icon-maskable-192.png
│ └── Icon-maskable-512.png
│ ├── index.html
│ └── manifest.json
├── lib
├── dashboard.dart
└── src
│ ├── controller
│ ├── dashboard_controller.dart
│ └── dashboard_item_storage.dart
│ ├── dashboard_base.dart
│ ├── edit_mode
│ ├── edit_mode_background_style.dart
│ ├── edit_mode_painter.dart
│ └── edit_mode_settings.dart
│ ├── exceptions
│ └── unbounded.dart
│ ├── models
│ ├── dashboard_item.dart
│ ├── item_current_layout.dart
│ ├── item_layout_data.dart
│ └── viewport_settings.dart
│ └── widgets
│ ├── animated_background_painter.dart
│ ├── dashboard.dart
│ ├── dashboard_item_widget.dart
│ ├── dashboard_stack.dart
│ ├── grid_builder.dart
│ └── style.dart
├── pubspec.yaml
└── test
└── todo_test.dart
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
25 | /pubspec.lock
26 | **/doc/api/
27 | .dart_tool/
28 | .packages
29 | build/
30 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: e961ea6f41387b2e25cdfcea1b5cb4dbe2a6630e
8 | channel: master
9 |
10 | project_type: package
11 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "dashboard",
9 | "request": "launch",
10 | "type": "dart"
11 | },
12 | {
13 | "name": "dashboard (profile mode)",
14 | "request": "launch",
15 | "type": "dart",
16 | "flutterMode": "profile"
17 | },
18 | {
19 | "name": "dashboard (release mode)",
20 | "request": "launch",
21 | "type": "dart",
22 | "flutterMode": "release"
23 | },
24 | {
25 | "name": "example",
26 | "cwd": "example",
27 | "request": "launch",
28 | "type": "dart"
29 | },
30 | {
31 | "name": "example (profile mode)",
32 | "cwd": "example",
33 | "request": "launch",
34 | "type": "dart",
35 | "flutterMode": "profile"
36 | },
37 | {
38 | "name": "example (release mode)",
39 | "cwd": "example",
40 | "request": "launch",
41 | "type": "dart",
42 | "flutterMode": "release"
43 | }
44 | ]
45 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 0.0.3+1
2 |
3 | * Fixed issue : ``At initial start or refresh dashboard not scrollable``
4 | * Empty placeholder customizable for empty dashboard
5 | * Added ``mountToTop`` parameter to DashboardItemController.add and addAll method. (if false controller trying to add item to given position)
6 |
7 | ## 0.0.2+5
8 |
9 | * Mouse cursor bug fix
10 |
11 | ## 0.0.2+4
12 |
13 | * Cursor don't show on desktop fixed.
14 |
15 | ## 0.0.2+3
16 |
17 | * Switched to dart sdk 2.17.0
18 | * Switched to linter 2.0.1
19 | * Fix static analysis recommends
20 |
21 |
22 | ## 0.0.2+2
23 |
24 | * Analysis error fixed
25 |
26 |
27 | ## 0.0.2+1
28 |
29 | * Flutter version specified
30 |
31 |
32 | ## 0.0.2
33 |
34 | * Initial Release.
35 |
36 | ## 0.0.1
37 |
38 | * TODO: Describe initial release.
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | Dynamic dashboard widget that allows your users to create their own layouts. Rezise, move, indirect resize/move, auto re-layout are supported.
3 |
4 | ## Features
5 |
6 | [Try online demo](https://styledart.dev/#/dashboard)
7 |
8 | Dashboard can be thought of as a dynamic grid. Your users can create their own layouts, add new widgets or remove widgets.
9 |
10 | Layout divides the screen into horizontal slots according to the entered value. The aspect ratio of the slots also determine their height. Widgets called DashboardItem are placed in these slots. This layout can be changed later.
11 |
12 |
13 |
14 | ### Resize
15 |
16 |
17 |
18 | Dashboard Items can be resized both by long pressing on mobile and by holding and moving on desktop/browser.
19 |
20 | ### Move
21 |
22 |
23 |
24 | Dashboard Item locations can be changed by long pressing on mobile or by holding and pulling on desktop/browser.
25 |
26 | ### Slots Changes
27 |
28 | With dynamic slot count, you can re-layout window size changes.
29 |
30 |
>
31 |
32 |
33 | ### Storage Delegate
34 |
35 | The layout information of the users can be stored in the database or on the local disk.
36 |
37 | Define your storage delegate.
38 |
39 | ```dart
40 | class MyItemStorage extends DashboardItemStorageDelegate {
41 |
42 | @override
43 | FutureOr> getAllItems(int slotCount) {
44 | // return items from db.
45 | }
46 |
47 | @override
48 | FutureOr onItemsUpdated(
49 | List items, int slotCount) {
50 | // save new layouts to db.
51 | }
52 | //[...]
53 | }
54 | ```
55 |
56 | And use it.
57 |
58 | ```dart
59 | DashboardItemController.withDelegate(
60 | itemStorageDelegate: MyItemsStorage())
61 | ```
62 |
63 |
64 | ## Getting started
65 |
66 | ### Define Dashboard
67 |
68 | ```dart
69 | Dashboard(
70 | dashboardItemController: itemController,
71 | itemBuilder: (item) {
72 | //return widget
73 | },
74 | );
75 | ```
76 |
77 | ### Define Items
78 |
79 | Items can come from the database, or can be defined as fixed.
80 |
81 | Fixed:
82 | ```dart
83 | DashboardItemController(items: [
84 | DashboardItem(width: 2, height: 3, identifier: "id_1"),
85 | DashboardItem(
86 | startX: 3, startY: 4, width: 3, height: 1, identifier: "id_2"),
87 | ]);
88 | ```
89 |
90 | Or with delegate:
91 |
92 | ```dart
93 | DashboardItemController.withDelegate(
94 | itemStorageDelegate: MyItemStorage())
95 | ```
96 |
97 | [See example](https://pub.dev/packages/dashboard/example)
98 |
99 |
100 | ### Define Builder
101 |
102 | The Builder is invoked with a DashboardItem and returns a widget.
103 |
104 | ```dart
105 | Dashboard(
106 | dashboardItemController: itemController,
107 | itemBuilder: (item) {
108 | return Text(item.identifier);
109 | },
110 | );
111 | ```
112 |
113 | ## Parameters
114 |
115 | ### Item Style
116 |
117 | Each item is wrapped with a Material widget. You can enter the parameters of the Material widget with item style.
118 |
119 | All is optional.
120 | ```dart
121 | ItemStyle(
122 | color: Colors.red,
123 | borderRadius: BorderRadius.circular(10),
124 | shape: const RoundedRectangleBorder(),
125 | shadowColor: Colors.black,
126 | animationDuration: const Duration(milliseconds: 200),
127 | borderOnForeground: false,
128 | clipBehavior: Clip.antiAliasWithSaveLayer,
129 | elevation: 10,
130 | textStyle: const TextStyle(color: Colors.black),
131 | type: MaterialType.card
132 | );
133 | ```
134 |
135 | #### Slide
136 |
137 | Slide to top items initially. Auto relayout places items to top as possible.
138 |
139 | ```slideToTop: true```
140 |
141 | Before:
142 |
143 |
>
144 |
145 | After:
146 |
147 |
>
148 |
149 | #### Shrink
150 |
151 | Shrink items when re-layout or editing is possible.
152 |
153 |
154 | ### Edit Mode Settings
155 | See code comments for edit mode settings parameters.
156 |
157 | All is optional.
158 | ````dart
159 | EditModeSettings(
160 |
161 | // animation settings
162 | curve: Curves.easeInOutCirc,
163 | duration: const Duration(milliseconds: 200),
164 |
165 | // fill editing item actual size
166 | fillEditingBackground: true,
167 |
168 | // space that can be held to resize
169 | resizeCursorSide: 20,
170 |
171 | // draw lines for slots
172 | paintBackgroundLines: true,
173 |
174 | // shrink items when editing if possible and necessary
175 | shrinkOnMove: true,
176 |
177 | // long press to edit
178 | longPressEnabled: true,
179 |
180 | // pan to edit
181 | panEnabled: true,
182 |
183 | backgroundStyle: const EditModeBackgroundStyle(
184 | fillColor: Colors.red,
185 | lineWidth: 1.5,
186 | lineColor: Colors.black,
187 |
188 | // line by vertical space
189 | dualLineHorizontal: true,
190 |
191 | // line by horizontal space
192 | dualLineVertical: true));
193 | ````
194 |
195 | ### Storage Delegate
196 |
197 | [See example](https://pub.dev/packages/dashboard/example)
198 |
199 | ## Additional
200 |
201 | [](https://www.buymeacoffee.com/mehmetyaz)
202 |
203 | TODO
204 | - [ ] Define and fix animation bugs.
205 | - [ ] Check performance improvements.
206 | - [ ] Write tests.
207 | - [ ] Add more documentation.
208 | - [ ] Add more example.
209 | - [ ] Create Youtube video.
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:flutter_lints/flutter.yaml
2 |
3 | # Additional information about this file can be found at
4 | # https://dart.dev/guides/language/analysis-options
5 |
--------------------------------------------------------------------------------
/documentation/after.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/documentation/after.png
--------------------------------------------------------------------------------
/documentation/before.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/documentation/before.png
--------------------------------------------------------------------------------
/documentation/move.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/documentation/move.gif
--------------------------------------------------------------------------------
/documentation/re-layout.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/documentation/re-layout.gif
--------------------------------------------------------------------------------
/documentation/resize.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/documentation/resize.gif
--------------------------------------------------------------------------------
/documentation/slots.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/documentation/slots.png
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | linux/
13 | windows/
14 | macos/
15 |
16 | # IntelliJ related
17 | *.iml
18 | *.ipr
19 | *.iws
20 | .idea/
21 |
22 | # The .vscode folder contains launch configuration and tasks you configure in
23 | # VS Code which you may wish to be included in version control, so this line
24 | # is commented out by default.
25 | #.vscode/
26 |
27 | # Flutter/Dart/Pub related
28 | **/doc/api/
29 | **/ios/Flutter/.last_build_id
30 | .dart_tool/
31 | .flutter-plugins
32 | .flutter-plugins-dependencies
33 | .packages
34 | .pub-cache/
35 | .pub/
36 | /build/
37 |
38 | # Web related
39 | lib/generated_plugin_registrant.dart
40 |
41 | # Symbolication related
42 | app.*.symbols
43 |
44 | # Obfuscation related
45 | app.*.map.json
46 |
47 | # Android Studio will place build artifacts here
48 | /android/app/debug
49 | /android/app/profile
50 | /android/app/release
51 |
--------------------------------------------------------------------------------
/example/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: "483ac618b6a524dbee0d3729b8ba2fcf4bd544ba"
8 | channel: "master"
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: 483ac618b6a524dbee0d3729b8ba2fcf4bd544ba
17 | base_revision: 483ac618b6a524dbee0d3729b8ba2fcf4bd544ba
18 | - platform: android
19 | create_revision: 483ac618b6a524dbee0d3729b8ba2fcf4bd544ba
20 | base_revision: 483ac618b6a524dbee0d3729b8ba2fcf4bd544ba
21 | - platform: ios
22 | create_revision: 483ac618b6a524dbee0d3729b8ba2fcf4bd544ba
23 | base_revision: 483ac618b6a524dbee0d3729b8ba2fcf4bd544ba
24 | - platform: linux
25 | create_revision: 483ac618b6a524dbee0d3729b8ba2fcf4bd544ba
26 | base_revision: 483ac618b6a524dbee0d3729b8ba2fcf4bd544ba
27 | - platform: macos
28 | create_revision: 483ac618b6a524dbee0d3729b8ba2fcf4bd544ba
29 | base_revision: 483ac618b6a524dbee0d3729b8ba2fcf4bd544ba
30 | - platform: web
31 | create_revision: 483ac618b6a524dbee0d3729b8ba2fcf4bd544ba
32 | base_revision: 483ac618b6a524dbee0d3729b8ba2fcf4bd544ba
33 | - platform: windows
34 | create_revision: 483ac618b6a524dbee0d3729b8ba2fcf4bd544ba
35 | base_revision: 483ac618b6a524dbee0d3729b8ba2fcf4bd544ba
36 |
37 | # User provided section
38 |
39 | # List of Local paths (relative to this file) that should be
40 | # ignored by the migrate tool.
41 | #
42 | # Files that are not part of the templates will be ignored by default.
43 | unmanaged_files:
44 | - 'lib/main.dart'
45 | - 'ios/Runner.xcodeproj/project.pbxproj'
46 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # example
2 |
3 | A new Flutter project.
4 |
5 | ## Getting Started
6 |
7 | This project is a starting point for a Flutter application.
8 |
9 | A few resources to get you started if this is your first Flutter project:
10 |
11 | - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
12 | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
13 |
14 | For help getting started with Flutter development, view the
15 | [online documentation](https://docs.flutter.dev/), which offers tutorials,
16 | samples, guidance on mobile development, and a full API reference.
17 |
--------------------------------------------------------------------------------
/example/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at
17 | # https://dart-lang.github.io/linter/lints/index.html.
18 | #
19 | # Instead of disabling a lint rule for the entire project in the
20 | # section below, it can also be suppressed for a single line of code
21 | # or a specific dart file by using the `// ignore: name_of_lint` and
22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
23 | # producing the lint.
24 | rules:
25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
27 |
28 | # Additional information about this file can be found at
29 | # https://dart.dev/guides/language/analysis-options
30 |
--------------------------------------------------------------------------------
/example/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/example/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | flutterVersionCode = '1'
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | flutterVersionName = '1.0'
22 | }
23 |
24 | apply plugin: 'com.android.application'
25 | apply plugin: 'kotlin-android'
26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
27 |
28 | android {
29 | namespace "com.example.example"
30 | compileSdkVersion flutter.compileSdkVersion
31 | ndkVersion flutter.ndkVersion
32 |
33 | compileOptions {
34 | sourceCompatibility JavaVersion.VERSION_1_8
35 | targetCompatibility JavaVersion.VERSION_1_8
36 | }
37 |
38 | defaultConfig {
39 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
40 | applicationId "com.example.example"
41 | minSdkVersion flutter.minSdkVersion
42 | targetSdkVersion flutter.targetSdkVersion
43 | versionCode flutterVersionCode.toInteger()
44 | versionName flutterVersionName
45 | }
46 |
47 | buildTypes {
48 | release {
49 | // TODO: Add your own signing config for the release build.
50 | // Signing with the debug keys for now, so `flutter run --release` works.
51 | signingConfig signingConfigs.debug
52 | }
53 | }
54 | }
55 |
56 | flutter {
57 | source '../..'
58 | }
59 |
--------------------------------------------------------------------------------
/example/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
15 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
30 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/example/example/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.example;
2 |
3 | import io.flutter.embedding.android.FlutterActivity;
4 |
5 | public class MainActivity extends FlutterActivity {
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/example/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.8.22'
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:8.0.0'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | mavenCentral()
18 | }
19 | // This code is where all the magic happens and fixes the error.
20 | subprojects {
21 | afterEvaluate { project ->
22 | if (project.hasProperty('android')) {
23 | project.android {
24 | if (namespace == null) {
25 | namespace project.group
26 | }
27 | }
28 | }
29 | }
30 | }
31 | // This code is where all the magic happens and fixes the error.
32 | }
33 |
34 | rootProject.buildDir = '../build'
35 | subprojects {
36 | project.buildDir = "${rootProject.buildDir}/${project.name}"
37 | }
38 | subprojects {
39 | project.evaluationDependsOn(':app')
40 | }
41 |
42 | tasks.register("clean", Delete) {
43 | delete rootProject.buildDir
44 | }
45 |
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip
6 |
--------------------------------------------------------------------------------
/example/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
4 | def properties = new Properties()
5 |
6 | assert localPropertiesFile.exists()
7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
8 |
9 | def flutterSdkPath = properties.getProperty("flutter.sdk")
10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
12 |
--------------------------------------------------------------------------------
/example/assets/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/assets/github.png
--------------------------------------------------------------------------------
/example/assets/img.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/assets/img.png
--------------------------------------------------------------------------------
/example/assets/linkedin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/assets/linkedin.png
--------------------------------------------------------------------------------
/example/assets/pub_dev.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/assets/pub_dev.png
--------------------------------------------------------------------------------
/example/assets/twitter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/assets/twitter.png
--------------------------------------------------------------------------------
/example/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/example/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 11.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '12.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | use_frameworks!
32 | use_modular_headers!
33 |
34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35 | target 'RunnerTests' do
36 | inherit! :search_paths
37 | end
38 | end
39 |
40 | post_install do |installer|
41 | installer.pods_project.targets.each do |target|
42 | flutter_additional_ios_build_settings(target)
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | 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.
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | Example
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | example
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | $(FLUTTER_BUILD_NUMBER)
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIMainStoryboardFile
30 | Main
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 | UIViewControllerBasedStatusBarAppearance
45 |
46 | CADisableMinimumFrameDurationOnPhone
47 |
48 | UIApplicationSupportsIndirectInputEvents
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/example/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/example/lib/add_dialog.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_colorpicker/flutter_colorpicker.dart';
3 |
4 | class AddDialog extends StatefulWidget {
5 | const AddDialog({super.key});
6 |
7 | @override
8 | State createState() => _AddDialogState();
9 | }
10 |
11 | class _AddDialogState extends State {
12 | int minW = 1, minH = 1, w = 1, h = 1;
13 |
14 | int? maxW, maxH;
15 |
16 | List values = [
17 | 1, //w 0
18 | 1, //h 1
19 | 1, //minW 2
20 | 1, //minH 3
21 | 0, //maxW 4
22 | 0, //maxH 5
23 | // 6
24 | ];
25 |
26 | Color color = Colors.red;
27 |
28 | @override
29 | Widget build(BuildContext context) {
30 | return Dialog(
31 | child: SizedBox(
32 | width: 600,
33 | child: SingleChildScrollView(
34 | child: Padding(
35 | padding: const EdgeInsets.all(8.0),
36 | child: Column(
37 | children: [
38 | const Text("Add item with:"),
39 | drop("Width", false, 0, false),
40 | drop("Height", false, 1, false),
41 | drop("Minimum Width", false, 2, true),
42 | drop("Minimum Height", false, 3, true),
43 | drop("Maximum Width", true, 4, false),
44 | drop("Maximum Height", true, 5, false),
45 | const SizedBox(
46 | height: 10,
47 | ),
48 | Row(
49 | crossAxisAlignment: CrossAxisAlignment.center,
50 | children: [
51 | const Text("Color: "),
52 | Expanded(
53 | child: Container(
54 | alignment: Alignment.center,
55 | height: 200,
56 | width: double.infinity,
57 | padding: const EdgeInsets.only(left: 10, right: 10),
58 | child: BlockPicker(
59 | pickerColor: color,
60 | onColorChanged: (c) {
61 | setState(() {
62 | color = c;
63 | });
64 | }),
65 | ),
66 | )
67 | ],
68 | ),
69 | const SizedBox(
70 | height: 20,
71 | ),
72 | ElevatedButton(
73 | onPressed: () {
74 | if (values[0] < values[2]) {
75 | ScaffoldMessenger.of(context).showSnackBar(
76 | const SnackBar(
77 | content:
78 | Text("width >= minWidth is not true.")));
79 | return;
80 | }
81 | if (values[1] < values[3]) {
82 | ScaffoldMessenger.of(context).showSnackBar(
83 | const SnackBar(
84 | content:
85 | Text("height >= minHeight is not true.")));
86 | return;
87 | }
88 | if (values[4] != 0 && values[0] > values[4]) {
89 | ScaffoldMessenger.of(context).showSnackBar(
90 | const SnackBar(
91 | content:
92 | Text("width <= maxWidth is not true.")));
93 | return;
94 | }
95 | if (values[5] != 0 && values[1] > values[5]) {
96 | ScaffoldMessenger.of(context).showSnackBar(
97 | const SnackBar(
98 | content:
99 | Text("height <= maxHeight is not true.")));
100 | return;
101 | }
102 |
103 | Navigator.pop(context, values..add(color));
104 | },
105 | child: const Padding(
106 | padding:
107 | EdgeInsets.symmetric(vertical: 8, horizontal: 30),
108 | child: Text("Add"),
109 | )),
110 | const SizedBox(
111 | height: 20,
112 | ),
113 | ],
114 | ),
115 | ),
116 | ),
117 | ),
118 | );
119 | }
120 |
121 | Widget drop(String name, bool nullable, int index, bool bounded) {
122 | return Row(
123 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
124 | children: [
125 | Text("$name: "),
126 | SizedBox(
127 | width: 100,
128 | child: DropdownButton(
129 | underline: const SizedBox(),
130 | alignment: Alignment.centerRight,
131 | items: [
132 | if (nullable) 0,
133 | 1,
134 | 2,
135 | 3,
136 | 4,
137 | if (!bounded) ...[5, 6, 7, 8, 9]
138 | ]
139 | .map((e) => DropdownMenuItem(
140 | alignment: Alignment.centerRight,
141 | value: e,
142 | child: Text(
143 | (e == 0 ? "null" : e).toString(),
144 | textAlign: TextAlign.right,
145 | )))
146 | .toList(),
147 | value: values[index],
148 | onChanged: (v) {
149 | setState(() {
150 | values[index] = v ?? 1;
151 | });
152 | }),
153 | ),
154 | ],
155 | );
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/example/lib/data_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:auto_size_text/auto_size_text.dart';
2 | import 'package:dashboard/dashboard.dart';
3 | import 'package:example/storage.dart';
4 | import 'package:flutter/gestures.dart';
5 | import 'package:flutter/material.dart';
6 | import 'package:url_launcher/url_launcher_string.dart';
7 |
8 | const Color blue = Color(0xFF4285F4);
9 | const Color red = Color(0xFFEA4335);
10 | const Color yellow = Color(0xFFFBBC05);
11 | const Color green = Color(0xFF34A853);
12 |
13 | class DataWidget extends StatelessWidget {
14 | DataWidget({super.key, required this.item});
15 |
16 | final ColoredDashboardItem item;
17 |
18 | final Map _map = {
19 | "welcome": (l) => const WelcomeWidget(),
20 | "resize": (l) => AdviceResize(size: l.layoutData.width),
21 | "description": (l) => const BasicDescription(),
22 | "transform": (l) => const TransformAdvice(),
23 | "add": (l) => const AddAdvice(),
24 | "buy_mee": (l) => const BuyMee(),
25 | "delete": (l) => const ClearAdvice(),
26 | "refresh": (l) => const DefaultAdvice(),
27 | "info": (l) => InfoAdvice(layout: l.layoutData),
28 | "github": (l) => const Github(),
29 | "twitter": (l) => const Twitter(),
30 | "linkedin": (l) => const LinkedIn(),
31 | "pub": (l) => const Pub(),
32 | };
33 |
34 | @override
35 | Widget build(BuildContext context) {
36 | return _map[item.data]!(item);
37 | }
38 | }
39 |
40 | class Pub extends StatelessWidget {
41 | const Pub({super.key});
42 |
43 | @override
44 | Widget build(BuildContext context) {
45 | return GestureDetector(
46 | onTap: () {
47 | launchUrlString("https://pub.dev/packages/dashboard");
48 | },
49 | child: Container(
50 | color: Colors.white,
51 | child: Container(
52 | alignment: Alignment.center,
53 | decoration: const BoxDecoration(
54 | image: DecorationImage(
55 | fit: BoxFit.contain,
56 | image: AssetImage("assets/pub_dev.png")))),
57 | ),
58 | );
59 | }
60 | }
61 |
62 | class LinkedIn extends StatelessWidget {
63 | const LinkedIn({super.key});
64 |
65 | @override
66 | Widget build(BuildContext context) {
67 | return GestureDetector(
68 | onTap: () {
69 | launchUrlString("https://www.linkedin.com/in/mehmetyaz/");
70 | },
71 | child: Container(
72 | color: const Color(0xFF0A66C2),
73 | child: Row(
74 | children: [
75 | const Expanded(
76 | child: Padding(
77 | padding: EdgeInsets.all(8.0),
78 | child: Text("Connect Me!", style: TextStyle(color: Colors.white)),
79 | )),
80 | Expanded(
81 | child: Container(
82 | alignment: Alignment.center,
83 | decoration: const BoxDecoration(
84 | image: DecorationImage(
85 | fit: BoxFit.contain,
86 | image: AssetImage("assets/linkedin.png")))),
87 | ),
88 | ],
89 | ),
90 | ),
91 | );
92 | }
93 | }
94 |
95 | class Twitter extends StatelessWidget {
96 | const Twitter({super.key});
97 |
98 | @override
99 | Widget build(BuildContext context) {
100 | return GestureDetector(
101 | onTap: () {
102 | launchUrlString("https://twitter.com/smehmetyaz");
103 | },
104 | child: Container(
105 | color: const Color(0xFF1DA0F1),
106 | child: Row(
107 | children: [
108 | const Expanded(
109 | child: Padding(
110 | padding: EdgeInsets.all(8.0),
111 | child: Text("Follow Me!", style: TextStyle(color: Colors.white)),
112 | )),
113 | Expanded(
114 | child: Container(
115 | alignment: Alignment.center,
116 | decoration: const BoxDecoration(
117 | image: DecorationImage(
118 | fit: BoxFit.contain,
119 | image: AssetImage("assets/twitter.png")))),
120 | ),
121 | ],
122 | ),
123 | ),
124 | );
125 | }
126 | }
127 |
128 | class Github extends StatelessWidget {
129 | const Github({super.key});
130 |
131 | @override
132 | Widget build(BuildContext context) {
133 | return GestureDetector(
134 | onTap: () {
135 | launchUrlString("https://github.com/Mehmetyaz/dashboard");
136 | },
137 | child: Container(
138 | color: Colors.white,
139 | child: Row(
140 | children: [
141 | const Expanded(
142 | child: Padding(
143 | padding: EdgeInsets.all(8.0),
144 | child: Text(
145 | "Create Issue!",
146 | style: TextStyle(color: Colors.black),
147 | ),
148 | )),
149 | Expanded(
150 | child: Container(
151 | margin: const EdgeInsets.all(5),
152 | alignment: Alignment.center,
153 | decoration: const BoxDecoration(
154 | image: DecorationImage(
155 | fit: BoxFit.contain,
156 | image: AssetImage("assets/github.png")))),
157 | ),
158 | ],
159 | ),
160 | ),
161 | );
162 | }
163 | }
164 |
165 | class BuyMee extends StatelessWidget {
166 | const BuyMee({super.key});
167 |
168 | @override
169 | Widget build(BuildContext context) {
170 | return GestureDetector(
171 | onTap: () {
172 | launchUrlString("https://www.buymeacoffee.com/mehmetyaz");
173 | },
174 | child: Container(
175 | alignment: Alignment.center,
176 | decoration: const BoxDecoration(
177 | image: DecorationImage(
178 | fit: BoxFit.cover, image: AssetImage("assets/img.png")))),
179 | );
180 | }
181 | }
182 |
183 | class InfoAdvice extends StatelessWidget {
184 | const InfoAdvice({super.key, required this.layout});
185 |
186 | final ItemLayout layout;
187 |
188 | @override
189 | Widget build(BuildContext context) {
190 | return Container(
191 | color: blue,
192 | alignment: Alignment.center,
193 | padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
194 | child: Column(
195 | children: [
196 | const Text("Example dimensions and locations. (showing this)",
197 | style: TextStyle(color: Colors.white)),
198 | Expanded(
199 | child: Container(
200 | alignment: Alignment.center,
201 | child: DataTable(
202 |
203 | headingRowHeight: 25,
204 | border: const TableBorder(
205 | horizontalInside: BorderSide(color: Colors.white)),
206 | headingTextStyle: const TextStyle(color: Colors.white),
207 | dataTextStyle: const TextStyle(color: Colors.white),
208 | columns: const [
209 | DataColumn(label: Text("startX")),
210 | DataColumn(label: Text("startY")),
211 | DataColumn(label: Text("width")),
212 | DataColumn(label: Text("height"))
213 | ],
214 | rows: [
215 | DataRow(cells: [
216 | DataCell(Text(layout.startX.toString())),
217 | DataCell(Text(layout.startY.toString())),
218 | DataCell(Text(layout.width.toString())),
219 | DataCell(Text(layout.height.toString())),
220 | ])
221 | ]),
222 | ),
223 | ),
224 | ],
225 | ));
226 | }
227 | }
228 |
229 | class DefaultAdvice extends StatelessWidget {
230 | const DefaultAdvice({super.key});
231 |
232 | @override
233 | Widget build(BuildContext context) {
234 | return Container(
235 | color: yellow,
236 | alignment: Alignment.center,
237 | padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
238 | child: const Row(
239 | mainAxisAlignment: MainAxisAlignment.spaceAround,
240 | crossAxisAlignment: CrossAxisAlignment.center,
241 | children: [
242 | Icon(
243 | Icons.refresh,
244 | size: 30,
245 | color: Colors.white,
246 | ),
247 | Expanded(
248 | child: Text(
249 | "Your layout changes saved locally."
250 | " Set default with this button.",
251 | textAlign: TextAlign.center,
252 | style: TextStyle(color: Colors.white),
253 | ),
254 | )
255 | ],
256 | ));
257 | }
258 | }
259 |
260 | class ClearAdvice extends StatelessWidget {
261 | const ClearAdvice({super.key});
262 |
263 | @override
264 | Widget build(BuildContext context) {
265 | return Container(
266 | color: green,
267 | alignment: Alignment.center,
268 | padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
269 | child: const Row(
270 | mainAxisAlignment: MainAxisAlignment.spaceAround,
271 | crossAxisAlignment: CrossAxisAlignment.center,
272 | children: [
273 | Icon(
274 | Icons.delete,
275 | size: 30,
276 | color: Colors.white,
277 | ),
278 | Text(
279 | "Delete all widgets.",
280 | textAlign: TextAlign.center,
281 | style: TextStyle(color: Colors.white),
282 | )
283 | ],
284 | ));
285 | }
286 | }
287 |
288 | class AddAdvice extends StatelessWidget {
289 | const AddAdvice({super.key});
290 |
291 | @override
292 | Widget build(BuildContext context) {
293 | return Container(
294 | color: blue,
295 | alignment: Alignment.center,
296 | padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
297 | child: const Column(
298 | mainAxisAlignment: MainAxisAlignment.spaceAround,
299 | crossAxisAlignment: CrossAxisAlignment.center,
300 | children: [
301 | Icon(
302 | Icons.add,
303 | size: 30,
304 | color: Colors.white,
305 | ),
306 | Text(
307 | "Add own colored widget with custom sizes.",
308 | textAlign: TextAlign.center,
309 | style: TextStyle(color: Colors.white),
310 | )
311 | ],
312 | ));
313 | }
314 | }
315 |
316 | class TransformAdvice extends StatelessWidget {
317 | const TransformAdvice({super.key});
318 |
319 | @override
320 | Widget build(BuildContext context) {
321 | return Container(
322 | color: red,
323 | alignment: Alignment.center,
324 | padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
325 | child: const Column(
326 | mainAxisAlignment: MainAxisAlignment.spaceAround,
327 | crossAxisAlignment: CrossAxisAlignment.center,
328 | children: [
329 | Text(
330 | "Users can move widgets.",
331 | style: TextStyle(color: Colors.white, fontSize: 13),
332 | textAlign: TextAlign.center,
333 | ),
334 | Text(
335 | "To try moving, hold (or long press) the widget and move.",
336 | textAlign: TextAlign.center,
337 | style: TextStyle(color: Colors.white, fontSize: 13),
338 | ),
339 | Row(
340 | children: [
341 | Expanded(
342 | child: Text(
343 | "While moving, it shrinks if possible according to the "
344 | "minimum width and height values.\n(This min w: 2 , h: 2)",
345 | textAlign: TextAlign.center,
346 | style: TextStyle(color: Colors.white, fontSize: 13),
347 | ),
348 | ),
349 | /*
350 | Icon(
351 | Icons.arrow_right_alt,
352 | color: Colors.white,
353 | size: 30,
354 | )
355 | */
356 | ],
357 | ),
358 | ],
359 | ));
360 | }
361 | }
362 |
363 | class WelcomeWidget extends StatelessWidget {
364 | const WelcomeWidget({super.key});
365 |
366 | @override
367 | Widget build(BuildContext context) {
368 | return Container(
369 | color: red,
370 | alignment: Alignment.center,
371 | child: Column(
372 | mainAxisAlignment: MainAxisAlignment.spaceAround,
373 | children: [
374 | RichText(
375 | text: TextSpan(
376 | style: const TextStyle(color: Colors.white, fontSize: 20),
377 | children: [
378 | const TextSpan(text: "Welcome to "),
379 | TextSpan(
380 | recognizer: TapGestureRecognizer()
381 | ..onTap = () {
382 | launchUrlString("https://pub.dev/packages/dashboard");
383 | },
384 | text: "dashboard",
385 | style: const TextStyle(
386 | decoration: TextDecoration.underline)),
387 | const TextSpan(text: " online demo!"),
388 | ])),
389 | ],
390 | ));
391 | }
392 | }
393 |
394 | class BasicDescription extends StatelessWidget {
395 | const BasicDescription({super.key});
396 |
397 | @override
398 | Widget build(BuildContext context) {
399 | return Container(
400 | color: yellow,
401 | alignment: Alignment.center,
402 | padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
403 | child: const Column(
404 | mainAxisAlignment: MainAxisAlignment.spaceAround,
405 | children: [
406 | AutoSizeText(
407 | "Each widget on the screen is called \"DashboardItem\"",
408 | maxLines: 4,
409 | style: TextStyle(color: Colors.white, fontSize: 13),
410 | textAlign: TextAlign.center),
411 | AutoSizeText("Each has a location and dimensions by slots.",
412 | maxLines: 3,
413 | style: TextStyle(color: Colors.white, fontSize: 13),
414 | textAlign: TextAlign.center),
415 | Row(
416 | children: [
417 | Expanded(
418 | child: AutoSizeText(
419 | "You can switch to edit mode to see these slots.",
420 | maxLines: 4,
421 | style: TextStyle(color: Colors.white, fontSize: 13),
422 | textAlign: TextAlign.center),
423 | ),
424 | SizedBox(
425 | width: 8,
426 | ),
427 | Column(
428 | children: [
429 | Text("Tap: ",
430 | style: TextStyle(color: Colors.white, fontSize: 10)),
431 | Icon(Icons.edit, color: Colors.white),
432 | ],
433 | )
434 | ],
435 | ),
436 | ],
437 | ));
438 | }
439 | }
440 |
441 | class AdviceResize extends StatelessWidget {
442 | const AdviceResize({super.key, required this.size});
443 |
444 | final int size;
445 |
446 | @override
447 | Widget build(BuildContext context) {
448 | return Container(
449 | color: green,
450 | alignment: Alignment.center,
451 | child: Row(
452 | children: [
453 | Container(
454 | margin: const EdgeInsets.only(left: 5),
455 | height: double.infinity,
456 | width: 1,
457 | color: Colors.white,
458 | ),
459 | const Expanded(
460 | child: Column(
461 | crossAxisAlignment: CrossAxisAlignment.center,
462 | mainAxisAlignment: MainAxisAlignment.spaceAround,
463 | children: [
464 | AutoSizeText("Users can resize widgets.",
465 | maxLines: 2,
466 | style: TextStyle(color: Colors.white, fontSize: 13),
467 | textAlign: TextAlign.center),
468 | AutoSizeText(
469 | "To try resizing, hold (or long press) the line on the left"
470 | " and drag it to the left.",
471 | maxLines: 5,
472 | style: TextStyle(color: Colors.white, fontSize: 13),
473 | textAlign: TextAlign.center),
474 | AutoSizeText("Don't forget switch to edit mode.",
475 | maxLines: 3,
476 | style: TextStyle(color: Colors.white, fontSize: 13),
477 | textAlign: TextAlign.center),
478 | ],
479 | ))
480 | ],
481 | ));
482 | }
483 | }
484 |
--------------------------------------------------------------------------------
/example/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:example/screens/dashboard_page.dart';
4 | import 'package:example/screens/main_page.dart';
5 | import 'package:flutter/material.dart';
6 |
7 | ///
8 | void main() {
9 | ///
10 | runApp(const MyApp());
11 | }
12 |
13 | ///
14 | class MyApp extends StatefulWidget {
15 | ///
16 | const MyApp({super.key});
17 |
18 | ///
19 | @override
20 | State createState() => _MyAppState();
21 | }
22 |
23 | class _MyAppState extends State {
24 | ///
25 | Color getRandomColor() {
26 | var r = Random();
27 | return Color.fromRGBO(r.nextInt(256), r.nextInt(256), r.nextInt(256), 1);
28 | }
29 |
30 | @override
31 | Widget build(BuildContext context) {
32 | return MaterialApp(
33 | debugShowCheckedModeBanner: false,
34 | title: 'Dashboard demo',
35 | onGenerateInitialRoutes: (r) {
36 | return r == "/dashboard"
37 | ? [
38 | MaterialPageRoute(builder: (c) {
39 | return const DashboardPage();
40 | })
41 | ]
42 | : [
43 | MaterialPageRoute(builder: (c) {
44 | return const MainPage();
45 | })
46 | ];
47 | },
48 | initialRoute: "/",
49 | routes: {
50 | "/": (c) => const MainPage(),
51 | "/dashboard": (c) => const DashboardPage()
52 | },
53 | theme: ThemeData(
54 | primarySwatch: Colors.blue,
55 | ),
56 | );
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/example/lib/screens/dashboard_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:dashboard/dashboard.dart';
4 | import 'package:example/add_dialog.dart';
5 | import 'package:example/data_widget.dart';
6 | import 'package:example/storage.dart';
7 | import 'package:flutter/material.dart';
8 |
9 | class MySlotBackground extends SlotBackgroundBuilder {
10 | @override
11 | Widget? buildBackground(BuildContext context, ColoredDashboardItem? item,
12 | int x, int y, bool editing) {
13 | if (item != null) {
14 | return Container(
15 | decoration: BoxDecoration(
16 | color: Colors.red.withOpacity(0.5),
17 | borderRadius: BorderRadius.circular(10)),
18 | );
19 | }
20 |
21 | return null;
22 | }
23 | }
24 |
25 | class DashboardPage extends StatefulWidget {
26 | const DashboardPage({super.key});
27 |
28 | @override
29 | State createState() => _DashboardPageState();
30 | }
31 |
32 | class _DashboardPageState extends State {
33 | final ScrollController scrollController = ScrollController();
34 |
35 | late var _itemController =
36 | DashboardItemController.withDelegate(
37 | itemStorageDelegate: storage);
38 |
39 | bool refreshing = false;
40 |
41 | var storage = MyItemStorage();
42 |
43 | //var dummyItemController =
44 | // DashboardItemController(items: []);
45 |
46 | DashboardItemController get itemController =>
47 | _itemController;
48 |
49 | int? slot;
50 |
51 | setSlot() {
52 | var w = MediaQuery.of(context).size.width;
53 | setState(() {
54 | slot = w > 600
55 | ? w > 900
56 | ? 8
57 | : 6
58 | : 4;
59 | });
60 | }
61 |
62 | List d = [];
63 |
64 | @override
65 | Widget build(BuildContext context) {
66 | var w = MediaQuery.of(context).size.width;
67 | slot = w > 600
68 | ? w > 900
69 | ? 8
70 | : 6
71 | : 4;
72 | return Scaffold(
73 | appBar: AppBar(
74 | backgroundColor: const Color(0xFF4285F4),
75 | automaticallyImplyLeading: false,
76 | actions: [
77 | IconButton(
78 | onPressed: () async {
79 | await storage.clear();
80 | setState(() {
81 | refreshing = true;
82 | });
83 | storage = MyItemStorage();
84 | _itemController = DashboardItemController.withDelegate(
85 | itemStorageDelegate: storage);
86 | Future.delayed(const Duration(milliseconds: 150)).then((value) {
87 | setState(() {
88 | refreshing = false;
89 | });
90 | });
91 | },
92 | icon: const Icon(Icons.refresh)),
93 | IconButton(
94 | onPressed: () {
95 | itemController.clear();
96 | },
97 | icon: const Icon(Icons.delete)),
98 | IconButton(
99 | onPressed: () {
100 | add(context);
101 | },
102 | icon: const Icon(Icons.add)),
103 | IconButton(
104 | onPressed: () {
105 | itemController.isEditing = !itemController.isEditing;
106 | setState(() {});
107 | },
108 | icon: !itemController.isEditing
109 | ? const Icon(Icons.edit)
110 | : const Icon(Icons.check)),
111 | ],
112 | ),
113 | body: SafeArea(
114 | child: refreshing
115 | ? const Center(
116 | child: CircularProgressIndicator(),
117 | )
118 | : Dashboard(
119 | scrollController: scrollController,
120 | shrinkToPlace: false,
121 | slideToTop: true,
122 | absorbPointer: false,
123 | slotBackgroundBuilder: SlotBackgroundBuilder.withFunction(
124 | (context, item, x, y, editing) {
125 | return Container(
126 | decoration: BoxDecoration(
127 | border: Border.all(color: Colors.black12, width: 0.5),
128 | borderRadius: BorderRadius.circular(10),
129 | ),
130 | );
131 | }),
132 | padding: const EdgeInsets.all(8),
133 | horizontalSpace: 8,
134 | verticalSpace: 8,
135 | slotAspectRatio: 1,
136 | animateEverytime: true,
137 | dashboardItemController: itemController,
138 | slotCount: slot!,
139 | errorPlaceholder: (e, s) {
140 | return Text("$e , $s");
141 | },
142 | emptyPlaceholder: const Center(child: Text("Empty")),
143 | itemStyle: ItemStyle(
144 | color: Colors.transparent,
145 | clipBehavior: Clip.antiAliasWithSaveLayer,
146 | elevation: 5,
147 | shape: RoundedRectangleBorder(
148 | borderRadius: BorderRadius.circular(15))),
149 | physics: const RangeMaintainingScrollPhysics(),
150 | editModeSettings: EditModeSettings(
151 | draggableOutside: false,
152 | paintBackgroundLines: false,
153 | autoScroll: true,
154 | resizeCursorSide: 15,
155 | curve: Curves.easeOut,
156 | duration: const Duration(milliseconds: 300),
157 | backgroundStyle: const EditModeBackgroundStyle(
158 | lineColor: Colors.black38,
159 | lineWidth: 0.5,
160 | dualLineHorizontal: false,
161 | dualLineVertical: false)),
162 | itemBuilder: (ColoredDashboardItem item) {
163 | var layout = item.layoutData;
164 |
165 | if (item.data != null) {
166 | return DataWidget(
167 | item: item,
168 | );
169 | }
170 |
171 | return LayoutBuilder(builder: (_, c) {
172 | return Stack(
173 | children: [
174 | Container(
175 | alignment: Alignment.center,
176 | padding: const EdgeInsets.all(10),
177 | decoration: BoxDecoration(
178 | color: item.color,
179 | borderRadius: BorderRadius.circular(10)),
180 | child: SizedBox(
181 | width: double.infinity,
182 | height: double.infinity,
183 | child: Text(
184 | "ID: ${item.identifier}\n${[
185 | "x: ${layout.startX}",
186 | "y: ${layout.startY}",
187 | "w: ${layout.width}",
188 | "h: ${layout.height}",
189 | if (layout.minWidth != 1)
190 | "minW: ${layout.minWidth}",
191 | if (layout.minHeight != 1)
192 | "minH: ${layout.minHeight}",
193 | if (layout.maxWidth != null)
194 | "maxW: ${layout.maxWidth}",
195 | if (layout.maxHeight != null)
196 | "maxH : ${layout.maxHeight}"
197 | ].join("\n")}",
198 | style: const TextStyle(color: Colors.white),
199 | )),
200 | ),
201 | if (itemController.isEditing)
202 | Positioned(
203 | right: 5,
204 | top: 5,
205 | child: InkResponse(
206 | radius: 20,
207 | onTap: () {
208 | itemController.delete(item.identifier);
209 | },
210 | child: const Icon(
211 | Icons.clear,
212 | color: Colors.white,
213 | size: 20,
214 | )))
215 | ],
216 | );
217 | });
218 | },
219 | ),
220 | ),
221 | );
222 | }
223 |
224 | Future add(BuildContext context) async {
225 | var res = await showDialog(
226 | context: context,
227 | builder: (c) {
228 | return const AddDialog();
229 | });
230 |
231 | if (res != null) {
232 | itemController.add(
233 | ColoredDashboardItem(
234 | color: res[6],
235 | width: res[0],
236 | height: res[1],
237 | startX: 0,
238 | startY: 0,
239 | identifier: (Random().nextInt(100000) + 4).toString(),
240 | minWidth: res[2],
241 | minHeight: res[3],
242 | maxWidth: res[4] == 0 ? null : res[4],
243 | maxHeight: res[5] == 0 ? null : res[5]),
244 | mountToTop: false);
245 | }
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/example/lib/screens/main_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class MainPage extends StatefulWidget {
4 | const MainPage({super.key});
5 |
6 | @override
7 | State createState() => _MainPageState();
8 | }
9 |
10 | class _MainPageState extends State {
11 | @override
12 | Widget build(BuildContext context) {
13 | return Scaffold(
14 | body: Column(
15 | mainAxisAlignment: MainAxisAlignment.center,
16 | children: [
17 | const Text("style_dart framework documentation coming soon...",
18 | textAlign: TextAlign.center),
19 | const SizedBox(
20 | height: 20,
21 | ),
22 | Container(
23 | alignment: Alignment.center,
24 | child: ElevatedButton(
25 | onPressed: () {
26 | Navigator.pushNamed(context, "/dashboard");
27 | },
28 | child: const Padding(
29 | padding: EdgeInsets.symmetric(vertical: 8, horizontal: 30),
30 | child: Text("Try dashboard demo"),
31 | )),
32 | )
33 | ],
34 | ),
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/example/lib/storage.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:convert';
3 |
4 | import 'package:dashboard/dashboard.dart';
5 | import 'package:flutter/material.dart';
6 | import 'package:shared_preferences/shared_preferences.dart';
7 |
8 | class ColoredDashboardItem extends DashboardItem {
9 | ColoredDashboardItem(
10 | {this.color,
11 | required super.width,
12 | required super.height,
13 | required super.identifier,
14 | this.data,
15 | super.minWidth,
16 | super.minHeight,
17 | super.maxHeight,
18 | super.maxWidth,
19 | super.startX,
20 | super.startY});
21 |
22 | ColoredDashboardItem.fromMap(Map map)
23 | : color = map["color"] != null ? Color(map["color"]) : null,
24 | data = map["data"],
25 | super.withLayout(map["item_id"], ItemLayout.fromMap(map["layout"]));
26 |
27 | Color? color;
28 |
29 | String? data;
30 |
31 | @override
32 | Map toMap() {
33 | var sup = super.toMap();
34 | if (color != null) {
35 | sup["color"] = color?.value;
36 | }
37 | if (data != null) {
38 | sup["data"] = data;
39 | }
40 | return sup;
41 | }
42 | }
43 |
44 | class MyItemStorage extends DashboardItemStorageDelegate {
45 | late SharedPreferences _preferences;
46 |
47 | final List _slotCounts = [4, 6, 8];
48 |
49 | final Map> _default = {
50 | 4: [
51 | ColoredDashboardItem(
52 | height: 2,
53 | width: 3,
54 | startX: 0,
55 | startY: 1,
56 | minHeight: 2,
57 | identifier: "1",
58 | data: "description",
59 | ),
60 | ColoredDashboardItem(
61 | startX: 3,
62 | startY: 1,
63 | minHeight: 2,
64 | height: 2,
65 | width: 1,
66 | identifier: "2",
67 | data: "resize"),
68 | ColoredDashboardItem(
69 | startX: 0,
70 | startY: 0,
71 | width: 4,
72 | height: 1,
73 | identifier: "3",
74 | minWidth: 3,
75 | data: "welcome"),
76 | ColoredDashboardItem(
77 | startX: 1,
78 | startY: 3,
79 | minWidth: 2,
80 | minHeight: 2,
81 | height: 2,
82 | width: 3,
83 | identifier: "4",
84 | data: "transform"),
85 | ColoredDashboardItem(
86 | startX: 0,
87 | startY: 3,
88 | minHeight: 2,
89 | height: 2,
90 | width: 1,
91 | identifier: "5",
92 | data: "add"),
93 | ColoredDashboardItem(
94 | minWidth: 2,
95 | maxWidth: 2,
96 | maxHeight: 1,
97 | height: 1,
98 | width: 2,
99 | startX: 2,
100 | startY: 7,
101 | identifier: "6",
102 | data: "buy_mee"),
103 | ColoredDashboardItem(
104 | minWidth: 2,
105 | height: 1,
106 | width: 2,
107 | startX: 2,
108 | startY: 5,
109 | identifier: "7",
110 | data: "delete"),
111 | ColoredDashboardItem(
112 | minWidth: 2,
113 | height: 1,
114 | width: 2,
115 | startX: 0,
116 | startY: 5,
117 | identifier: "8",
118 | data: "refresh"),
119 | ColoredDashboardItem(
120 | minWidth: 3,
121 | height: 1,
122 | width: 3,
123 | startX: 0,
124 | startY: 6,
125 | identifier: "9",
126 | data: "info"),
127 | ColoredDashboardItem(
128 | startX: 3,
129 | startY: 6,
130 | height: 1,
131 | width: 1,
132 | identifier: "13",
133 | data: "pub"),
134 | ColoredDashboardItem(
135 | height: 1, width: 2, identifier: "10", data: "github"),
136 | ColoredDashboardItem(
137 | height: 1, width: 2, identifier: "11", data: "twitter"),
138 | ColoredDashboardItem(
139 | height: 1, width: 2, identifier: "12", data: "linkedin")
140 | ],
141 | 6: [
142 | ColoredDashboardItem(
143 | height: 2,
144 | width: 3,
145 | startX: 0,
146 | startY: 0,
147 | minHeight: 2,
148 | identifier: "1",
149 | data: "description",
150 | ),
151 | ColoredDashboardItem(
152 | startX: 3,
153 | startY: 0,
154 | minHeight: 2,
155 | height: 2,
156 | width: 1,
157 | identifier: "2",
158 | data: "resize"),
159 | ColoredDashboardItem(
160 | startX: 0,
161 | startY: 2,
162 | width: 5,
163 | height: 1,
164 | identifier: "3",
165 | minWidth: 3,
166 | data: "welcome"),
167 | ColoredDashboardItem(
168 | startX: 4,
169 | startY: 0,
170 | minWidth: 2,
171 | minHeight: 2,
172 | height: 2,
173 | width: 2,
174 | identifier: "4",
175 | data: "transform"),
176 | ColoredDashboardItem(
177 | startX: 5,
178 | startY: 2,
179 | minHeight: 2,
180 | height: 2,
181 | width: 1,
182 | identifier: "5",
183 | data: "add"),
184 | ColoredDashboardItem(
185 | minWidth: 2,
186 | maxWidth: 2,
187 | maxHeight: 1,
188 | height: 1,
189 | width: 2,
190 | startX: 4,
191 | startY: 4,
192 | identifier: "6",
193 | data: "buy_mee"),
194 | ColoredDashboardItem(
195 | minWidth: 2,
196 | height: 1,
197 | width: 2,
198 | startX: 0,
199 | startY: 4,
200 | identifier: "7",
201 | data: "delete"),
202 | ColoredDashboardItem(
203 | minWidth: 2,
204 | height: 1,
205 | width: 2,
206 | startX: 2,
207 | startY: 4,
208 | identifier: "8",
209 | data: "refresh"),
210 | ColoredDashboardItem(
211 | minWidth: 4,
212 | height: 1,
213 | width: 4,
214 | startX: 0,
215 | startY: 3,
216 | identifier: "9",
217 | data: "info"),
218 | ColoredDashboardItem(
219 | startX: 4,
220 | startY: 3,
221 | height: 1,
222 | width: 1,
223 | identifier: "13",
224 | data: "pub"),
225 | ColoredDashboardItem(
226 | height: 1, width: 2, identifier: "10", data: "github"),
227 | ColoredDashboardItem(
228 | height: 1, width: 2, identifier: "11", data: "twitter"),
229 | ColoredDashboardItem(
230 | height: 1, width: 2, identifier: "12", data: "linkedin")
231 | ],
232 | 8: [
233 | ColoredDashboardItem(
234 | height: 2,
235 | width: 3,
236 | startX: 0,
237 | startY: 0,
238 | minHeight: 2,
239 | identifier: "1",
240 | data: "description",
241 | ),
242 | ColoredDashboardItem(
243 | startX: 3,
244 | startY: 0,
245 | minHeight: 2,
246 | height: 2,
247 | width: 2,
248 | identifier: "2",
249 | data: "resize"),
250 | ColoredDashboardItem(
251 | startX: 2,
252 | startY: 2,
253 | width: 4,
254 | height: 1,
255 | identifier: "3",
256 | minWidth: 3,
257 | data: "welcome"),
258 | ColoredDashboardItem(
259 | startX: 5,
260 | startY: 0,
261 | minWidth: 2,
262 | minHeight: 2,
263 | height: 2,
264 | width: 2,
265 | identifier: "4",
266 | data: "transform"),
267 | ColoredDashboardItem(
268 | startX: 7,
269 | startY: 0,
270 | minHeight: 2,
271 | height: 2,
272 | width: 1,
273 | identifier: "5",
274 | data: "add"),
275 | ColoredDashboardItem(
276 | minWidth: 2,
277 | maxWidth: 2,
278 | maxHeight: 1,
279 | height: 1,
280 | width: 2,
281 | startX: 2,
282 | startY: 4,
283 | identifier: "6",
284 | data: "buy_mee"),
285 | ColoredDashboardItem(
286 | minWidth: 2,
287 | height: 1,
288 | width: 2,
289 | startX: 0,
290 | startY: 2,
291 | identifier: "7",
292 | data: "delete"),
293 | ColoredDashboardItem(
294 | minWidth: 2,
295 | height: 1,
296 | width: 2,
297 | startX: 6,
298 | startY: 2,
299 | identifier: "8",
300 | data: "refresh"),
301 | ColoredDashboardItem(
302 | minWidth: 3,
303 | height: 1,
304 | width: 4,
305 | startX: 0,
306 | startY: 3,
307 | identifier: "9",
308 | data: "info"),
309 | ColoredDashboardItem(
310 | startX: 6,
311 | startY: 3,
312 | height: 2,
313 | width: 2,
314 | identifier: "13",
315 | data: "pub"),
316 | ColoredDashboardItem(
317 | height: 1, width: 2, identifier: "10", data: "github"),
318 | ColoredDashboardItem(
319 | height: 1, width: 2, identifier: "11", data: "twitter"),
320 | ColoredDashboardItem(
321 | height: 1, width: 2, identifier: "12", data: "linkedin")
322 | ]
323 | };
324 |
325 | Map>? _localItems;
326 |
327 | @override
328 | FutureOr> getAllItems(int slotCount) {
329 | try {
330 | if (_localItems != null) {
331 | return _localItems![slotCount]!.values.toList();
332 | }
333 |
334 | return Future.microtask(() async {
335 | _preferences = await SharedPreferences.getInstance();
336 |
337 | var init = _preferences.getBool("init") ?? false;
338 |
339 | if (!init) {
340 | _localItems = {
341 | for (var s in _slotCounts)
342 | s: _default[s]!
343 | .asMap()
344 | .map((key, value) => MapEntry(value.identifier, value))
345 | };
346 |
347 | for (var s in _slotCounts) {
348 | await _preferences.setString(
349 | "layout_data_$s",
350 | json.encode(_default[s]!.asMap().map((key, value) =>
351 | MapEntry(value.identifier, value.toMap()))));
352 | }
353 |
354 | await _preferences.setBool("init", true);
355 | }
356 |
357 | var js = json.decode(_preferences.getString("layout_data_$slotCount")!);
358 |
359 | return js!.values
360 | .map(
361 | (value) => ColoredDashboardItem.fromMap(value))
362 | .toList();
363 | });
364 | } on Exception {
365 | rethrow;
366 | }
367 | }
368 |
369 | @override
370 | FutureOr onItemsUpdated(
371 | List items, int slotCount) async {
372 | _setLocal();
373 |
374 | for (var item in items) {
375 | _localItems?[slotCount]?[item.identifier] = item;
376 | }
377 |
378 | var js = json.encode(_localItems![slotCount]!
379 | .map((key, value) => MapEntry(key, value.toMap())));
380 |
381 | await _preferences.setString("layout_data_$slotCount", js);
382 | }
383 |
384 | @override
385 | FutureOr onItemsAdded(
386 | List items, int slotCount) async {
387 | _setLocal();
388 | for (var s in _slotCounts) {
389 | for (var i in items) {
390 | _localItems![s]?[i.identifier] = i;
391 | }
392 |
393 | await _preferences.setString(
394 | "layout_data_$s",
395 | json.encode(_localItems![s]!
396 | .map((key, value) => MapEntry(key, value.toMap()))));
397 | }
398 | }
399 |
400 | @override
401 | FutureOr onItemsDeleted(
402 | List items, int slotCount) async {
403 | _setLocal();
404 | for (var s in _slotCounts) {
405 | for (var i in items) {
406 | _localItems![s]?.remove(i.identifier);
407 | }
408 |
409 | await _preferences.setString(
410 | "layout_data_$s",
411 | json.encode(_localItems![s]!
412 | .map((key, value) => MapEntry(key, value.toMap()))));
413 | }
414 | }
415 |
416 | Future clear() async {
417 | for (var s in _slotCounts) {
418 | _localItems?[s]?.clear();
419 | await _preferences.remove("layout_data_$s");
420 | }
421 | _localItems = null;
422 | await _preferences.setBool("init", false);
423 | }
424 |
425 | _setLocal() {
426 | _localItems ??= {
427 | for (var s in _slotCounts)
428 | s: _default[s]!
429 | .asMap()
430 | .map((key, value) => MapEntry(value.identifier, value))
431 | };
432 | }
433 |
434 | @override
435 | bool get layoutsBySlotCount => true;
436 |
437 | @override
438 | bool get cacheItems => true;
439 | }
440 |
--------------------------------------------------------------------------------
/example/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | async:
5 | dependency: transitive
6 | description:
7 | name: async
8 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
9 | url: "https://pub.dev"
10 | source: hosted
11 | version: "2.11.0"
12 | auto_size_text:
13 | dependency: "direct main"
14 | description:
15 | name: auto_size_text
16 | sha256: "3f5261cd3fb5f2a9ab4e2fc3fba84fd9fcaac8821f20a1d4e71f557521b22599"
17 | url: "https://pub.dev"
18 | source: hosted
19 | version: "3.0.0"
20 | boolean_selector:
21 | dependency: transitive
22 | description:
23 | name: boolean_selector
24 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
25 | url: "https://pub.dev"
26 | source: hosted
27 | version: "2.1.1"
28 | characters:
29 | dependency: transitive
30 | description:
31 | name: characters
32 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
33 | url: "https://pub.dev"
34 | source: hosted
35 | version: "1.3.0"
36 | clock:
37 | dependency: transitive
38 | description:
39 | name: clock
40 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
41 | url: "https://pub.dev"
42 | source: hosted
43 | version: "1.1.1"
44 | collection:
45 | dependency: transitive
46 | description:
47 | name: collection
48 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
49 | url: "https://pub.dev"
50 | source: hosted
51 | version: "1.18.0"
52 | cupertino_icons:
53 | dependency: "direct main"
54 | description:
55 | name: cupertino_icons
56 | sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
57 | url: "https://pub.dev"
58 | source: hosted
59 | version: "1.0.6"
60 | dashboard:
61 | dependency: "direct main"
62 | description:
63 | path: ".."
64 | relative: true
65 | source: path
66 | version: "0.0.5"
67 | fake_async:
68 | dependency: transitive
69 | description:
70 | name: fake_async
71 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
72 | url: "https://pub.dev"
73 | source: hosted
74 | version: "1.3.1"
75 | ffi:
76 | dependency: transitive
77 | description:
78 | name: ffi
79 | sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978
80 | url: "https://pub.dev"
81 | source: hosted
82 | version: "2.0.1"
83 | file:
84 | dependency: transitive
85 | description:
86 | name: file
87 | sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
88 | url: "https://pub.dev"
89 | source: hosted
90 | version: "6.1.4"
91 | flutter:
92 | dependency: "direct main"
93 | description: flutter
94 | source: sdk
95 | version: "0.0.0"
96 | flutter_colorpicker:
97 | dependency: "direct main"
98 | description:
99 | name: flutter_colorpicker
100 | sha256: "458a6ed8ea480eb16ff892aedb4b7092b2804affd7e046591fb03127e8d8ef8b"
101 | url: "https://pub.dev"
102 | source: hosted
103 | version: "1.0.3"
104 | flutter_lints:
105 | dependency: "direct dev"
106 | description:
107 | name: flutter_lints
108 | sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7
109 | url: "https://pub.dev"
110 | source: hosted
111 | version: "3.0.1"
112 | flutter_test:
113 | dependency: "direct dev"
114 | description: flutter
115 | source: sdk
116 | version: "0.0.0"
117 | flutter_web_plugins:
118 | dependency: transitive
119 | description: flutter
120 | source: sdk
121 | version: "0.0.0"
122 | leak_tracker:
123 | dependency: transitive
124 | description:
125 | name: leak_tracker
126 | sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
127 | url: "https://pub.dev"
128 | source: hosted
129 | version: "10.0.0"
130 | leak_tracker_flutter_testing:
131 | dependency: transitive
132 | description:
133 | name: leak_tracker_flutter_testing
134 | sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
135 | url: "https://pub.dev"
136 | source: hosted
137 | version: "2.0.1"
138 | leak_tracker_testing:
139 | dependency: transitive
140 | description:
141 | name: leak_tracker_testing
142 | sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
143 | url: "https://pub.dev"
144 | source: hosted
145 | version: "2.0.1"
146 | lints:
147 | dependency: transitive
148 | description:
149 | name: lints
150 | sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
151 | url: "https://pub.dev"
152 | source: hosted
153 | version: "3.0.0"
154 | matcher:
155 | dependency: transitive
156 | description:
157 | name: matcher
158 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
159 | url: "https://pub.dev"
160 | source: hosted
161 | version: "0.12.16+1"
162 | material_color_utilities:
163 | dependency: transitive
164 | description:
165 | name: material_color_utilities
166 | sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
167 | url: "https://pub.dev"
168 | source: hosted
169 | version: "0.8.0"
170 | meta:
171 | dependency: transitive
172 | description:
173 | name: meta
174 | sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
175 | url: "https://pub.dev"
176 | source: hosted
177 | version: "1.11.0"
178 | path:
179 | dependency: transitive
180 | description:
181 | name: path
182 | sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
183 | url: "https://pub.dev"
184 | source: hosted
185 | version: "1.9.0"
186 | path_provider_linux:
187 | dependency: transitive
188 | description:
189 | name: path_provider_linux
190 | sha256: ab0987bf95bc591da42dffb38c77398fc43309f0b9b894dcc5d6f40c4b26c379
191 | url: "https://pub.dev"
192 | source: hosted
193 | version: "2.1.7"
194 | path_provider_platform_interface:
195 | dependency: transitive
196 | description:
197 | name: path_provider_platform_interface
198 | sha256: f0abc8ebd7253741f05488b4813d936b4d07c6bae3e86148a09e342ee4b08e76
199 | url: "https://pub.dev"
200 | source: hosted
201 | version: "2.0.5"
202 | path_provider_windows:
203 | dependency: transitive
204 | description:
205 | name: path_provider_windows
206 | sha256: bcabbe399d4042b8ee687e17548d5d3f527255253b4a639f5f8d2094a9c2b45c
207 | url: "https://pub.dev"
208 | source: hosted
209 | version: "2.1.3"
210 | platform:
211 | dependency: transitive
212 | description:
213 | name: platform
214 | sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
215 | url: "https://pub.dev"
216 | source: hosted
217 | version: "3.1.0"
218 | plugin_platform_interface:
219 | dependency: transitive
220 | description:
221 | name: plugin_platform_interface
222 | sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a
223 | url: "https://pub.dev"
224 | source: hosted
225 | version: "2.1.3"
226 | process:
227 | dependency: transitive
228 | description:
229 | name: process
230 | sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
231 | url: "https://pub.dev"
232 | source: hosted
233 | version: "4.2.4"
234 | shared_preferences:
235 | dependency: "direct main"
236 | description:
237 | name: shared_preferences
238 | sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02"
239 | url: "https://pub.dev"
240 | source: hosted
241 | version: "2.2.2"
242 | shared_preferences_android:
243 | dependency: transitive
244 | description:
245 | name: shared_preferences_android
246 | sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06"
247 | url: "https://pub.dev"
248 | source: hosted
249 | version: "2.2.1"
250 | shared_preferences_foundation:
251 | dependency: transitive
252 | description:
253 | name: shared_preferences_foundation
254 | sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c"
255 | url: "https://pub.dev"
256 | source: hosted
257 | version: "2.3.5"
258 | shared_preferences_linux:
259 | dependency: transitive
260 | description:
261 | name: shared_preferences_linux
262 | sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
263 | url: "https://pub.dev"
264 | source: hosted
265 | version: "2.3.2"
266 | shared_preferences_platform_interface:
267 | dependency: transitive
268 | description:
269 | name: shared_preferences_platform_interface
270 | sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a
271 | url: "https://pub.dev"
272 | source: hosted
273 | version: "2.3.1"
274 | shared_preferences_web:
275 | dependency: transitive
276 | description:
277 | name: shared_preferences_web
278 | sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21"
279 | url: "https://pub.dev"
280 | source: hosted
281 | version: "2.2.2"
282 | shared_preferences_windows:
283 | dependency: transitive
284 | description:
285 | name: shared_preferences_windows
286 | sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
287 | url: "https://pub.dev"
288 | source: hosted
289 | version: "2.3.2"
290 | sky_engine:
291 | dependency: transitive
292 | description: flutter
293 | source: sdk
294 | version: "0.0.99"
295 | source_span:
296 | dependency: transitive
297 | description:
298 | name: source_span
299 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
300 | url: "https://pub.dev"
301 | source: hosted
302 | version: "1.10.0"
303 | stack_trace:
304 | dependency: transitive
305 | description:
306 | name: stack_trace
307 | sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
308 | url: "https://pub.dev"
309 | source: hosted
310 | version: "1.11.1"
311 | stream_channel:
312 | dependency: transitive
313 | description:
314 | name: stream_channel
315 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
316 | url: "https://pub.dev"
317 | source: hosted
318 | version: "2.1.2"
319 | string_scanner:
320 | dependency: transitive
321 | description:
322 | name: string_scanner
323 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
324 | url: "https://pub.dev"
325 | source: hosted
326 | version: "1.2.0"
327 | term_glyph:
328 | dependency: transitive
329 | description:
330 | name: term_glyph
331 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
332 | url: "https://pub.dev"
333 | source: hosted
334 | version: "1.2.1"
335 | test_api:
336 | dependency: transitive
337 | description:
338 | name: test_api
339 | sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
340 | url: "https://pub.dev"
341 | source: hosted
342 | version: "0.6.1"
343 | url_launcher:
344 | dependency: "direct main"
345 | description:
346 | name: url_launcher
347 | sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c
348 | url: "https://pub.dev"
349 | source: hosted
350 | version: "6.2.4"
351 | url_launcher_android:
352 | dependency: transitive
353 | description:
354 | name: url_launcher_android
355 | sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f"
356 | url: "https://pub.dev"
357 | source: hosted
358 | version: "6.2.2"
359 | url_launcher_ios:
360 | dependency: transitive
361 | description:
362 | name: url_launcher_ios
363 | sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03"
364 | url: "https://pub.dev"
365 | source: hosted
366 | version: "6.2.4"
367 | url_launcher_linux:
368 | dependency: transitive
369 | description:
370 | name: url_launcher_linux
371 | sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811
372 | url: "https://pub.dev"
373 | source: hosted
374 | version: "3.1.1"
375 | url_launcher_macos:
376 | dependency: transitive
377 | description:
378 | name: url_launcher_macos
379 | sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234
380 | url: "https://pub.dev"
381 | source: hosted
382 | version: "3.1.0"
383 | url_launcher_platform_interface:
384 | dependency: transitive
385 | description:
386 | name: url_launcher_platform_interface
387 | sha256: "4aca1e060978e19b2998ee28503f40b5ba6226819c2b5e3e4d1821e8ccd92198"
388 | url: "https://pub.dev"
389 | source: hosted
390 | version: "2.3.0"
391 | url_launcher_web:
392 | dependency: transitive
393 | description:
394 | name: url_launcher_web
395 | sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b
396 | url: "https://pub.dev"
397 | source: hosted
398 | version: "2.2.3"
399 | url_launcher_windows:
400 | dependency: transitive
401 | description:
402 | name: url_launcher_windows
403 | sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7
404 | url: "https://pub.dev"
405 | source: hosted
406 | version: "3.1.1"
407 | vector_math:
408 | dependency: transitive
409 | description:
410 | name: vector_math
411 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
412 | url: "https://pub.dev"
413 | source: hosted
414 | version: "2.1.4"
415 | vm_service:
416 | dependency: transitive
417 | description:
418 | name: vm_service
419 | sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
420 | url: "https://pub.dev"
421 | source: hosted
422 | version: "13.0.0"
423 | web:
424 | dependency: transitive
425 | description:
426 | name: web
427 | sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
428 | url: "https://pub.dev"
429 | source: hosted
430 | version: "0.3.0"
431 | win32:
432 | dependency: transitive
433 | description:
434 | name: win32
435 | sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46
436 | url: "https://pub.dev"
437 | source: hosted
438 | version: "3.1.3"
439 | xdg_directories:
440 | dependency: transitive
441 | description:
442 | name: xdg_directories
443 | sha256: "11541eedefbcaec9de35aa82650b695297ce668662bbd6e3911a7fabdbde589f"
444 | url: "https://pub.dev"
445 | source: hosted
446 | version: "0.2.0+2"
447 | sdks:
448 | dart: ">=3.2.0 <4.0.0"
449 | flutter: ">=3.16.0"
450 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: example
2 | description: A new Flutter project.
3 |
4 | publish_to: 'none'
5 | version: 1.0.0+1
6 |
7 | environment:
8 | sdk: ">=2.17.0 <4.0.0"
9 |
10 | dependencies:
11 | shared_preferences: ^2.2.2
12 | flutter_colorpicker: ^1.0.3
13 | url_launcher: ^6.2.4
14 | auto_size_text: ^3.0.0
15 | flutter:
16 | sdk: flutter
17 | dashboard:
18 | path: ..
19 | cupertino_icons: ^1.0.6
20 |
21 | dev_dependencies:
22 | flutter_test:
23 | sdk: flutter
24 |
25 | flutter_lints: ^3.0.1
26 |
27 | flutter:
28 | uses-material-design: true
29 | assets:
30 | - assets/img.png
31 | - assets/github.png
32 | - assets/linkedin.png
33 | - assets/twitter.png
34 | - assets/pub_dev.png
35 |
--------------------------------------------------------------------------------
/example/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility in the flutter_test package. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_test/flutter_test.dart';
10 |
11 | import 'package:example/main.dart';
12 |
13 | void main() {
14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
15 | // Build our app and trigger a frame.
16 | await tester.pumpWidget(const MyApp());
17 |
18 | // Verify that our counter starts at 0.
19 | expect(find.text('0'), findsOneWidget);
20 | expect(find.text('1'), findsNothing);
21 |
22 | // Tap the '+' icon and trigger a frame.
23 | await tester.tap(find.byIcon(Icons.add));
24 | await tester.pump();
25 |
26 | // Verify that our counter has incremented.
27 | expect(find.text('0'), findsNothing);
28 | expect(find.text('1'), findsOneWidget);
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/example/web/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/web/favicon.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/web/icons/Icon-192.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/web/icons/Icon-512.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-maskable-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/web/icons/Icon-maskable-192.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-maskable-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mehmetyaz/dashboard/76a2d5959c8679696682622e86ce857ae83c8ac4/example/web/icons/Icon-maskable-512.png
--------------------------------------------------------------------------------
/example/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | example
33 |
34 |
35 |
36 |
39 |
103 |
104 |
105 |
--------------------------------------------------------------------------------
/example/web/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "short_name": "example",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "background_color": "#0175C2",
7 | "theme_color": "#0175C2",
8 | "description": "A new Flutter project.",
9 | "orientation": "portrait-primary",
10 | "prefer_related_applications": false,
11 | "icons": [
12 | {
13 | "src": "icons/Icon-192.png",
14 | "sizes": "192x192",
15 | "type": "image/png"
16 | },
17 | {
18 | "src": "icons/Icon-512.png",
19 | "sizes": "512x512",
20 | "type": "image/png"
21 | },
22 | {
23 | "src": "icons/Icon-maskable-192.png",
24 | "sizes": "192x192",
25 | "type": "image/png",
26 | "purpose": "maskable"
27 | },
28 | {
29 | "src": "icons/Icon-maskable-512.png",
30 | "sizes": "512x512",
31 | "type": "image/png",
32 | "purpose": "maskable"
33 | }
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/lib/dashboard.dart:
--------------------------------------------------------------------------------
1 | export 'src/dashboard_base.dart';
2 | export 'src/widgets/style.dart';
3 |
--------------------------------------------------------------------------------
/lib/src/controller/dashboard_item_storage.dart:
--------------------------------------------------------------------------------
1 | part of '../dashboard_base.dart';
2 |
3 | /// Dashboard item layout changes stored/handled with the delegate. Keeps the
4 | /// layout in memory, fetches the layout when necessary. It can store different
5 | /// layouts for different slotCounts. Different slotCounts may be required,
6 | /// especially for browser users.
7 | ///
8 | /// If [cacheItems] the delegate store layouts from memory.
9 | ///
10 | /// If [layoutsBySlotCount] the delegate store and request new layout
11 | /// by slotCount.
12 | ///
13 | /// Cached items can be get with [itemsFor].
14 | ///
15 | /// [getAllItems] will call if necessary. If your item getter is not Future,
16 | /// do not override as [async], because if you use unnecessary Future, layout
17 | /// changes not animated on slotCount changes.
18 | ///
19 | /// [onItemsAdded] will call when [DashboardItemController.add] or
20 | /// [addAll] called. Function called with up-to-date layout data.
21 | ///
22 | /// [onItemsUpdated] will call when item layout changes. Layout can be changed
23 | /// by user or initially re-mount operations. Function called with up-to-date
24 | /// layout data.
25 | ///
26 | /// [onItemsDeleted] will call when item deleted by
27 | /// [DashboardItemController.delete] or [deleteAll] called.
28 | ///
29 | abstract class DashboardItemStorageDelegate {
30 | final Map> _items = {};
31 |
32 | /// Item list for given slotCount
33 | Map? itemsFor(int slotCount) {
34 | return _items[slotCount] == null ? null : Map.from(_items[slotCount]!);
35 | }
36 |
37 | /// If [cacheItems] the delegate store layouts from memory.
38 | bool get cacheItems;
39 |
40 | /// If [layoutsBySlotCount] the delegate store and request new layout
41 | /// by slotCount.
42 | bool get layoutsBySlotCount;
43 |
44 | FutureOr> _getAllItems(int slotCount) {
45 | var sc = layoutsBySlotCount ? slotCount : -1;
46 |
47 | if (!cacheItems) {
48 | return getAllItems(slotCount);
49 | } else {
50 | if (_items[sc] != null) {
51 | return _items[sc]!.values.toList();
52 | } else {
53 | var itemsFtrOr = getAllItems(slotCount);
54 | if (itemsFtrOr is Future) {
55 | var items = Future>.microtask(() async {
56 | return await itemsFtrOr;
57 | }).then((value) {
58 | _items[sc] = value
59 | .asMap()
60 | .map((key, value) => MapEntry(value.identifier, value));
61 | return value;
62 | });
63 | return items;
64 | } else {
65 | _items[sc] = itemsFtrOr
66 | .asMap()
67 | .map((key, value) => MapEntry(value.identifier, value));
68 | return itemsFtrOr;
69 | }
70 | }
71 | }
72 | }
73 |
74 | ///
75 | FutureOr _onItemsUpdated(List items, int slotCount) {
76 | var sc = layoutsBySlotCount ? slotCount : -1;
77 |
78 | if (cacheItems) {
79 | var map = _items[sc];
80 | if (map != null) {
81 | for (var i in items) {
82 | map[i.identifier] = i;
83 | }
84 | }
85 | }
86 | return onItemsUpdated(items, slotCount);
87 | }
88 |
89 | ///
90 | FutureOr _onItemsAdded(List items, int slotCount) {
91 | if (cacheItems) {
92 | _items.forEach((key, value) {
93 | for (var i in items) {
94 | var l = i.layoutData;
95 | if (l.minWidth <= key) {
96 | value[i.identifier] = i;
97 | }
98 | }
99 | });
100 | }
101 |
102 | return onItemsAdded(items, slotCount);
103 | }
104 |
105 | ///
106 | FutureOr _onItemsDeleted(List items, int slotCount) {
107 | if (cacheItems) {
108 | _items.forEach((key, value) {
109 | for (var i in items) {
110 | value.remove(i.identifier);
111 | }
112 | });
113 | }
114 | return onItemsDeleted(items, slotCount);
115 | }
116 |
117 | /// [getAllItems] will call if necessary. If your item getter is not Future,
118 | /// do not override as [async], because if you use unnecessary Future, layout
119 | /// changes not animated on slotCount changes.
120 | FutureOr> getAllItems(int slotCount);
121 |
122 | /// [onItemsUpdated] will call when item layout changes. Layout can be changed
123 | /// by user or initially re-mount operations. Function called with up-to-date
124 | /// layout data.
125 | FutureOr onItemsUpdated(List items, int slotCount);
126 |
127 | /// [onItemsAdded] will call when [DashboardItemController.add] or
128 | /// [addAll] called. Function called with up-to-date layout data.
129 | FutureOr onItemsAdded(List items, int slotCount);
130 |
131 | /// [onItemsDeleted] will call when item deleted by
132 | /// [DashboardItemController.delete] or [deleteAll] called.
133 | FutureOr onItemsDeleted(List items, int slotCount);
134 | }
135 |
--------------------------------------------------------------------------------
/lib/src/dashboard_base.dart:
--------------------------------------------------------------------------------
1 | library dashboard;
2 |
3 | import 'dart:async';
4 | import 'dart:collection';
5 | import 'dart:math';
6 |
7 | import 'package:dashboard/dashboard.dart';
8 | import 'package:dashboard/src/widgets/style.dart';
9 | import 'package:flutter/gestures.dart';
10 | import 'package:flutter/material.dart';
11 | import 'package:flutter/rendering.dart';
12 | import 'package:flutter/scheduler.dart';
13 |
14 | part 'widgets/dashboard.dart';
15 |
16 | part 'widgets/dashboard_stack.dart';
17 |
18 | part 'widgets/dashboard_item_widget.dart';
19 | part 'widgets/animated_background_painter.dart';
20 | part 'widgets/grid_builder.dart';
21 |
22 | part 'models/dashboard_item.dart';
23 | part 'models/viewport_settings.dart';
24 |
25 | part 'models/item_current_layout.dart';
26 |
27 | part 'models/item_layout_data.dart';
28 |
29 | part 'edit_mode/edit_mode_background_style.dart';
30 |
31 | part 'edit_mode/edit_mode_painter.dart';
32 |
33 | part 'edit_mode/edit_mode_settings.dart';
34 |
35 | part 'exceptions/unbounded.dart';
36 |
37 | part 'controller/dashboard_controller.dart';
38 | part 'controller/dashboard_item_storage.dart';
39 |
--------------------------------------------------------------------------------
/lib/src/edit_mode/edit_mode_background_style.dart:
--------------------------------------------------------------------------------
1 | part of '../dashboard_base.dart';
2 |
3 | /// In edit mode, dashboard background are painted with lines and a rect if
4 | /// any items is editing.
5 | ///
6 | /// The lines show where the slots begin and end, along with the
7 | /// [verticalSpace] and the [horizontalSpace].
8 | ///
9 | /// Filling shows the actual location of the editing item. This pan/move also
10 | /// shows where the item will go when released. It is recommended not to use
11 | /// very obscure colors to reflect the sense of editing well.
12 | class EditModeBackgroundStyle {
13 | /// in default lines are dual.
14 | const EditModeBackgroundStyle(
15 | {this.dualLineVertical = true,
16 | this.dualLineHorizontal = true,
17 | this.lineWidth = 0.7,
18 | this.lineColor = Colors.black54,
19 | this.fillColor = Colors.black38,
20 | this.outherRadius = 8});
21 |
22 | @override
23 | bool operator ==(Object other) {
24 | return other is EditModeBackgroundStyle &&
25 | dualLineVertical == other.dualLineVertical &&
26 | dualLineHorizontal == other.dualLineHorizontal &&
27 | lineWidth == other.lineWidth &&
28 | lineColor == other.lineColor &&
29 | fillColor == other.fillColor;
30 | }
31 |
32 | final double outherRadius;
33 |
34 | /// Editing item background filling color.
35 | final Color fillColor;
36 |
37 | /// If [dualLineVertical] lines draw exactly slots ends and starts vertically.
38 | /// Else only one line draw center of the horizontalSpace.
39 | final bool dualLineVertical;
40 |
41 | /// If [dualLineVertical] lines draw exactly slots ends and starts
42 | /// horizontally.
43 | /// Else only one line draw center of the verticalSpace.
44 | final bool dualLineHorizontal;
45 |
46 | /// Line thickness.
47 | final double lineWidth;
48 |
49 | /// Line color
50 | final Color lineColor;
51 |
52 | @override
53 | int get hashCode => Object.hash(
54 | fillColor, dualLineVertical, dualLineHorizontal, lineWidth, lineColor);
55 | }
56 |
57 | // class EditModeForegroundStyle {
58 | // const EditModeForegroundStyle(
59 | // {this.fillColor = Colors.black26,
60 | // this.innerRadius = 8,
61 | // this.outherRadius = 8,
62 | // this.shadowColor = Colors.white24,
63 | // this.shadowElevation = 4,
64 | // this.shadowTransparentOccluder = true,
65 | // this.sideWidth});
66 | //
67 | // ///
68 | // final double? sideWidth;
69 | // final Color fillColor;
70 | // final double innerRadius, outherRadius;
71 | // final Color shadowColor;
72 | // final bool shadowTransparentOccluder;
73 | // final double shadowElevation;
74 | // }
75 |
--------------------------------------------------------------------------------
/lib/src/edit_mode/edit_mode_painter.dart:
--------------------------------------------------------------------------------
1 | part of '../dashboard_base.dart';
2 |
3 | class _EditModeBackgroundPainter extends CustomPainter {
4 | _EditModeBackgroundPainter(
5 | {required this.offset,
6 | required this.slotEdge,
7 | required this.verticalSlotEdge,
8 | required this.slotCount,
9 | required this.viewportDelegate,
10 | this.fillPosition,
11 | required this.lines,
12 | this.style = const EditModeBackgroundStyle()});
13 |
14 | _ViewportDelegate viewportDelegate;
15 |
16 | final bool lines;
17 |
18 | Rect? fillPosition;
19 |
20 | double offset;
21 |
22 | double slotEdge, verticalSlotEdge;
23 |
24 | int slotCount;
25 |
26 | BoxConstraints get constraints => viewportDelegate.resolvedConstrains;
27 |
28 | double get mainAxisSpace => viewportDelegate.mainAxisSpace;
29 |
30 | double get crossAxisSpace => viewportDelegate.crossAxisSpace;
31 |
32 | EditModeBackgroundStyle style;
33 |
34 | void drawVerticalLines(Canvas canvas) {
35 | if (!lines) {
36 | return;
37 | }
38 |
39 | var horizontalLinePaint = Paint()
40 | ..style = PaintingStyle.stroke
41 | ..color = style.lineColor
42 | ..strokeWidth = style.lineWidth;
43 | var sY = -0.0 -
44 | (offset.clamp(0, 100)) -
45 | (style.dualLineHorizontal ? mainAxisSpace / 2 : 0);
46 | var eY = constraints.maxHeight + 100;
47 | for (var i in List.generate(slotCount + 1, (index) => index)) {
48 | if (i == 0) {
49 | canvas.drawLine(Offset(0, sY), Offset(0, eY), horizontalLinePaint);
50 | } else if (i == slotCount) {
51 | var x = slotEdge * slotCount;
52 | canvas.drawLine(Offset(x, sY), Offset(x, eY), horizontalLinePaint);
53 | } else {
54 | if (style.dualLineVertical) {
55 | var l = (slotEdge * i) - crossAxisSpace / 2;
56 | var r = (slotEdge * i) + crossAxisSpace / 2;
57 | canvas.drawLine(Offset(l, sY), Offset(l, eY), horizontalLinePaint);
58 | canvas.drawLine(Offset(r, sY), Offset(r, eY), horizontalLinePaint);
59 | } else {
60 | var x = (slotEdge * i);
61 | canvas.drawLine(Offset(x, sY), Offset(x, eY), horizontalLinePaint);
62 | }
63 | }
64 | }
65 | }
66 |
67 | void drawHorizontals(Canvas canvas) {
68 | if (!lines) {
69 | return;
70 | }
71 |
72 | var horizontalLinePaint = Paint()
73 | ..style = PaintingStyle.stroke
74 | ..color = style.lineColor
75 | ..strokeWidth = style.lineWidth;
76 | var max = constraints.maxHeight;
77 | var i = 0;
78 | var s = offset % verticalSlotEdge;
79 | while (true) {
80 | var y = (i * verticalSlotEdge) - s;
81 | if (y > max) {
82 | break;
83 | }
84 |
85 | if (style.dualLineHorizontal) {
86 | var t = y - mainAxisSpace / 2;
87 | var b = y + mainAxisSpace / 2;
88 | canvas.drawLine(
89 | Offset(0, t), Offset(constraints.maxWidth, t), horizontalLinePaint);
90 | canvas.drawLine(
91 | Offset(0, b), Offset(constraints.maxWidth, b), horizontalLinePaint);
92 | } else {
93 | canvas.drawLine(
94 | Offset(0, y), Offset(constraints.maxWidth, y), horizontalLinePaint);
95 | }
96 |
97 | i++;
98 | }
99 | }
100 |
101 | @override
102 | void paint(Canvas canvas, Size size) {
103 | drawVerticalLines(canvas);
104 | drawHorizontals(canvas);
105 | if (fillPosition != null) {
106 | var path = Path()
107 | ..moveTo(fillPosition!.left + style.outherRadius, fillPosition!.top)
108 | ..lineTo(fillPosition!.right - style.outherRadius, fillPosition!.top)
109 | ..arcToPoint(
110 | Offset(fillPosition!.right, fillPosition!.top + style.outherRadius),
111 | radius: Radius.circular(style.outherRadius))
112 | ..lineTo(fillPosition!.right, fillPosition!.bottom - style.outherRadius)
113 | ..arcToPoint(
114 | Offset(
115 | fillPosition!.right - style.outherRadius, fillPosition!.bottom),
116 | radius: Radius.circular(style.outherRadius))
117 | ..lineTo(fillPosition!.left + style.outherRadius, fillPosition!.bottom)
118 | ..arcToPoint(
119 | Offset(
120 | fillPosition!.left, fillPosition!.bottom - style.outherRadius),
121 | radius: Radius.circular(style.outherRadius))
122 | ..lineTo(fillPosition!.left, fillPosition!.top + style.outherRadius)
123 | ..arcToPoint(
124 | Offset(fillPosition!.left + style.outherRadius, fillPosition!.top),
125 | radius: Radius.circular(style.outherRadius))
126 | ..close();
127 |
128 | canvas.drawPath(
129 | path,
130 | Paint()
131 | ..style = PaintingStyle.fill
132 | ..color = style.fillColor,
133 | );
134 | }
135 | }
136 |
137 | @override
138 | bool shouldRepaint(_EditModeBackgroundPainter oldDelegate) {
139 | return true /*fillPosition != oldDelegate.fillPosition ||
140 | offset != oldDelegate.offset ||
141 | slotEdge != oldDelegate.slotEdge ||
142 | slotCount != oldDelegate.slotCount ||
143 | style != oldDelegate.style ||
144 | viewportDelegate != oldDelegate.viewportDelegate*/
145 | ;
146 | }
147 |
148 | @override
149 | bool shouldRebuildSemantics(_EditModeBackgroundPainter oldDelegate) {
150 | return true;
151 | }
152 | }
153 |
154 | // class _EditModeItemPainter extends CustomPainter {
155 | // _EditModeItemPainter(
156 | // {required this.style,
157 | // required double tolerance,
158 | // required this.constraints})
159 | // : tolerance = style.sideWidth ?? tolerance;
160 | //
161 | // final EditModeForegroundStyle style;
162 | // final double tolerance;
163 | // final BoxConstraints constraints;
164 | //
165 | // @override
166 | // void paint(Canvas canvas, Size size) {
167 | // var outher = Path()
168 | // ..moveTo(style.outherRadius, 0)
169 | // ..lineTo(constraints.maxWidth - style.outherRadius, 0)
170 | // ..arcToPoint(Offset(constraints.maxWidth, style.outherRadius),
171 | // radius: Radius.circular(style.outherRadius))
172 | // ..lineTo(constraints.maxWidth, constraints.maxHeight - style.outherRadius)
173 | // ..arcToPoint(
174 | // Offset(
175 | // constraints.maxWidth - style.outherRadius, constraints.maxHeight),
176 | // radius: Radius.circular(style.outherRadius))
177 | // ..lineTo(style.outherRadius, constraints.maxHeight)
178 | // ..arcToPoint(Offset(0, constraints.maxHeight - style.outherRadius),
179 | // radius: Radius.circular(style.outherRadius))
180 | // ..lineTo(0, style.outherRadius)
181 | // ..arcToPoint(Offset(style.outherRadius, 0),
182 | // radius: Radius.circular(style.outherRadius))
183 | // ..close();
184 | //
185 | // var inner = Path()
186 | // ..moveTo(style.innerRadius + tolerance, tolerance)
187 | // ..lineTo(constraints.maxWidth - style.innerRadius - tolerance, tolerance)
188 | // ..arcToPoint(
189 | // Offset(constraints.maxWidth, style.innerRadius)
190 | // .translate(-tolerance, tolerance),
191 | // radius: Radius.circular(style.innerRadius))
192 | // ..lineTo(constraints.maxWidth - tolerance,
193 | // constraints.maxHeight - style.innerRadius - tolerance)
194 | // ..arcToPoint(
195 | // Offset(constraints.maxWidth - style.innerRadius,
196 | // constraints.maxHeight)
197 | // .translate(-tolerance, -tolerance),
198 | // radius: Radius.circular(style.innerRadius))
199 | // ..lineTo(style.innerRadius + tolerance, constraints.maxHeight - tolerance)
200 | // ..arcToPoint(
201 | // Offset(0, constraints.maxHeight - style.innerRadius)
202 | // .translate(tolerance, -tolerance),
203 | // radius: Radius.circular(style.innerRadius))
204 | // ..lineTo(tolerance, style.innerRadius + tolerance)
205 | // ..arcToPoint(Offset(style.innerRadius + tolerance, tolerance),
206 | // radius: Radius.circular(style.innerRadius))
207 | // ..close();
208 | //
209 | // var path = Path.combine(PathOperation.difference, outher, inner);
210 | // canvas.drawShadow(path, style.shadowColor, style.shadowElevation,
211 | // style.shadowTransparentOccluder);
212 | // canvas.drawPath(
213 | // path,
214 | // Paint()
215 | // ..style = PaintingStyle.fill
216 | // ..color = style.fillColor,
217 | // );
218 | // }
219 | //
220 | // @override
221 | // bool shouldRepaint(_EditModeItemPainter oldDelegate) {
222 | // return constraints != oldDelegate.constraints;
223 | // }
224 | //
225 | // @override
226 | // bool shouldRebuildSemantics(_EditModeItemPainter oldDelegate) {
227 | // return constraints != oldDelegate.constraints;
228 | // }
229 | // }
230 |
--------------------------------------------------------------------------------
/lib/src/edit_mode/edit_mode_settings.dart:
--------------------------------------------------------------------------------
1 | part of '../dashboard_base.dart';
2 |
3 | /// Edit mode settings.
4 | /// It contains parameters related to gesture, animations, resizing.
5 | class EditModeSettings {
6 | /// duration default is package:flutter/foundation.dart
7 | /// [kThemeAnimationDuration]
8 | EditModeSettings({
9 | this.resizeCursorSide = 10,
10 | this.paintBackgroundLines = true,
11 | this.fillEditingBackground = true,
12 | this.longPressEnabled = true,
13 | this.panEnabled = true,
14 | this.backgroundStyle = const EditModeBackgroundStyle(),
15 | this.curve = Curves.easeOut,
16 | Duration? duration,
17 | this.shrinkOnMove = true,
18 | this.draggableOutside = true,
19 | this.autoScroll = true,
20 | }) : duration = duration ?? kThemeAnimationDuration;
21 |
22 | /// If [draggableOutside] is true, items can be dragged outside the viewport.
23 | /// Else items can't be dragged outside the viewport.
24 | ///
25 | /// This only effects horizontal drag. To disable vertical drag set
26 | /// [autoScroll] to false also.
27 | final bool draggableOutside;
28 |
29 | /// If [autoScroll] is true, viewport will scroll automatically when item is
30 | /// dragged outside the viewport vertically.
31 | final bool autoScroll;
32 |
33 | /// Animation duration
34 | final Duration duration;
35 |
36 | /// Start resize or move with long press.
37 | final bool longPressEnabled;
38 |
39 | /// Start resize or move with pan.
40 | final bool panEnabled;
41 |
42 | /// Animation curve
43 | final Curve curve;
44 |
45 | /// Shrink items on moving if necessary.
46 | final bool shrinkOnMove;
47 |
48 | /// Resize side width. If pan/longPress start in side editing is resizing.
49 | final double resizeCursorSide;
50 |
51 | /// Paint background lines.
52 | final bool paintBackgroundLines;
53 |
54 | /// Fill editing item background.
55 | final bool fillEditingBackground;
56 |
57 | ///final bool paintItemForeground = false;
58 |
59 | /// Background style
60 | final EditModeBackgroundStyle backgroundStyle;
61 | }
62 |
--------------------------------------------------------------------------------
/lib/src/exceptions/unbounded.dart:
--------------------------------------------------------------------------------
1 | part of '../dashboard_base.dart';
2 |
3 | /// Unbounded exception
4 | class Unbounded implements Exception {
5 | /// Create with constraints and axis.
6 | Unbounded({required this.constraints, required this.axis});
7 |
8 | static void check(Axis axis, BoxConstraints constraints) {
9 | if (axis == Axis.vertical && !constraints.hasBoundedWidth) {
10 | throw Unbounded(constraints: constraints, axis: axis);
11 | }
12 | if (axis == Axis.horizontal && !constraints.hasBoundedHeight) {
13 | throw Unbounded(constraints: constraints, axis: axis);
14 | }
15 | }
16 |
17 | final BoxConstraints constraints;
18 | final Axis axis;
19 |
20 | @override
21 | String toString() {
22 | return "Unbounded ${axis == Axis.vertical ? "width" : "height"}\n"
23 | "BoxConstrains: $constraints";
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/lib/src/models/dashboard_item.dart:
--------------------------------------------------------------------------------
1 | part of '../dashboard_base.dart';
2 |
3 | /// A dashboard consists of [DashboardItem]s.
4 | /// [DashboardItem] holds item identifier([identifier]) and [layoutData].
5 | ///
6 | /// [DashboardItem] hold items layout data and identifier.
7 | ///
8 | /// Look [ItemLayout] for more information about layout data.
9 | ///
10 | /// Item losses may occur in [identifier] conflicts. It should be noted that
11 | /// they are unique.
12 | class DashboardItem {
13 | /// The constructor create a ItemLayout with given parameters.
14 | DashboardItem(
15 | {int? startX,
16 | int? startY,
17 | required int width,
18 | required int height,
19 | int minWidth = 1,
20 | int minHeight = 1,
21 | int? maxHeight,
22 | int? maxWidth,
23 | required this.identifier})
24 | : layoutData = ItemLayout(
25 | startX: startX,
26 | startY: startY,
27 | width: width,
28 | height: height,
29 | maxHeight: maxHeight,
30 | maxWidth: maxWidth,
31 | minWidth: minWidth,
32 | minHeight: minHeight);
33 |
34 | /// Create [DashboardItem] with layoutData and identifier.
35 | DashboardItem.withLayout(this.identifier, this.layoutData);
36 |
37 | /// Converts json encodable Map to items to create from storage.
38 | factory DashboardItem.fromMap(Map map) {
39 | return DashboardItem.withLayout(
40 | map["item_id"], ItemLayout.fromMap(map["layout"]));
41 | }
42 |
43 | /// It contains the location and dimensions of the Item on the Dashboard.
44 | ItemLayout layoutData;
45 |
46 | /// It is the items identifier. This is necessary both when creating
47 | /// the layout and storing it in memory, and this identifier helps
48 | /// determine the Widget that [itemBuilder] will return.
49 | String identifier;
50 |
51 | /// Converts items to json encodable Map to store their layout.
52 | Map toMap() {
53 | return {"item_id": identifier, "layout": layoutData.toMap()};
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/lib/src/models/item_layout_data.dart:
--------------------------------------------------------------------------------
1 | part of '../dashboard_base.dart';
2 |
3 | class ItemLayout {
4 | ItemLayout(
5 | {int? startX,
6 | int? startY,
7 | required this.width,
8 | required this.height,
9 | this.minWidth = 1,
10 | this.minHeight = 1,
11 | this.maxHeight,
12 | this.maxWidth})
13 | : assert(minWidth <= width),
14 | assert(minHeight <= height),
15 | assert(maxHeight == null || maxHeight >= height),
16 | assert(maxWidth == null || maxWidth >= width),
17 | _haveLocation = startX != null || startY != null,
18 | startX = startX ?? 0,
19 | startY = startY ?? 0;
20 |
21 | ItemLayout._(
22 | {required this.startX,
23 | required this.startY,
24 | required this.width,
25 | required this.height,
26 | this.minWidth = 1,
27 | this.minHeight = 1,
28 | this.maxHeight,
29 | this.maxWidth,
30 | required bool haveLocation})
31 | : _haveLocation = haveLocation,
32 | assert(minWidth <= width),
33 | assert(minHeight <= height),
34 | assert(maxHeight == null || maxHeight >= height),
35 | assert(maxWidth == null || maxWidth >= width);
36 |
37 | factory ItemLayout.fromMap(Map map) {
38 | return ItemLayout(
39 | startX: map["s_X"],
40 | startY: map["s_Y"],
41 | width: map["w"],
42 | height: map["h"],
43 | maxHeight: map["max_H"],
44 | maxWidth: map["max_W"],
45 | minHeight: map["min_H"],
46 | minWidth: map["min_W"]);
47 | }
48 |
49 | Map toMap() {
50 | return {
51 | if (_haveLocation) "s_X": startX,
52 | if (_haveLocation) "s_Y": startY,
53 | "w": width,
54 | "h": height,
55 | "min_W": minWidth,
56 | "min_H": minHeight,
57 | if (maxHeight != null) "max_H": maxHeight,
58 | if (maxWidth != null) "max_W": maxWidth,
59 | };
60 | }
61 |
62 | @override
63 | String toString() {
64 | return "startX: $startX , startY: $startY , width: $width , height: $height";
65 | }
66 |
67 | bool _haveLocation;
68 |
69 | ///
70 | final int startX, startY;
71 |
72 | ///
73 | final int width, height;
74 |
75 | ///
76 | final int minWidth, minHeight;
77 |
78 | final int? maxWidth, maxHeight;
79 |
80 | ItemLayout copyWithDimension({int? width, int? height}) {
81 | return ItemLayout._(
82 | startX: startX,
83 | startY: startY,
84 | width: width ?? this.width,
85 | height: height ?? this.height,
86 | minHeight: minHeight,
87 | maxHeight: maxHeight,
88 | maxWidth: maxWidth,
89 | minWidth: minWidth,
90 | haveLocation: _haveLocation);
91 | }
92 |
93 | ItemLayout copyWithStarts({int? startX, int? startY}) {
94 | var x = startX ?? this.startX;
95 | var y = startY ?? this.startY;
96 | return ItemLayout._(
97 | startX: x,
98 | startY: y,
99 | width: width,
100 | height: height,
101 | minHeight: minHeight,
102 | maxHeight: maxHeight,
103 | maxWidth: maxWidth,
104 | minWidth: minWidth,
105 | haveLocation: _haveLocation);
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/lib/src/models/viewport_settings.dart:
--------------------------------------------------------------------------------
1 | part of '../dashboard_base.dart';
2 |
3 | @immutable
4 | class _ViewportDelegate {
5 | _ViewportDelegate(
6 | {required this.constraints,
7 | required this.mainAxisSpace,
8 | required this.padding,
9 | required this.crossAxisSpace})
10 | : resolvedConstrains = BoxConstraints(
11 | maxHeight: constraints.maxHeight - padding.vertical,
12 | maxWidth: constraints.maxWidth - padding.horizontal,
13 | );
14 |
15 | @override
16 | bool operator ==(Object other) {
17 | return other is _ViewportDelegate &&
18 | constraints == other.constraints &&
19 | mainAxisSpace == other.mainAxisSpace &&
20 | padding == other.padding &&
21 | crossAxisSpace == other.crossAxisSpace;
22 | }
23 |
24 | final BoxConstraints constraints;
25 | final BoxConstraints resolvedConstrains;
26 | final EdgeInsets padding;
27 | final double mainAxisSpace, crossAxisSpace;
28 |
29 | @override
30 | int get hashCode =>
31 | Object.hash(mainAxisSpace, crossAxisSpace, crossAxisSpace, padding);
32 | }
33 |
--------------------------------------------------------------------------------
/lib/src/widgets/animated_background_painter.dart:
--------------------------------------------------------------------------------
1 | part of '../dashboard_base.dart';
2 |
3 | class _AnimatedBackgroundPainter extends StatefulWidget {
4 | const _AnimatedBackgroundPainter(
5 | {required this.layoutController,
6 | required this.editModeSettings,
7 | required this.offset});
8 |
9 | final _DashboardLayoutController layoutController;
10 | final EditModeSettings editModeSettings;
11 | final ViewportOffset offset;
12 |
13 | @override
14 | State<_AnimatedBackgroundPainter> createState() =>
15 | _AnimatedBackgroundPainterState();
16 | }
17 |
18 | class _AnimatedBackgroundPainterState extends State<_AnimatedBackgroundPainter>
19 | with SingleTickerProviderStateMixin {
20 | _ViewportDelegate get viewportDelegate =>
21 | widget.layoutController._viewportDelegate;
22 |
23 | Rect? fillRect;
24 |
25 | late double offset;
26 |
27 | late AnimationController _animationController;
28 |
29 | Animation? _animation;
30 |
31 | @override
32 | void initState() {
33 | offset = widget.offset.pixels;
34 | _animationController = AnimationController(
35 | vsync: this, duration: widget.editModeSettings.duration);
36 | super.initState();
37 | }
38 |
39 | @override
40 | void dispose() {
41 | _animationController.dispose();
42 | super.dispose();
43 | }
44 |
45 | bool onAnimation = false;
46 |
47 | Rect? _last;
48 |
49 | DateTime? _start;
50 |
51 | @override
52 | Widget build(BuildContext context) {
53 | if (widget.editModeSettings.fillEditingBackground &&
54 | widget.layoutController.editSession != null) {
55 | var pos = widget.layoutController.editSession?.editing._currentPosition(
56 | viewportDelegate: widget.layoutController._viewportDelegate,
57 | slotEdge: widget.layoutController.slotEdge,
58 | verticalSlotEdge: widget.layoutController.verticalSlotEdge);
59 | var rect = Rect.fromLTWH(pos!.x - viewportDelegate.padding.left,
60 | pos.y - offset - viewportDelegate.padding.top, pos.width, pos.height);
61 |
62 | if (fillRect != null && fillRect != rect) {
63 | var begin = fillRect!;
64 |
65 | if (onAnimation && _last != null && _start != null) {
66 | begin = _last!;
67 |
68 | _animationController.duration = (widget.editModeSettings.duration -
69 | DateTime.now().difference(_start!).abs())
70 | .abs();
71 | } else {
72 | _start = DateTime.now();
73 | _last = fillRect;
74 | _animationController.duration = widget.editModeSettings.duration;
75 | }
76 | _animationController.reset();
77 | _animation = RectTween(begin: begin, end: rect).animate(CurvedAnimation(
78 | parent: _animationController,
79 | curve: widget.editModeSettings.curve));
80 | SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
81 | onAnimation = true;
82 | _animationController.forward().then((value) {
83 | _animationController.duration = widget.editModeSettings.duration;
84 | onAnimation = false;
85 | _last = null;
86 | _start = null;
87 | });
88 | });
89 | }
90 | fillRect = rect;
91 | }
92 |
93 | if (widget.layoutController.editSession == null ||
94 | !widget.editModeSettings.fillEditingBackground) {
95 | fillRect = null;
96 | _animation = null;
97 | onAnimation = false;
98 | _last = null;
99 | _animationController.duration = widget.editModeSettings.duration;
100 | _start = null;
101 | }
102 |
103 | if (_animation != null && offset == widget.offset.pixels) {
104 | offset = widget.offset.pixels;
105 | return AnimatedBuilder(
106 | animation: _animation!,
107 | builder: (context, child) {
108 | _last = _animation!.value;
109 | return CustomPaint(
110 | painter: _EditModeBackgroundPainter(
111 | verticalSlotEdge: widget.layoutController.verticalSlotEdge,
112 | fillPosition: _animation!.value,
113 | slotCount: widget.layoutController.slotCount,
114 | style: widget.editModeSettings.backgroundStyle,
115 | slotEdge: widget.layoutController.slotEdge,
116 | lines: widget.editModeSettings.paintBackgroundLines,
117 | offset: widget.offset.pixels,
118 | viewportDelegate: widget.layoutController._viewportDelegate),
119 | isComplex: true,
120 | );
121 | });
122 | } else {
123 | _last = null;
124 | onAnimation = false;
125 | _start = null;
126 | _animationController.duration = widget.editModeSettings.duration;
127 | offset = widget.offset.pixels;
128 | return CustomPaint(
129 | painter: _EditModeBackgroundPainter(
130 | fillPosition: fillRect,
131 | lines: widget.editModeSettings.paintBackgroundLines,
132 | verticalSlotEdge: widget.layoutController.verticalSlotEdge,
133 | slotCount: widget.layoutController.slotCount,
134 | style: widget.editModeSettings.backgroundStyle,
135 | slotEdge: widget.layoutController.slotEdge,
136 | offset: widget.offset.pixels,
137 | viewportDelegate: widget.layoutController._viewportDelegate),
138 | isComplex: false,
139 | );
140 | }
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/lib/src/widgets/dashboard_item_widget.dart:
--------------------------------------------------------------------------------
1 | part of '../dashboard_base.dart';
2 |
3 | class DashboardItemWidget extends InheritedWidget {
4 | const DashboardItemWidget(
5 | {required this.item, required super.child, super.key});
6 |
7 | final DashboardItem item;
8 |
9 | static DashboardItemWidget of(
10 | BuildContext context) {
11 | final DashboardItemWidget? result =
12 | context.dependOnInheritedWidgetOfExactType();
13 | assert(result != null, 'No DashboardItemWidget found in context');
14 | return result! as DashboardItemWidget;
15 | }
16 |
17 | @override
18 | bool updateShouldNotify(covariant DashboardItemWidget oldWidget) {
19 | return oldWidget.item.identifier != item.identifier;
20 | }
21 | }
22 |
23 | class _DashboardItemWidget extends StatefulWidget {
24 | const _DashboardItemWidget(
25 | {required Key key,
26 | required this.layoutController,
27 | required this.child,
28 | required this.editModeSettings,
29 | required this.id,
30 | required this.itemCurrentLayout,
31 | required this.itemGlobalPosition,
32 | required this.offset,
33 | required this.style})
34 | : super(key: key);
35 |
36 | final _ItemCurrentLayout itemCurrentLayout;
37 | final Widget child;
38 | final String id;
39 | final _DashboardLayoutController layoutController;
40 | final EditModeSettings editModeSettings;
41 | final _ItemCurrentPosition itemGlobalPosition;
42 | final ViewportOffset offset;
43 | final ItemStyle style;
44 |
45 | @override
46 | State<_DashboardItemWidget> createState() => _DashboardItemWidgetState();
47 | }
48 |
49 | class _DashboardItemWidgetState extends State<_DashboardItemWidget>
50 | with TickerProviderStateMixin {
51 | late MouseCursor cursor;
52 |
53 | // late double leftPad, rightPad, topPad, bottomPad;
54 |
55 | @override
56 | void dispose() {
57 | _animationController.dispose();
58 | _multiplierAnimationController.dispose();
59 | widget.itemCurrentLayout.removeListener(_listen);
60 | super.dispose();
61 | }
62 |
63 | _listen() {
64 | setState(() {});
65 | }
66 |
67 | late AnimationController _multiplierAnimationController;
68 |
69 | @override
70 | void initState() {
71 | cursor = MouseCursor.defer;
72 | _animationController = AnimationController(
73 | vsync: this, duration: widget.editModeSettings.duration);
74 | _multiplierAnimationController = AnimationController(
75 | vsync: this, value: 0, duration: widget.editModeSettings.duration);
76 | widget.itemCurrentLayout.addListener(_listen);
77 | super.initState();
78 | }
79 |
80 | _ItemCurrentPosition? get _resizePosition =>
81 | widget.itemCurrentLayout._resizePosition?.value;
82 |
83 | bool onRightSide(double dX) =>
84 | dX >
85 | (widget.itemGlobalPosition.width + (_resizePosition?.width ?? 0)) -
86 | widget.editModeSettings.resizeCursorSide;
87 |
88 | bool onLeftSide(double dX) =>
89 | (dX + (_resizePosition?.x ?? 0)) <
90 | widget.editModeSettings.resizeCursorSide;
91 |
92 | bool onTopSide(double dY) =>
93 | (dY + (_resizePosition?.y ?? 0)) <
94 | widget.editModeSettings.resizeCursorSide;
95 |
96 | bool onBottomSide(double dY) =>
97 | dY >
98 | (widget.itemGlobalPosition.height + (_resizePosition?.height ?? 0)) -
99 | widget.editModeSettings.resizeCursorSide;
100 |
101 | void _hover(PointerHoverEvent hover) {
102 | var x = hover.localPosition.dx;
103 | var y = hover.localPosition.dy;
104 | MouseCursor cursor;
105 | var r = onRightSide(x);
106 | var l = onLeftSide(x);
107 | var t = onTopSide(y);
108 | var b = onBottomSide(y);
109 | if (r) {
110 | if (b) {
111 | cursor = SystemMouseCursors.resizeUpLeftDownRight;
112 | } else if (t) {
113 | cursor = SystemMouseCursors.resizeUpRightDownLeft;
114 | } else {
115 | cursor = SystemMouseCursors.resizeLeftRight;
116 | }
117 | } else if (l) {
118 | if (b) {
119 | cursor = SystemMouseCursors.resizeUpRightDownLeft;
120 | } else if (t) {
121 | cursor = SystemMouseCursors.resizeUpLeftDownRight;
122 | } else {
123 | cursor = SystemMouseCursors.resizeLeftRight;
124 | }
125 | } else if (b || t) {
126 | cursor = SystemMouseCursors.resizeUpDown;
127 | } else {
128 | cursor = SystemMouseCursors.move;
129 | }
130 | if (this.cursor != cursor) {
131 | setState(() {
132 | this.cursor = cursor;
133 | });
134 | }
135 | }
136 |
137 | void _exit(PointerExitEvent exit) {
138 | setState(() {
139 | cursor = MouseCursor.defer;
140 | });
141 | }
142 |
143 | Offset transform = Offset.zero;
144 |
145 | Offset? panStart;
146 |
147 | double scrollOffset = 0;
148 |
149 | double startScrollOffset = 0;
150 |
151 | _ItemCurrentLayout get l => widget.itemCurrentLayout;
152 |
153 | double get slotEdge => widget.layoutController.slotEdge;
154 |
155 | _ItemCurrentPosition? ex;
156 |
157 | late AnimationController _animationController;
158 | Animation<_ItemCurrentPosition>? _animation;
159 |
160 | Offset? _lastTransform;
161 | _ItemCurrentPosition? _lastPosition;
162 |
163 | Future _setLast(
164 | Offset? lastOffset, _ItemCurrentPosition? lastPosition) async {
165 | _lastTransform = lastOffset;
166 | _lastPosition = lastPosition;
167 | _multiplierAnimationController.reset();
168 | _multiplierAnimationController.value = 1;
169 | await _multiplierAnimationController
170 | .animateTo(0,
171 | duration: widget.editModeSettings.duration,
172 | curve: widget.editModeSettings.curve)
173 | .then((value) {
174 | setState(() {
175 | _lastTransform = null;
176 | _lastPosition = null;
177 | });
178 | });
179 | }
180 |
181 | bool get onEditMode => widget.layoutController.isEditing;
182 |
183 | ItemLayout? _exLayout;
184 |
185 | bool equal() {
186 | return _exLayout!.startX == widget.itemCurrentLayout.startX &&
187 | _exLayout!.startY == widget.itemCurrentLayout.startY &&
188 | _exLayout!.width == widget.itemCurrentLayout.width &&
189 | _exLayout!.height == widget.itemCurrentLayout.height;
190 | }
191 |
192 | bool onAnimation = false;
193 | DateTime? animationStart;
194 |
195 | @override
196 | Widget build(BuildContext context) {
197 | Widget result = widget.child;
198 |
199 | if (onEditMode) {
200 | if (widget.layoutController.absorbPointer) {
201 | result = AbsorbPointer(child: result);
202 | }
203 | result = MouseRegion(
204 | cursor: cursor,
205 | onHover: _hover,
206 | onExit: _exit,
207 | child: result,
208 | );
209 | }
210 |
211 | var currentEdit = widget.layoutController.editSession?.editing.id ==
212 | widget.itemCurrentLayout.id;
213 |
214 | var transform =
215 | currentEdit ? widget.layoutController.editSession!.transform : false;
216 |
217 | var onlyDimensions = currentEdit && transform;
218 |
219 | if (onAnimation ||
220 | ((currentEdit ? transform : true) &&
221 | (onEditMode || widget.layoutController.animateEverytime) &&
222 | ex != null &&
223 | (widget.itemCurrentLayout._change))) {
224 | widget.itemCurrentLayout._change = false;
225 |
226 | if (onAnimation) {
227 | ex = _animation!.value;
228 |
229 | var difMicro = (widget.editModeSettings.duration -
230 | (DateTime.now().difference(animationStart!).abs()))
231 | .inMicroseconds;
232 | _animationController.duration = Duration(
233 | microseconds: difMicro.clamp(
234 | 0, widget.editModeSettings.duration.inMicroseconds));
235 | } else {
236 | animationStart = DateTime.now();
237 | onAnimation = true;
238 | }
239 | _animationController.reset();
240 |
241 | _animation = CurvedAnimation(
242 | parent: _animationController,
243 | curve: widget.editModeSettings.curve)
244 | .drive(_ItemCurrentPositionTween(
245 | begin: onlyDimensions
246 | ? _ItemCurrentPosition(
247 | height: ex!.height,
248 | width: ex!.width,
249 | y: widget.itemGlobalPosition.y,
250 | x: widget.itemGlobalPosition.x)
251 | : ex!,
252 | end: widget.itemGlobalPosition,
253 | onlyDimensions: onlyDimensions));
254 |
255 | _animationController.forward().then((value) {
256 | onAnimation = false;
257 | animationStart = null;
258 | _animationController.duration = widget.editModeSettings.duration;
259 | _animation = null;
260 | widget.itemCurrentLayout._change = false;
261 | ex = widget.itemGlobalPosition;
262 | });
263 | } else {
264 | ex = widget.itemGlobalPosition;
265 | widget.itemCurrentLayout._change = false;
266 | }
267 | if (!onEditMode && !widget.layoutController.animateEverytime) {
268 | var cp = widget.itemGlobalPosition;
269 | return Positioned(
270 | left: cp.x,
271 | top: cp.y - widget.offset.pixels,
272 | width: cp.width,
273 | height: cp.height,
274 | child: result);
275 | }
276 |
277 | return AnimatedBuilder(
278 | animation: Listenable.merge([
279 | if (widget.itemCurrentLayout._resizePosition != null)
280 | widget.itemCurrentLayout._resizePosition,
281 | if (widget.itemCurrentLayout._transform != null)
282 | widget.itemCurrentLayout._transform,
283 | if (_animation != null) _animation,
284 | if (onEditMode) _multiplierAnimationController,
285 | ]),
286 | child: result,
287 | builder: (c, w) {
288 | var m = _multiplierAnimationController.value;
289 |
290 | var p = widget.itemCurrentLayout._resizePosition?.value;
291 |
292 | var cp = onAnimation
293 | ? (_animation?.value ?? widget.itemGlobalPosition)
294 | : widget.itemGlobalPosition;
295 |
296 | if (p != null) {
297 | if (_lastPosition != null) {
298 | p = _lastPosition! * m;
299 | }
300 | cp += p;
301 | }
302 | double left = cp.x, top = cp.y;
303 |
304 | var o = widget.itemCurrentLayout._transform?.value;
305 |
306 | if (o != null) {
307 | if (_lastTransform != null) {
308 | o = _lastTransform! * m;
309 | }
310 | left += o.dx;
311 | top += o.dy;
312 | }
313 |
314 | var wi = cp.width;
315 |
316 | if (!widget.editModeSettings.draggableOutside) {
317 | final constraints =
318 | widget.layoutController._viewportDelegate.constraints;
319 | final maxW = constraints.maxWidth;
320 |
321 | if (left < 0) {
322 | left = 0;
323 | } else if (left + wi > maxW) {
324 | left = maxW - wi;
325 | }
326 |
327 | if (!widget.editModeSettings.autoScroll) {
328 | if (o != null) {
329 | final maxH = constraints.maxHeight;
330 | final hi = cp.height;
331 | final scrollOffset = widget.offset.pixels;
332 |
333 | if (top < scrollOffset) {
334 | top = scrollOffset;
335 | } else if (top + hi > maxH + scrollOffset) {
336 | top = maxH + scrollOffset - hi;
337 | }
338 | }
339 | }
340 | }
341 |
342 | return Positioned(
343 | left: left,
344 | top: top - widget.offset.pixels,
345 | width: cp.width,
346 | height: cp.height,
347 | child: w!);
348 | },
349 | );
350 | }
351 | }
352 |
--------------------------------------------------------------------------------
/lib/src/widgets/dashboard_stack.dart:
--------------------------------------------------------------------------------
1 | part of '../dashboard_base.dart';
2 |
3 | class _DashboardStack extends StatefulWidget {
4 | const _DashboardStack(
5 | {super.key,
6 | required this.editModeSettings,
7 | required this.offset,
8 | required this.dashboardController,
9 | required this.itemBuilder,
10 | required this.cacheExtend,
11 | required this.maxScrollOffset,
12 | required this.onScrollStateChange,
13 | required this.shouldCalculateNewDimensions,
14 | required this.itemStyle,
15 | required this.emptyPlaceholder,
16 | required this.slotBackground});
17 |
18 | final Widget? emptyPlaceholder;
19 | final ViewportOffset offset;
20 | final _DashboardLayoutController dashboardController;
21 | final double cacheExtend;
22 | final EditModeSettings editModeSettings;
23 | final SlotBackgroundBuilder? slotBackground;
24 | final double maxScrollOffset;
25 | final void Function(bool scrollable) onScrollStateChange;
26 |
27 | ///
28 | final DashboardItemBuilder itemBuilder;
29 |
30 | final ItemStyle itemStyle;
31 |
32 | final void Function() shouldCalculateNewDimensions;
33 |
34 | @override
35 | State<_DashboardStack> createState() => _DashboardStackState();
36 | }
37 |
38 | class _DashboardStackState
39 | extends State<_DashboardStack> {
40 | ViewportOffset get viewportOffset => widget.offset;
41 |
42 | _ViewportDelegate get viewportDelegate =>
43 | widget.dashboardController._viewportDelegate;
44 |
45 | double get pixels => viewportOffset.pixels;
46 |
47 | double get width => viewportDelegate.resolvedConstrains.maxWidth;
48 |
49 | double get height => viewportDelegate.resolvedConstrains.maxHeight;
50 |
51 | @override
52 | void didUpdateWidget(covariant _DashboardStack old) {
53 | _widgetsMap.clear();
54 | super.didUpdateWidget(old);
55 | }
56 |
57 | ///
58 | _listenOffset(ViewportOffset o) {
59 | setState(() {});
60 | o.removeListener(_listen);
61 | o.addListener(_listen);
62 | }
63 |
64 | ///
65 | @override
66 | void didChangeDependencies() {
67 | _widgetsMap.clear();
68 | super.didChangeDependencies();
69 | }
70 |
71 | @override
72 | void initState() {
73 | _widgetsMap.clear();
74 | super.initState();
75 | }
76 |
77 | @override
78 | void dispose() {
79 | viewportOffset.removeListener(_listen);
80 | super.dispose();
81 | }
82 |
83 | void _listen() {
84 | setState(() {});
85 | }
86 |
87 | Widget buildPositioned(List list) {
88 | return _DashboardItemWidget(
89 | style: widget.itemStyle,
90 | key: _keys[list[2]]!,
91 | itemGlobalPosition: (list[0] as _ItemCurrentLayout)._currentPosition(
92 | viewportDelegate: viewportDelegate,
93 | slotEdge: slotEdge,
94 | verticalSlotEdge: verticalSlotEdge),
95 | itemCurrentLayout: list[0],
96 | id: list[2],
97 | editModeSettings: widget.editModeSettings,
98 | child: list[1],
99 | offset: viewportOffset,
100 | layoutController: widget.dashboardController,
101 | );
102 | }
103 |
104 | late double slotEdge;
105 | late double verticalSlotEdge;
106 | final Map _widgetsMap = {};
107 |
108 | void addWidget(String id) {
109 | var i = widget.dashboardController.itemController._items[id];
110 | var l = widget.dashboardController._layouts![i!.identifier]!;
111 | i.layoutData = l.asLayout();
112 |
113 | _widgetsMap[id] = [
114 | l,
115 | DashboardItemWidget(
116 | item: i,
117 | child: Material(
118 | elevation: widget.itemStyle.elevation ?? 0.0,
119 | type: widget.itemStyle.type ?? MaterialType.card,
120 | shape: widget.itemStyle.shape,
121 | color: widget.itemStyle.color,
122 | clipBehavior: widget.itemStyle.clipBehavior ?? Clip.none,
123 | animationDuration:
124 | widget.itemStyle.animationDuration ?? kThemeChangeDuration,
125 | child: widget.itemBuilder(i),
126 | //shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
127 | )),
128 | id,
129 | ];
130 |
131 | _keys[id] ??= GlobalKey<_DashboardItemWidgetState>();
132 | l._key = _keys[id]!;
133 | }
134 |
135 | final Map> _keys = {};
136 |
137 | late int startIndex, endIndex;
138 |
139 | List _buildBackground() {
140 | final res = [];
141 |
142 | int i = startIndex;
143 |
144 | var l =
145 | viewportDelegate.padding.left + (viewportDelegate.crossAxisSpace / 2);
146 | var t = viewportDelegate.padding.top -
147 | pixels +
148 | (viewportDelegate.mainAxisSpace / 2);
149 | var w = slotEdge - viewportDelegate.crossAxisSpace;
150 | var h = verticalSlotEdge - viewportDelegate.mainAxisSpace;
151 |
152 | while (i <= endIndex) {
153 | var x = i % widget.dashboardController.slotCount;
154 | var y = (i / widget.dashboardController.slotCount).floor();
155 | widget.slotBackground!._itemController =
156 | widget.dashboardController.itemController;
157 | res.add(Positioned(
158 | left: x * slotEdge + l,
159 | top: y * verticalSlotEdge + t,
160 | width: w,
161 | height: h,
162 | child: Builder(
163 | builder: (c) {
164 | return widget.slotBackground!._build(context, x, y);
165 | },
166 | ),
167 | ));
168 | i++;
169 | }
170 |
171 | return res;
172 | }
173 |
174 | @override
175 | Widget build(BuildContext context) {
176 | if (widget.dashboardController._rebuild) {
177 | _widgetsMap.clear();
178 | widget.dashboardController._rebuild = false;
179 | }
180 |
181 | slotEdge = widget.dashboardController.slotEdge;
182 | verticalSlotEdge = widget.dashboardController.verticalSlotEdge;
183 | var startPixels = (viewportOffset.pixels) - widget.cacheExtend;
184 | var startY = (startPixels / verticalSlotEdge).floor();
185 |
186 | startIndex = widget.dashboardController.getIndex([0, startY]);
187 |
188 | var endPixels = viewportOffset.pixels + height + widget.cacheExtend;
189 | var endY = (endPixels / verticalSlotEdge).ceil();
190 | endIndex = widget.dashboardController
191 | .getIndex([widget.dashboardController.slotCount - 1, endY]);
192 |
193 | var needs = [];
194 | var key = startIndex;
195 |
196 | if (widget.dashboardController._indexesTree[key] != null) {
197 | needs.add(widget.dashboardController._indexesTree[key]!);
198 | }
199 |
200 | while (true) {
201 | var f = widget.dashboardController._indexesTree.firstKeyAfter(key);
202 | if (f != null) {
203 | key = f;
204 | needs.add(widget.dashboardController._indexesTree[key]!);
205 | if (key >= endIndex) {
206 | break;
207 | }
208 | } else {
209 | break;
210 | }
211 | }
212 |
213 | var beforeIt = [];
214 | key = startIndex;
215 | while (true) {
216 | var f = widget.dashboardController._indexesTree.lastKeyBefore(key);
217 | if (f != null) {
218 | key = f;
219 | beforeIt.add(widget.dashboardController._indexesTree[key]!);
220 | } else {
221 | break;
222 | }
223 | }
224 |
225 | var afterIt = [];
226 | key = startIndex;
227 | while (true) {
228 | var f = widget.dashboardController._indexesTree.firstKeyAfter(key);
229 | if (f != null) {
230 | key = f;
231 | afterIt.add(widget.dashboardController._indexesTree[key]!);
232 | } else {
233 | break;
234 | }
235 | }
236 |
237 | var needDelete = [...afterIt, ...beforeIt];
238 |
239 | var edit = widget.dashboardController.editSession?.editing;
240 |
241 | for (var n in needDelete) {
242 | if (!needs.contains(n) && n != edit?.id) {
243 | _widgetsMap.remove(n);
244 | }
245 | }
246 |
247 | for (var n in needs) {
248 | if (!_widgetsMap.containsKey(n)) {
249 | addWidget(n);
250 | }
251 | }
252 |
253 | if (edit != null && !_widgetsMap.containsKey(edit.id)) {
254 | _widgetsMap.remove(edit.id);
255 | _keys.remove(edit.id);
256 | addWidget(edit.id);
257 | }
258 |
259 | Widget result = Stack(
260 | clipBehavior: Clip.hardEdge,
261 | children: [
262 | if (widget.slotBackground != null) ..._buildBackground(),
263 | if (widget.dashboardController.isEditing)
264 | Positioned(
265 | top: viewportDelegate.padding.top,
266 | left: viewportDelegate.padding.left,
267 | width: viewportDelegate.constraints.maxWidth -
268 | viewportDelegate.padding.vertical,
269 | height: viewportDelegate.constraints.maxHeight -
270 | viewportDelegate.padding.horizontal,
271 | child: Builder(builder: (context) {
272 | return _AnimatedBackgroundPainter(
273 | layoutController: widget.dashboardController,
274 | editModeSettings: widget.editModeSettings,
275 | offset: viewportOffset);
276 | }),
277 | ),
278 | ..._widgetsMap.entries
279 | .where((element) =>
280 | element.value[2] !=
281 | widget.dashboardController.editSession?.editing.id)
282 | .map((e) {
283 | return buildPositioned(e.value);
284 | }),
285 | if (widget.dashboardController.itemController._items.isEmpty &&
286 | !widget.dashboardController._isEditing)
287 | Positioned(
288 | bottom: 0,
289 | left: 0,
290 | right: 0,
291 | top: 0,
292 | child: widget.emptyPlaceholder ?? Container()),
293 | ...?(widget.dashboardController.editSession == null
294 | ? null
295 | : [
296 | buildPositioned(_widgetsMap[
297 | widget.dashboardController.editSession?.editing.id]!)
298 | ])
299 | ],
300 | );
301 |
302 | if (widget.dashboardController.isEditing) {
303 | result = GestureDetector(
304 | onPanStart: widget.editModeSettings.panEnabled
305 | ? (panStart) {
306 | _onMoveStart(panStart.localPosition);
307 | }
308 | : null,
309 | onPanUpdate: widget.editModeSettings.panEnabled &&
310 | edit != null &&
311 | edit.id.isNotEmpty
312 | ? (u) {
313 | setSpeed(u.localPosition);
314 | _onMoveUpdate(u.localPosition);
315 | }
316 | : null,
317 | onPanEnd: widget.editModeSettings.panEnabled
318 | ? (e) {
319 | _onMoveEnd();
320 | }
321 | : null,
322 | onLongPressStart: widget.editModeSettings.longPressEnabled
323 | ? (longPressStart) {
324 | _onMoveStart(longPressStart.localPosition);
325 | }
326 | : null,
327 | onLongPressMoveUpdate: widget.editModeSettings.longPressEnabled &&
328 | edit != null &&
329 | edit.id.isNotEmpty
330 | ? (u) {
331 | setSpeed(u.localPosition);
332 | _onMoveUpdate(u.localPosition);
333 | }
334 | : null,
335 | onLongPressEnd: widget.editModeSettings.longPressEnabled
336 | ? (e) {
337 | _onMoveEnd();
338 | }
339 | : null,
340 | child: result,
341 | );
342 | }
343 | return result;
344 | }
345 |
346 | void setSpeed(Offset global) {
347 | if (!widget.editModeSettings.autoScroll) {
348 | speed = 0;
349 | return;
350 | }
351 |
352 | var last = min((height - global.dy), global.dy);
353 | var m = global.dy < 50 ? -1 : 1;
354 | if (last < 10) {
355 | speed = 0.3 * m;
356 | } else if (last < 20) {
357 | speed = 0.1 * m;
358 | } else if (last < 50) {
359 | speed = 0.05 * m;
360 | } else {
361 | speed = 0;
362 | }
363 | scroll();
364 | }
365 |
366 | void scroll() {
367 | SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
368 | try {
369 | if (speed != 0) {
370 | var n = pixels + speed;
371 |
372 |
373 | viewportOffset.jumpTo(n.clamp(0.0, (1 << 31).toDouble()));
374 |
375 |
376 | scroll();
377 | }
378 | } catch (e) {
379 | rethrow;
380 | }
381 | });
382 | }
383 |
384 | @override
385 | void reassemble() {
386 | _widgetsMap.clear();
387 | super.reassemble();
388 | }
389 |
390 | double speed = 0;
391 |
392 | Offset holdOffset = Offset.zero;
393 |
394 | void _onMoveStart(Offset local) {
395 | var holdGlobal = Offset(local.dx - viewportDelegate.padding.left,
396 | local.dy - viewportDelegate.padding.top);
397 |
398 | var x = (local.dx - viewportDelegate.padding.left) ~/ slotEdge;
399 | var y =
400 | (local.dy + pixels - viewportDelegate.padding.top) ~/ verticalSlotEdge;
401 |
402 | var e = widget.dashboardController
403 | ._indexesTree[widget.dashboardController.getIndex([x, y])];
404 |
405 | if (e is String) {
406 | var directions = [];
407 | _editing = widget.dashboardController._layouts![e]!;
408 | var current = _editing!._currentPosition(
409 | slotEdge: slotEdge,
410 | viewportDelegate: viewportDelegate,
411 | verticalSlotEdge: verticalSlotEdge);
412 | var itemGlobal = _ItemCurrentPosition(
413 | x: current.x - viewportDelegate.padding.left,
414 | y: current.y - viewportDelegate.padding.top - pixels,
415 | height: current.height,
416 | width: current.width);
417 | if (holdGlobal.dx < itemGlobal.x || holdGlobal.dy < itemGlobal.y) {
418 | _editing = null;
419 | setState(() {});
420 | return;
421 | }
422 | if (itemGlobal.x + widget.editModeSettings.resizeCursorSide >
423 | holdGlobal.dx) {
424 | directions.add(AxisDirection.left);
425 | }
426 |
427 | if ((itemGlobal.y) + widget.editModeSettings.resizeCursorSide >
428 | holdGlobal.dy) {
429 | directions.add(AxisDirection.up);
430 | }
431 |
432 | if (itemGlobal.endX - widget.editModeSettings.resizeCursorSide <
433 | holdGlobal.dx) {
434 | directions.add(AxisDirection.right);
435 | }
436 |
437 | if ((itemGlobal.endY) - widget.editModeSettings.resizeCursorSide <
438 | holdGlobal.dy) {
439 | directions.add(AxisDirection.down);
440 | }
441 | if (directions.isNotEmpty) {
442 | _holdDirections = directions;
443 | } else {
444 | _holdDirections = null;
445 | }
446 | _moveStartOffset = local;
447 | _startScrollPixels = pixels;
448 | widget.dashboardController.startEdit(e, _holdDirections == null);
449 |
450 | holdOffset = holdGlobal - Offset(itemGlobal.x, itemGlobal.y);
451 |
452 | var l = widget.dashboardController._layouts![e];
453 | widget.dashboardController.editSession!.editing._originSize = [
454 | l!.width,
455 | l.height
456 | ];
457 | setState(() {});
458 | widget.onScrollStateChange(false);
459 | } else {
460 | _moveStartOffset = null;
461 | _editing = null;
462 | _holdDirections = null;
463 | widget.dashboardController.editSession?.editing._originSize = null;
464 | speed = 0;
465 | widget.dashboardController.saveEditSession();
466 | widget.onScrollStateChange(true);
467 | }
468 | }
469 |
470 | _ItemCurrentLayout? _editing;
471 |
472 | bool get _editingResize => _holdDirections != null;
473 | List? _holdDirections;
474 | Offset? _moveStartOffset;
475 | double? _startScrollPixels;
476 |
477 | bool isResizing(AxisDirection direction) =>
478 | _holdDirections!.contains(direction);
479 |
480 | void _onMoveUpdate(Offset local) {
481 | if (_editing == null) {
482 | return;
483 | }
484 |
485 | var e = widget.dashboardController._endsTree.lastKey() ?? 0;
486 |
487 | if (_editingResize) {
488 | var scrollDifference = pixels - _startScrollPixels!;
489 | var differences = {};
490 | var resizeMoveResult = _editing!._resizeMove(
491 | holdDirections: _holdDirections!,
492 | local: local,
493 | onChange: (s) {
494 | differences.add(s);
495 | },
496 | start: _moveStartOffset!,
497 | scrollDifference: scrollDifference);
498 |
499 | if (resizeMoveResult.isChanged) {
500 | setState(() {
501 | _moveStartOffset =
502 | _moveStartOffset! + resizeMoveResult.startDifference;
503 | _widgetsMap.remove(_editing!.id);
504 | for (var r in differences) {
505 | _widgetsMap.remove(r);
506 | }
507 | if (_editing!._endIndex > (e)) {
508 | widget.shouldCalculateNewDimensions();
509 | }
510 | });
511 | }
512 | } else {
513 | var resizeMoveResult = _editing!._transformUpdate(
514 | local - _moveStartOffset!, pixels - _startScrollPixels!, holdOffset);
515 |
516 | if (resizeMoveResult != null && resizeMoveResult.isChanged) {
517 | setState(() {
518 | _moveStartOffset =
519 | _moveStartOffset! + resizeMoveResult.startDifference;
520 | _widgetsMap.remove(_editing!.id);
521 |
522 | if (_editing!._endIndex > (e)) {
523 | widget.shouldCalculateNewDimensions();
524 | }
525 | });
526 | }
527 | }
528 | }
529 |
530 | void _onMoveEnd() {
531 | _editing?._key = _keys[_editing!.id]!;
532 | _editing?._key.currentState
533 | ?._setLast(
534 | _editing!._transform?.value, _editing!._resizePosition?.value)
535 | .then((value) {
536 | widget.dashboardController.editSession?.editing._originSize = null;
537 | _editing?._clearListeners();
538 | _editing = null;
539 | _moveStartOffset = null;
540 | _holdDirections = null;
541 | _startScrollPixels = null;
542 | widget.dashboardController.saveEditSession();
543 | });
544 | speed = 0;
545 | widget.onScrollStateChange(true);
546 | }
547 | }
548 |
--------------------------------------------------------------------------------
/lib/src/widgets/grid_builder.dart:
--------------------------------------------------------------------------------
1 | part of '../dashboard_base.dart';
2 |
3 | /// Slot background builder.
4 | abstract class SlotBackgroundBuilder {
5 | SlotBackgroundBuilder();
6 |
7 | /// Create a builder with a function.
8 | static SlotBackgroundBuilder withFunction(
9 | Widget? Function(
10 | BuildContext context, T? item, int x, int y, bool editing)
11 | builder) {
12 | return _WithFunctionSlotBackgroundBuilder(builder);
13 | }
14 |
15 | DashboardItemController? _itemController;
16 |
17 | Widget _build(BuildContext context, int x, int y) {
18 | final layoutController = _itemController!._layoutController!;
19 | final i = layoutController._indexesTree[layoutController.getIndex([x, y])];
20 |
21 | T? item;
22 |
23 | if (i != null) {
24 | item = layoutController.itemController._items[i] as T;
25 | }
26 |
27 | return buildBackground(context, item, x, y, layoutController._isEditing) ??
28 | Container();
29 | }
30 |
31 | /// Build background widget.
32 | Widget? buildBackground(
33 | BuildContext context, T? item, int x, int y, bool editing);
34 | }
35 |
36 | class _WithFunctionSlotBackgroundBuilder
37 | extends SlotBackgroundBuilder {
38 | final Widget? Function(
39 | BuildContext context, T? item, int x, int y, bool editing) builder;
40 |
41 | _WithFunctionSlotBackgroundBuilder(this.builder);
42 |
43 | @override
44 | Widget? buildBackground(
45 | BuildContext context, T? item, int x, int y, bool editing) {
46 | return builder(context, item, x, y, editing);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/lib/src/widgets/style.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class ItemStyle {
4 | const ItemStyle({
5 | this.color,
6 | this.shadowColor,
7 | this.shape,
8 | this.borderOnForeground,
9 | this.clipBehavior,
10 | this.animationDuration,
11 | this.type,
12 | this.elevation,
13 | this.borderRadius,
14 | this.textStyle,
15 | });
16 |
17 | /// The kind of material to show (e.g., card or canvas). This
18 | /// affects the shape of the widget, the roundness of its corners if
19 | /// the shape is rectangular, and the default color.
20 | final MaterialType? type;
21 |
22 | /// {@template flutter.material.material.elevation}
23 | /// The z-coordinate at which to place this material relative to its parent.
24 | ///
25 | /// This controls the size of the shadow below the material and the opacity
26 | /// of the elevation overlay color if it is applied.
27 | ///
28 | /// If this is non-zero, the contents of the material are clipped, because the
29 | /// widget conceptually defines an independent printed piece of material.
30 | ///
31 | /// Defaults to 0. Changing this value will cause the shadow and the elevation
32 | /// overlay to animate over [Material.animationDuration].
33 | ///
34 | /// The value is non-negative.
35 | ///
36 | /// See also:
37 | ///
38 | /// * [ThemeData.applyElevationOverlayColor] which controls the whether
39 | /// an overlay color will be applied to indicate elevation.
40 | /// * [Material.color] which may have an elevation overlay applied.
41 | ///
42 | /// {@endtemplate}
43 | final double? elevation;
44 |
45 | /// The color to paint the material.
46 | ///
47 | /// Must be opaque. To create a transparent piece of material, use
48 | /// [MaterialType.transparency].
49 | ///
50 | /// To support dark themes, if the surrounding
51 | /// [ThemeData.applyElevationOverlayColor] is true and [ThemeData.brightness]
52 | /// is [Brightness.dark] then a semi-transparent overlay color will be
53 | /// composited on top of this color to indicate the elevation.
54 | ///
55 | /// By default, the color is derived from the [type] of material.
56 | final Color? color;
57 |
58 | /// The color to paint the shadow below the material.
59 | ///
60 | /// If null, [ThemeData.shadowColor] is used, which defaults to fully opaque black.
61 | ///
62 | /// Shadows can be difficult to see in a dark theme, so the elevation of a
63 | /// surface should be portrayed with an "overlay" in addition to the shadow.
64 | /// As the elevation of the component increases, the overlay increases in
65 | /// opacity.
66 | ///
67 | /// See also:
68 | ///
69 | /// * [ThemeData.applyElevationOverlayColor], which turns elevation overlay
70 | /// on or off for dark themes.
71 | final Color? shadowColor;
72 |
73 | /// The typographical style to use for text within this material.
74 | final TextStyle? textStyle;
75 |
76 | /// Defines the material's shape as well its shadow.
77 | ///
78 | /// If shape is non null, the [borderRadius] is ignored and the material's
79 | /// clip boundary and shadow are defined by the shape.
80 | ///
81 | /// A shadow is only displayed if the [elevation] is greater than
82 | /// zero.
83 | final ShapeBorder? shape;
84 |
85 | /// Whether to paint the [shape] border in front of the [child].
86 | ///
87 | /// The default value is true.
88 | /// If false, the border will be painted behind the [child].
89 | final bool? borderOnForeground;
90 |
91 | /// {@template flutter.material.Material.clipBehavior}
92 | /// The content will be clipped (or not) according to this option.
93 | ///
94 | /// See the enum [Clip] for details of all possible options and their common
95 | /// use cases.
96 | /// {@endtemplate}
97 | ///
98 | /// Defaults to [Clip.none], and must not be null.
99 | final Clip? clipBehavior;
100 |
101 | /// Defines the duration of animated changes for [shape], [elevation],
102 | /// [shadowColor] and the elevation overlay if it is applied.
103 | ///
104 | /// The default value is [kThemeChangeDuration].
105 | final Duration? animationDuration;
106 |
107 | /// If non-null, the corners of this box are rounded by this
108 | /// [BorderRadiusGeometry] value.
109 | ///
110 | /// Otherwise, the corners specified for the current [type] of material are
111 | /// used.
112 | ///
113 | /// If [shape] is non null then the border radius is ignored.
114 | ///
115 | /// Must be null if [type] is [MaterialType.circle].
116 | final BorderRadiusGeometry? borderRadius;
117 | }
118 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: dashboard
2 | description: Dynamic dashboard widget that allows your users to create their own layouts. Resize, move, indirect resize/move, auto
3 | re-layout are supported.
4 | version: 0.0.5
5 | homepage: https://github.com/Mehmetyaz/dashboard
6 | repository: https://github.com/Mehmetyaz/dashboard
7 |
8 | environment:
9 | sdk: ">=2.17.0 <4.0.0"
10 |
11 | dependencies:
12 | flutter:
13 | sdk: flutter
14 |
15 | dev_dependencies:
16 | flutter_test:
17 | sdk: flutter
18 | flutter_lints: ^3.0.1
19 |
20 | flutter:
21 |
--------------------------------------------------------------------------------
/test/todo_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 |
3 | void main() {
4 | test("TODO", () {
5 | expect("wrote", "not");
6 | });
7 | }
8 |
--------------------------------------------------------------------------------