├── .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 | [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](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 | --------------------------------------------------------------------------------