├── .gitignore ├── .metadata ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ ├── build │ │ ├── intermediates │ │ │ ├── flutter │ │ │ │ └── release │ │ │ │ │ ├── .last_build_id │ │ │ │ │ ├── arm64-v8a │ │ │ │ │ └── app.so │ │ │ │ │ ├── armeabi-v7a │ │ │ │ │ └── app.so │ │ │ │ │ ├── flutter_assets │ │ │ │ │ ├── AssetManifest.bin │ │ │ │ │ ├── AssetManifest.json │ │ │ │ │ ├── FontManifest.json │ │ │ │ │ ├── NOTICES.Z │ │ │ │ │ ├── fonts │ │ │ │ │ │ └── MaterialIcons-Regular.otf │ │ │ │ │ ├── packages │ │ │ │ │ │ └── wakelock_plus │ │ │ │ │ │ │ └── assets │ │ │ │ │ │ │ └── no_sleep.js │ │ │ │ │ └── shaders │ │ │ │ │ │ └── ink_sparkle.frag │ │ │ │ │ ├── flutter_build.d │ │ │ │ │ ├── libs.jar │ │ │ │ │ └── x86_64 │ │ │ │ │ └── app.so │ │ │ └── incremental │ │ │ │ └── mergeReleaseJniLibFolders │ │ │ │ └── merger.xml │ │ └── tmp │ │ │ └── packJniLibsflutterBuildRelease │ │ │ └── MANIFEST.MF │ ├── keystore.jks │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ ├── com │ │ │ │ └── example │ │ │ │ │ └── reciper │ │ │ │ │ └── MainActivity.kt │ │ │ └── jdm │ │ │ │ └── apps │ │ │ │ └── reciper │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── build │ └── reports │ │ └── problems │ │ └── problems-report.html ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets └── icon.png ├── fastlane └── metadata │ └── en-US │ ├── full_description.txt │ ├── images │ ├── icon.png │ └── phoneScreenshots │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ └── 7.png │ ├── short_description.txt │ └── title.txt ├── lib ├── main.dart ├── models │ ├── recipe.dart │ ├── tag.dart │ └── tag_link.dart ├── screens │ ├── home.dart │ ├── pages_layout.dart │ ├── recipe_editor.dart │ ├── recipe_view.dart │ └── settings.dart ├── utilities │ ├── database.dart │ └── utils.dart └── widgets │ ├── bottom_nav_bar.dart │ ├── extract_recipe_button.dart │ ├── extract_recipe_dialog.dart │ ├── new_tag_dialog.dart │ ├── recipe_tag_selector.dart │ ├── reciper_list_tile.dart │ ├── recipes_list.dart │ ├── tag_actions_dialog.dart │ └── tags_selector.dart ├── pubspec.lock └── pubspec.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Symbolication related 35 | app.*.symbols 36 | 37 | # Obfuscation related 38 | app.*.map.json 39 | 40 | # Android Studio will place build artifacts here 41 | /android/app/debug 42 | /android/app/profile 43 | /android/app/release 44 | 45 | buildProd.gradle 46 | 47 | linux/ -------------------------------------------------------------------------------- /.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: "c519ee916eaeb88923e67befb89c0f1dabfa83e6" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6 17 | base_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6 18 | - platform: android 19 | create_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6 20 | base_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6 21 | - platform: ios 22 | create_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6 23 | base_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6 24 | - platform: linux 25 | create_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6 26 | base_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6 27 | - platform: macos 28 | create_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6 29 | base_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6 30 | - platform: web 31 | create_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6 32 | base_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6 33 | - platform: windows 34 | create_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6 35 | base_revision: c519ee916eaeb88923e67befb89c0f1dabfa83e6 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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🍳 Reciper - Your Ultimate Kitchen Companion! 📱 2 | 3 | --- 4 | 5 | --- 6 | --- 7 | 8 |
9 |
10 | 11 | 12 |
13 |
14 | 15 | 16 | 17 | **_Simple but Powerful Recipe Management App built with ❤️ and Flutter._** 18 |
19 | 1 20 | 2 21 | 3 22 | 4 23 | 5 24 | 5 25 | 5 26 |
27 | 28 | 29 | ### Features : 30 | 31 | - 📝 Organize and store all your favorite recipes in one convenient place. 32 | - 🌐 Easily import recipes from any website and customize them to your liking. 33 | - 📤 Export your recipes for easy sharing or backup. 34 | - 🎨 Enjoy a beautiful and stylish Material You design . 35 | - 🆓 Reciper is open source, ad-free, and completely free for a seamless culinary experience. 36 | 37 | ### Internet recipes extraction 38 | 39 | Extracting recipes from a website works using the [recipe_extractor](https://github.com/judemont/recipe_extractor) dart package created by me. 40 | 41 | 42 | | Supported recipe sites : | 43 | | ------------------------ | 44 | | marmiton.org | 45 | | allrecipes.com | 46 | | swissmilk.ch | 47 | | bbcgoodfood.com | 48 | | simplyrecipes.com | 49 | | recipetineats.com | 50 | | topsecretrecipes.com | 51 | | giallozafferano.it | 52 | | cucchiaio.it | 53 | 54 | More coming soon... 55 | -------------------------------------------------------------------------------- /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 https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | -------------------------------------------------------------------------------- /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 | 13 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | } 6 | 7 | def localProperties = new Properties() 8 | def localPropertiesFile = rootProject.file('local.properties') 9 | if (localPropertiesFile.exists()) { 10 | localPropertiesFile.withReader('UTF-8') { reader -> 11 | localProperties.load(reader) 12 | } 13 | } 14 | 15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 16 | if (flutterVersionCode == null) { 17 | flutterVersionCode = '1' 18 | } 19 | 20 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 21 | if (flutterVersionName == null) { 22 | flutterVersionName = '1.0' 23 | } 24 | 25 | def keystoreProperties = new Properties() 26 | def keystorePropertiesFile = rootProject.file('key.properties') 27 | if (keystorePropertiesFile.exists()) { 28 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 29 | } 30 | 31 | 32 | android { 33 | namespace "jdm.apps.reciper" 34 | compileSdk 35 35 | ndkVersion flutter.ndkVersion 36 | 37 | compileOptions { 38 | sourceCompatibility JavaVersion.VERSION_1_8 39 | targetCompatibility JavaVersion.VERSION_1_8 40 | } 41 | 42 | kotlinOptions { 43 | jvmTarget = '1.8' 44 | } 45 | 46 | sourceSets { 47 | main.java.srcDirs += 'src/main/kotlin' 48 | } 49 | 50 | defaultConfig { 51 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 52 | applicationId "jdm.apps.reciper" 53 | // You can update the following values to match your application needs. 54 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 55 | minSdkVersion flutter.minSdkVersion 56 | targetSdkVersion 34 57 | versionCode flutterVersionCode.toInteger() 58 | versionName flutterVersionName 59 | 60 | ndk { 61 | abiFilters "arm64-v8a", "armeabi-v7a" 62 | } 63 | } 64 | 65 | signingConfigs { 66 | release { 67 | keyAlias keystoreProperties['keyAlias'] 68 | keyPassword keystoreProperties['keyPassword'] 69 | storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null 70 | storePassword keystoreProperties['storePassword'] 71 | } 72 | } 73 | 74 | buildTypes { 75 | release { 76 | signingConfig signingConfigs.release 77 | } 78 | } 79 | 80 | 81 | 82 | 83 | 84 | } 85 | 86 | 87 | flutter { 88 | source '../..' 89 | } 90 | 91 | dependencies {} -------------------------------------------------------------------------------- /android/app/build/intermediates/flutter/release/.last_build_id: -------------------------------------------------------------------------------- 1 | 759e2ba362d97454fbc14571dc03ef9b -------------------------------------------------------------------------------- /android/app/build/intermediates/flutter/release/arm64-v8a/app.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judemont/reciper/48a203590abd8dfa5b76a61e6a797bffafcafd83/android/app/build/intermediates/flutter/release/arm64-v8a/app.so -------------------------------------------------------------------------------- /android/app/build/intermediates/flutter/release/armeabi-v7a/app.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judemont/reciper/48a203590abd8dfa5b76a61e6a797bffafcafd83/android/app/build/intermediates/flutter/release/armeabi-v7a/app.so -------------------------------------------------------------------------------- /android/app/build/intermediates/flutter/release/flutter_assets/AssetManifest.bin: -------------------------------------------------------------------------------- 1 | )packages/wakelock_plus/assets/no_sleep.js  asset)packages/wakelock_plus/assets/no_sleep.js -------------------------------------------------------------------------------- /android/app/build/intermediates/flutter/release/flutter_assets/AssetManifest.json: -------------------------------------------------------------------------------- 1 | {"packages/wakelock_plus/assets/no_sleep.js":["packages/wakelock_plus/assets/no_sleep.js"]} -------------------------------------------------------------------------------- /android/app/build/intermediates/flutter/release/flutter_assets/FontManifest.json: -------------------------------------------------------------------------------- 1 | [{"family":"MaterialIcons","fonts":[{"asset":"fonts/MaterialIcons-Regular.otf"}]}] -------------------------------------------------------------------------------- /android/app/build/intermediates/flutter/release/flutter_assets/NOTICES.Z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judemont/reciper/48a203590abd8dfa5b76a61e6a797bffafcafd83/android/app/build/intermediates/flutter/release/flutter_assets/NOTICES.Z -------------------------------------------------------------------------------- /android/app/build/intermediates/flutter/release/flutter_assets/fonts/MaterialIcons-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judemont/reciper/48a203590abd8dfa5b76a61e6a797bffafcafd83/android/app/build/intermediates/flutter/release/flutter_assets/fonts/MaterialIcons-Regular.otf -------------------------------------------------------------------------------- /android/app/build/intermediates/flutter/release/flutter_assets/packages/wakelock_plus/assets/no_sleep.js: -------------------------------------------------------------------------------- 1 | var webm = 2 | 'data:video/webm;base64,GkXfo0AgQoaBAUL3gQFC8oEEQvOBCEKCQAR3ZWJtQoeBAkKFgQIYU4BnQI0VSalmQCgq17FAAw9CQE2AQAZ3aGFtbXlXQUAGd2hhbW15RIlACECPQAAAAAAAFlSua0AxrkAu14EBY8WBAZyBACK1nEADdW5khkAFVl9WUDglhohAA1ZQOIOBAeBABrCBCLqBCB9DtnVAIueBAKNAHIEAAIAwAQCdASoIAAgAAUAmJaQAA3AA/vz0AAA=' 3 | var mp4 = 4 | 'data:video/mp4;base64,AAAAIGZ0eXBtcDQyAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAACKBtZGF0AAAC8wYF///v3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE0MiByMjQ3OSBkZDc5YTYxIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAxNCAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTEgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MToweDExMSBtZT1oZXggc3VibWU9MiBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0wIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MCA4eDhkY3Q9MCBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0wIHRocmVhZHM9NiBsb29rYWhlYWRfdGhyZWFkcz0xIHNsaWNlZF90aHJlYWRzPTAgbnI9MCBkZWNpbWF0ZT0xIGludGVybGFjZWQ9MCBibHVyYXlfY29tcGF0PTAgY29uc3RyYWluZWRfaW50cmE9MCBiZnJhbWVzPTMgYl9weXJhbWlkPTIgYl9hZGFwdD0xIGJfYmlhcz0wIGRpcmVjdD0xIHdlaWdodGI9MSBvcGVuX2dvcD0wIHdlaWdodHA9MSBrZXlpbnQ9MzAwIGtleWludF9taW49MzAgc2NlbmVjdXQ9NDAgaW50cmFfcmVmcmVzaD0wIHJjX2xvb2thaGVhZD0xMCByYz1jcmYgbWJ0cmVlPTEgY3JmPTIwLjAgcWNvbXA9MC42MCBxcG1pbj0wIHFwbWF4PTY5IHFwc3RlcD00IHZidl9tYXhyYXRlPTIwMDAwIHZidl9idWZzaXplPTI1MDAwIGNyZl9tYXg9MC4wIG5hbF9ocmQ9bm9uZSBmaWxsZXI9MCBpcF9yYXRpbz0xLjQwIGFxPTE6MS4wMACAAAAAOWWIhAA3//p+C7v8tDDSTjf97w55i3SbRPO4ZY+hkjD5hbkAkL3zpJ6h/LR1CAABzgB1kqqzUorlhQAAAAxBmiQYhn/+qZYADLgAAAAJQZ5CQhX/AAj5IQADQGgcIQADQGgcAAAACQGeYUQn/wALKCEAA0BoHAAAAAkBnmNEJ/8ACykhAANAaBwhAANAaBwAAAANQZpoNExDP/6plgAMuSEAA0BoHAAAAAtBnoZFESwr/wAI+SEAA0BoHCEAA0BoHAAAAAkBnqVEJ/8ACykhAANAaBwAAAAJAZ6nRCf/AAsoIQADQGgcIQADQGgcAAAADUGarDRMQz/+qZYADLghAANAaBwAAAALQZ7KRRUsK/8ACPkhAANAaBwAAAAJAZ7pRCf/AAsoIQADQGgcIQADQGgcAAAACQGe60Qn/wALKCEAA0BoHAAAAA1BmvA0TEM//qmWAAy5IQADQGgcIQADQGgcAAAAC0GfDkUVLCv/AAj5IQADQGgcAAAACQGfLUQn/wALKSEAA0BoHCEAA0BoHAAAAAkBny9EJ/8ACyghAANAaBwAAAANQZs0NExDP/6plgAMuCEAA0BoHAAAAAtBn1JFFSwr/wAI+SEAA0BoHCEAA0BoHAAAAAkBn3FEJ/8ACyghAANAaBwAAAAJAZ9zRCf/AAsoIQADQGgcIQADQGgcAAAADUGbeDRMQz/+qZYADLkhAANAaBwAAAALQZ+WRRUsK/8ACPghAANAaBwhAANAaBwAAAAJAZ+1RCf/AAspIQADQGgcAAAACQGft0Qn/wALKSEAA0BoHCEAA0BoHAAAAA1Bm7w0TEM//qmWAAy4IQADQGgcAAAAC0Gf2kUVLCv/AAj5IQADQGgcAAAACQGf+UQn/wALKCEAA0BoHCEAA0BoHAAAAAkBn/tEJ/8ACykhAANAaBwAAAANQZvgNExDP/6plgAMuSEAA0BoHCEAA0BoHAAAAAtBnh5FFSwr/wAI+CEAA0BoHAAAAAkBnj1EJ/8ACyghAANAaBwhAANAaBwAAAAJAZ4/RCf/AAspIQADQGgcAAAADUGaJDRMQz/+qZYADLghAANAaBwAAAALQZ5CRRUsK/8ACPkhAANAaBwhAANAaBwAAAAJAZ5hRCf/AAsoIQADQGgcAAAACQGeY0Qn/wALKSEAA0BoHCEAA0BoHAAAAA1Bmmg0TEM//qmWAAy5IQADQGgcAAAAC0GehkUVLCv/AAj5IQADQGgcIQADQGgcAAAACQGepUQn/wALKSEAA0BoHAAAAAkBnqdEJ/8ACyghAANAaBwAAAANQZqsNExDP/6plgAMuCEAA0BoHCEAA0BoHAAAAAtBnspFFSwr/wAI+SEAA0BoHAAAAAkBnulEJ/8ACyghAANAaBwhAANAaBwAAAAJAZ7rRCf/AAsoIQADQGgcAAAADUGa8DRMQz/+qZYADLkhAANAaBwhAANAaBwAAAALQZ8ORRUsK/8ACPkhAANAaBwAAAAJAZ8tRCf/AAspIQADQGgcIQADQGgcAAAACQGfL0Qn/wALKCEAA0BoHAAAAA1BmzQ0TEM//qmWAAy4IQADQGgcAAAAC0GfUkUVLCv/AAj5IQADQGgcIQADQGgcAAAACQGfcUQn/wALKCEAA0BoHAAAAAkBn3NEJ/8ACyghAANAaBwhAANAaBwAAAANQZt4NExC//6plgAMuSEAA0BoHAAAAAtBn5ZFFSwr/wAI+CEAA0BoHCEAA0BoHAAAAAkBn7VEJ/8ACykhAANAaBwAAAAJAZ+3RCf/AAspIQADQGgcAAAADUGbuzRMQn/+nhAAYsAhAANAaBwhAANAaBwAAAAJQZ/aQhP/AAspIQADQGgcAAAACQGf+UQn/wALKCEAA0BoHCEAA0BoHCEAA0BoHCEAA0BoHCEAA0BoHCEAA0BoHAAACiFtb292AAAAbG12aGQAAAAA1YCCX9WAgl8AAAPoAAAH/AABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAGGlvZHMAAAAAEICAgAcAT////v7/AAAF+XRyYWsAAABcdGtoZAAAAAPVgIJf1YCCXwAAAAEAAAAAAAAH0AAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAygAAAMoAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAB9AAABdwAAEAAAAABXFtZGlhAAAAIG1kaGQAAAAA1YCCX9WAgl8AAV+QAAK/IFXEAAAAAAAtaGRscgAAAAAAAAAAdmlkZQAAAAAAAAAAAAAAAFZpZGVvSGFuZGxlcgAAAAUcbWluZgAAABR2bWhkAAAAAQAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAAE3HN0YmwAAACYc3RzZAAAAAAAAAABAAAAiGF2YzEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAygDKAEgAAABIAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY//8AAAAyYXZjQwFNQCj/4QAbZ01AKOyho3ySTUBAQFAAAAMAEAAr8gDxgxlgAQAEaO+G8gAAABhzdHRzAAAAAAAAAAEAAAA8AAALuAAAABRzdHNzAAAAAAAAAAEAAAABAAAB8GN0dHMAAAAAAAAAPAAAAAEAABdwAAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAADqYAAAAAQAAF3AAAAABAAAAAAAAAAEAAAu4AAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAADqYAAAAAQAAF3AAAAABAAAAAAAAAAEAAAu4AAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAADqYAAAAAQAAF3AAAAABAAAAAAAAAAEAAAu4AAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAADqYAAAAAQAAF3AAAAABAAAAAAAAAAEAAAu4AAAAAQAAOpgAAAABAAAXcAAAAAEAAAAAAAAAAQAAC7gAAAABAAA6mAAAAAEAABdwAAAAAQAAAAAAAAABAAALuAAAAAEAAC7gAAAAAQAAF3AAAAABAAAAAAAAABxzdHNjAAAAAAAAAAEAAAABAAAAAQAAAAEAAAEEc3RzegAAAAAAAAAAAAAAPAAAAzQAAAAQAAAADQAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAAPAAAADQAAAA0AAAARAAAADwAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAAPAAAADQAAAA0AAAARAAAADwAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAAPAAAADQAAAA0AAAARAAAADwAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAAPAAAADQAAAA0AAAARAAAADwAAAA0AAAANAAAAEQAAAA8AAAANAAAADQAAABEAAAANAAAADQAAAQBzdGNvAAAAAAAAADwAAAAwAAADZAAAA3QAAAONAAADoAAAA7kAAAPQAAAD6wAAA/4AAAQXAAAELgAABEMAAARcAAAEbwAABIwAAAShAAAEugAABM0AAATkAAAE/wAABRIAAAUrAAAFQgAABV0AAAVwAAAFiQAABaAAAAW1AAAFzgAABeEAAAX+AAAGEwAABiwAAAY/AAAGVgAABnEAAAaEAAAGnQAABrQAAAbPAAAG4gAABvUAAAcSAAAHJwAAB0AAAAdTAAAHcAAAB4UAAAeeAAAHsQAAB8gAAAfjAAAH9gAACA8AAAgmAAAIQQAACFQAAAhnAAAIhAAACJcAAAMsdHJhawAAAFx0a2hkAAAAA9WAgl/VgIJfAAAAAgAAAAAAAAf8AAAAAAAAAAAAAAABAQAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAACsm1kaWEAAAAgbWRoZAAAAADVgIJf1YCCXwAArEQAAWAAVcQAAAAAACdoZGxyAAAAAAAAAABzb3VuAAAAAAAAAAAAAAAAU3RlcmVvAAAAAmNtaW5mAAAAEHNtaGQAAAAAAAAAAAAAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAAAQAAAidzdGJsAAAAZ3N0c2QAAAAAAAAAAQAAAFdtcDRhAAAAAAAAAAEAAAAAAAAAAAACABAAAAAArEQAAAAAADNlc2RzAAAAAAOAgIAiAAIABICAgBRAFQAAAAADDUAAAAAABYCAgAISEAaAgIABAgAAABhzdHRzAAAAAAAAAAEAAABYAAAEAAAAABxzdHNjAAAAAAAAAAEAAAABAAAAAQAAAAEAAAAUc3RzegAAAAAAAAAGAAAAWAAAAXBzdGNvAAAAAAAAAFgAAAOBAAADhwAAA5oAAAOtAAADswAAA8oAAAPfAAAD5QAAA/gAAAQLAAAEEQAABCgAAAQ9AAAEUAAABFYAAARpAAAEgAAABIYAAASbAAAErgAABLQAAATHAAAE3gAABPMAAAT5AAAFDAAABR8AAAUlAAAFPAAABVEAAAVXAAAFagAABX0AAAWDAAAFmgAABa8AAAXCAAAFyAAABdsAAAXyAAAF+AAABg0AAAYgAAAGJgAABjkAAAZQAAAGZQAABmsAAAZ+AAAGkQAABpcAAAauAAAGwwAABskAAAbcAAAG7wAABwYAAAcMAAAHIQAABzQAAAc6AAAHTQAAB2QAAAdqAAAHfwAAB5IAAAeYAAAHqwAAB8IAAAfXAAAH3QAAB/AAAAgDAAAICQAACCAAAAg1AAAIOwAACE4AAAhhAAAIeAAACH4AAAiRAAAIpAAACKoAAAiwAAAItgAACLwAAAjCAAAAFnVkdGEAAAAObmFtZVN0ZXJlbwAAAHB1ZHRhAAAAaG1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAAO2lsc3QAAAAzqXRvbwAAACtkYXRhAAAAAQAAAABIYW5kQnJha2UgMC4xMC4yIDIwMTUwNjExMDA=' 5 | 6 | var _createClass = (function () { 7 | function defineProperties(target, props) { 8 | for (var i = 0; i < props.length; i++) { 9 | var descriptor = props[i] 10 | descriptor.enumerable = descriptor.enumerable || false 11 | descriptor.configurable = true 12 | if ('value' in descriptor) descriptor.writable = true 13 | Object.defineProperty(target, descriptor.key, descriptor) 14 | } 15 | } 16 | return function (Constructor, protoProps, staticProps) { 17 | if (protoProps) defineProperties(Constructor.prototype, protoProps) 18 | if (staticProps) defineProperties(Constructor, staticProps) 19 | return Constructor 20 | } 21 | })() 22 | 23 | function _classCallCheck(instance, Constructor) { 24 | if (!(instance instanceof Constructor)) { 25 | throw new TypeError('Cannot call a class as a function') 26 | } 27 | } 28 | 29 | // Detect iOS browsers < version 10 30 | var oldIOS = 31 | typeof navigator !== 'undefined' && 32 | parseFloat( 33 | ( 34 | '' + 35 | (/CPU.*OS ([0-9_]{3,4})[0-9_]{0,1}|(CPU like).*AppleWebKit.*Mobile/i.exec( 36 | navigator.userAgent 37 | ) || [0, ''])[1] 38 | ) 39 | .replace('undefined', '3_2') 40 | .replace('_', '.') 41 | .replace('_', '') 42 | ) < 10 && 43 | !window.MSStream 44 | 45 | // Detect native Wake Lock API support 46 | var nativeWakeLock = 'wakeLock' in navigator 47 | 48 | var NoSleep = (function () { 49 | var _releasedNative = true 50 | var _nativeRequestInProgress = false 51 | 52 | function NoSleep() { 53 | var _this = this 54 | 55 | _classCallCheck(this, NoSleep) 56 | 57 | if (nativeWakeLock) { 58 | this._wakeLock = null 59 | var handleVisibilityChange = function handleVisibilityChange() { 60 | if ( 61 | _this._wakeLock !== null && 62 | document.visibilityState === 'visible' 63 | ) { 64 | _this.enable() 65 | } 66 | } 67 | document.addEventListener('visibilitychange', handleVisibilityChange) 68 | document.addEventListener('fullscreenchange', handleVisibilityChange) 69 | } else if (oldIOS) { 70 | this.noSleepTimer = null 71 | } else { 72 | // Set up no sleep video element 73 | this.noSleepVideo = document.createElement('video') 74 | 75 | this.noSleepVideo.setAttribute('title', 'No Sleep') 76 | this.noSleepVideo.setAttribute('playsinline', '') 77 | 78 | this._addSourceToVideo(this.noSleepVideo, 'webm', webm) 79 | this._addSourceToVideo(this.noSleepVideo, 'mp4', mp4) 80 | 81 | this.noSleepVideo.addEventListener('loadedmetadata', function () { 82 | if (_this.noSleepVideo.duration <= 1) { 83 | // webm source 84 | _this.noSleepVideo.setAttribute('loop', '') 85 | } else { 86 | // mp4 source 87 | _this.noSleepVideo.addEventListener('timeupdate', function () { 88 | if (_this.noSleepVideo.currentTime > 0.5) { 89 | _this.noSleepVideo.currentTime = Math.random() 90 | } 91 | }) 92 | } 93 | }) 94 | } 95 | } 96 | 97 | _createClass(NoSleep, [ 98 | { 99 | key: '_addSourceToVideo', 100 | value: function _addSourceToVideo(element, type, dataURI) { 101 | var source = document.createElement('source') 102 | source.src = dataURI 103 | source.type = 'video/' + type 104 | element.appendChild(source) 105 | }, 106 | }, 107 | { 108 | key: 'enable', 109 | value: function enable() { 110 | var _this2 = this 111 | 112 | if (nativeWakeLock) { 113 | _nativeRequestInProgress = true 114 | navigator.wakeLock 115 | .request('screen') 116 | .then(function (wakeLock) { 117 | _releasedNative = false 118 | _nativeRequestInProgress = false 119 | 120 | _this2._wakeLock = wakeLock 121 | _this2._wakeLock.addEventListener('release', function () { 122 | _releasedNative = true 123 | _this2._wakeLock = null 124 | }) 125 | }) 126 | .catch(function (err) { 127 | _nativeRequestInProgress = false 128 | console.error(err.name + ', ' + err.message) 129 | }) 130 | } else if (oldIOS) { 131 | this.disable() 132 | console.warn( 133 | '\n NoSleep enabled for older iOS devices. This can interrupt\n active or long-running network requests from completing successfully.\n See https://github.com/richtr/NoSleep.js/issues/15 for more details.\n ' 134 | ) 135 | this.noSleepTimer = window.setInterval(function () { 136 | if (!document.hidden) { 137 | window.location.href = window.location.href.split('#')[0] 138 | window.setTimeout(window.stop, 0) 139 | } 140 | }, 15000) 141 | } else { 142 | this.noSleepVideo.play() 143 | } 144 | }, 145 | }, 146 | { 147 | key: 'disable', 148 | value: function disable() { 149 | if (nativeWakeLock) { 150 | if (this._wakeLock != null) { 151 | _releasedNative = true 152 | this._wakeLock.release() 153 | } 154 | 155 | this._wakeLock = null 156 | } else if (oldIOS) { 157 | if (this.noSleepTimer) { 158 | console.warn( 159 | '\n NoSleep now disabled for older iOS devices.\n ' 160 | ) 161 | window.clearInterval(this.noSleepTimer) 162 | this.noSleepTimer = null 163 | } 164 | } else { 165 | this.noSleepVideo.pause() 166 | } 167 | }, 168 | }, 169 | { 170 | key: 'enabled', 171 | value: async function enabled() { 172 | if (nativeWakeLock) { 173 | if (_nativeRequestInProgress == true) { 174 | // Wait until the request is done. 175 | while (true) { 176 | // Wait for 42 milliseconds. 177 | await new Promise((resolve, reject) => setTimeout(resolve, 42)) 178 | if (_nativeRequestInProgress == false) { 179 | break 180 | } 181 | } 182 | } 183 | 184 | // todo: use WakeLockSentinel.released when that is available (https://developer.mozilla.org/en-US/docs/Web/API/WakeLockSentinel/released) 185 | if (_releasedNative != false) { 186 | return false 187 | } 188 | 189 | return true 190 | } else if (oldIOS) { 191 | return this.noSleepTimer != null 192 | } else { 193 | if (this.noSleepVideo == undefined) { 194 | return false 195 | } 196 | 197 | return !this.noSleepVideo.paused 198 | } 199 | }, 200 | }, 201 | ]) 202 | 203 | return NoSleep 204 | })() 205 | 206 | var noSleep = new NoSleep() 207 | 208 | var Wakelock = { 209 | enabled: async function () { 210 | try { 211 | return noSleep.enabled() 212 | } catch (e) { 213 | return false 214 | } 215 | }, 216 | toggle: async function (enable) { 217 | if (enable) { 218 | noSleep.enable() 219 | } else { 220 | noSleep.disable() 221 | } 222 | }, 223 | } 224 | 225 | if (nativeWakeLock != true) { 226 | // The first non-native call sometimes throws an error, however, 227 | // the error does not leak the try-catch above. Therefore, this 228 | // is an easy fix that realiably works. 229 | Wakelock.enabled() 230 | } 231 | -------------------------------------------------------------------------------- /android/app/build/intermediates/flutter/release/flutter_assets/shaders/ink_sparkle.frag: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judemont/reciper/48a203590abd8dfa5b76a61e6a797bffafcafd83/android/app/build/intermediates/flutter/release/flutter_assets/shaders/ink_sparkle.frag -------------------------------------------------------------------------------- /android/app/build/intermediates/flutter/release/libs.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judemont/reciper/48a203590abd8dfa5b76a61e6a797bffafcafd83/android/app/build/intermediates/flutter/release/libs.jar -------------------------------------------------------------------------------- /android/app/build/intermediates/flutter/release/x86_64/app.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judemont/reciper/48a203590abd8dfa5b76a61e6a797bffafcafd83/android/app/build/intermediates/flutter/release/x86_64/app.so -------------------------------------------------------------------------------- /android/app/build/intermediates/incremental/mergeReleaseJniLibFolders/merger.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /android/app/build/tmp/packJniLibsflutterBuildRelease/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | 3 | -------------------------------------------------------------------------------- /android/app/keystore.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judemont/reciper/48a203590abd8dfa5b76a61e6a797bffafcafd83/android/app/keystore.jks -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/reciper/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package jdm.apps.reciper 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() -------------------------------------------------------------------------------- /android/app/src/main/kotlin/jdm/apps/reciper/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package jdm.apps.reciper 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judemont/reciper/48a203590abd8dfa5b76a61e6a797bffafcafd83/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judemont/reciper/48a203590abd8dfa5b76a61e6a797bffafcafd83/android/app/src/main/res/mipmap-hdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judemont/reciper/48a203590abd8dfa5b76a61e6a797bffafcafd83/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judemont/reciper/48a203590abd8dfa5b76a61e6a797bffafcafd83/android/app/src/main/res/mipmap-mdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judemont/reciper/48a203590abd8dfa5b76a61e6a797bffafcafd83/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judemont/reciper/48a203590abd8dfa5b76a61e6a797bffafcafd83/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judemont/reciper/48a203590abd8dfa5b76a61e6a797bffafcafd83/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judemont/reciper/48a203590abd8dfa5b76a61e6a797bffafcafd83/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judemont/reciper/48a203590abd8dfa5b76a61e6a797bffafcafd83/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judemont/reciper/48a203590abd8dfa5b76a61e6a797bffafcafd83/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = '../build' 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(':app') 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /android/build/reports/problems/problems-report.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judemont/reciper/48a203590abd8dfa5b76a61e6a797bffafcafd83/android/build/reports/problems/problems-report.html -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /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.11.1-all.zip -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | } 9 | settings.ext.flutterSdkPath = flutterSdkPath() 10 | 11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") 12 | 13 | repositories { 14 | google() 15 | mavenCentral() 16 | gradlePluginPortal() 17 | } 18 | } 19 | 20 | plugins { 21 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 22 | id "com.android.application" version "8.1.0" apply false 23 | id "org.jetbrains.kotlin.android" version "1.8.22" apply false 24 | } 25 | 26 | include ":app" 27 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judemont/reciper/48a203590abd8dfa5b76a61e6a797bffafcafd83/assets/icon.png -------------------------------------------------------------------------------- /fastlane/metadata/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 | 🍳 Reciper - Your Ultimate Kitchen Companion! 📱 2 | 3 | Manage your recipes with ease using Reciper: 4 | 5 | - 📝 Organize and store all your favorite recipes in one convenient place. 6 | - 🌐 Easily import recipes from any website and customize them to your liking. 7 | - 📤 Export your recipes for easy sharing or backup. 8 | - 🎨 Enjoy a beautiful and stylish Material You design. 9 | - 🆓 Reciper is open source, ad-free, and completely free for a seamless culinary experience. 10 | - 📲 Download Reciper today and simplify the management of your recipes! 11 | 12 | Transform your cooking experience with Reciper! 🍽️ -------------------------------------------------------------------------------- /fastlane/metadata/en-US/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judemont/reciper/48a203590abd8dfa5b76a61e6a797bffafcafd83/fastlane/metadata/en-US/images/icon.png -------------------------------------------------------------------------------- /fastlane/metadata/en-US/images/phoneScreenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judemont/reciper/48a203590abd8dfa5b76a61e6a797bffafcafd83/fastlane/metadata/en-US/images/phoneScreenshots/1.png -------------------------------------------------------------------------------- /fastlane/metadata/en-US/images/phoneScreenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judemont/reciper/48a203590abd8dfa5b76a61e6a797bffafcafd83/fastlane/metadata/en-US/images/phoneScreenshots/2.png -------------------------------------------------------------------------------- /fastlane/metadata/en-US/images/phoneScreenshots/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judemont/reciper/48a203590abd8dfa5b76a61e6a797bffafcafd83/fastlane/metadata/en-US/images/phoneScreenshots/3.png -------------------------------------------------------------------------------- /fastlane/metadata/en-US/images/phoneScreenshots/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judemont/reciper/48a203590abd8dfa5b76a61e6a797bffafcafd83/fastlane/metadata/en-US/images/phoneScreenshots/4.png -------------------------------------------------------------------------------- /fastlane/metadata/en-US/images/phoneScreenshots/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judemont/reciper/48a203590abd8dfa5b76a61e6a797bffafcafd83/fastlane/metadata/en-US/images/phoneScreenshots/5.png -------------------------------------------------------------------------------- /fastlane/metadata/en-US/images/phoneScreenshots/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judemont/reciper/48a203590abd8dfa5b76a61e6a797bffafcafd83/fastlane/metadata/en-US/images/phoneScreenshots/6.png -------------------------------------------------------------------------------- /fastlane/metadata/en-US/images/phoneScreenshots/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/judemont/reciper/48a203590abd8dfa5b76a61e6a797bffafcafd83/fastlane/metadata/en-US/images/phoneScreenshots/7.png -------------------------------------------------------------------------------- /fastlane/metadata/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | Your Ultimate Kitchen Companion ! -------------------------------------------------------------------------------- /fastlane/metadata/en-US/title.txt: -------------------------------------------------------------------------------- 1 | Reciper -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:reciper/screens/pages_layout.dart'; 3 | import 'screens/home.dart'; 4 | import 'package:sqflite_common_ffi/sqflite_ffi.dart'; 5 | import 'package:flex_color_scheme/flex_color_scheme.dart'; 6 | 7 | Future main() async { 8 | sqfliteFfiInit(); 9 | databaseFactory = databaseFactoryFfi; 10 | 11 | runApp(const MyApp()); 12 | } 13 | 14 | class MyApp extends StatelessWidget { 15 | const MyApp({super.key}); 16 | 17 | // This widget is the root of your application. 18 | @override 19 | Widget build(BuildContext context) { 20 | return MaterialApp( 21 | title: 'Reciper', 22 | theme: FlexThemeData.light( 23 | scheme: FlexScheme.materialBaseline, 24 | surfaceMode: FlexSurfaceMode.levelSurfacesLowScaffold, 25 | blendLevel: 7, 26 | subThemesData: const FlexSubThemesData( 27 | blendOnLevel: 10, 28 | blendOnColors: false, 29 | useTextTheme: true, 30 | useM2StyleDividerInM3: true, 31 | alignedDropdown: true, 32 | useInputDecoratorThemeInDialogs: true, 33 | ), 34 | visualDensity: FlexColorScheme.comfortablePlatformDensity, 35 | useMaterial3: true, 36 | swapLegacyOnMaterial3: true, 37 | // To use the Playground font, add GoogleFonts package and uncomment 38 | // fontFamily: GoogleFonts.notoSans().fontFamily, 39 | ), 40 | darkTheme: FlexThemeData.dark( 41 | scheme: FlexScheme.materialBaseline, 42 | surfaceMode: FlexSurfaceMode.levelSurfacesLowScaffold, 43 | blendLevel: 13, 44 | subThemesData: const FlexSubThemesData( 45 | blendOnLevel: 20, 46 | useTextTheme: true, 47 | useM2StyleDividerInM3: true, 48 | alignedDropdown: true, 49 | useInputDecoratorThemeInDialogs: true, 50 | ), 51 | visualDensity: FlexColorScheme.comfortablePlatformDensity, 52 | useMaterial3: true, 53 | swapLegacyOnMaterial3: true, 54 | // To use the Playground font, add GoogleFonts package and uncomment 55 | // fontFamily: GoogleFonts.notoSans().fontFamily, 56 | ), 57 | home: const PagesLayout(child: HomePage())); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/models/recipe.dart: -------------------------------------------------------------------------------- 1 | class Recipe { 2 | int? id; 3 | String? steps; 4 | String? servings; 5 | String? title; 6 | String? ingredients; 7 | String? source; 8 | String? image; 9 | 10 | Recipe( 11 | {this.id, 12 | this.steps, 13 | this.servings, 14 | this.title, 15 | this.ingredients, 16 | this.source, 17 | this.image}); 18 | 19 | Map toMap() { 20 | return { 21 | 'id': id, 22 | 'servings': servings, 23 | 'steps': steps, 24 | 'title': title, 25 | 'ingredients': ingredients, 26 | 'source': source, 27 | 'image': image, 28 | }; 29 | } 30 | 31 | static Recipe fromMap(Map map) { 32 | return Recipe( 33 | id: map['id'], 34 | servings: map["servings"], 35 | steps: map['steps'], 36 | title: map['title'], 37 | ingredients: map['ingredients'], 38 | source: map['source'], 39 | image: map['image'], 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/models/tag.dart: -------------------------------------------------------------------------------- 1 | class Tag { 2 | int? id; 3 | String? name; 4 | 5 | Tag({this.id, this.name}); 6 | 7 | Map toMap() { 8 | return {'id': id, 'name': name}; 9 | } 10 | 11 | static Tag fromMap(Map map) { 12 | return Tag( 13 | id: map['id'], 14 | name: map['name'], 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/models/tag_link.dart: -------------------------------------------------------------------------------- 1 | class TagLink { 2 | int? recipeId; 3 | int? tagId; 4 | 5 | TagLink({this.recipeId, this.tagId}); 6 | 7 | Map toMap() { 8 | return {'recipeId': recipeId, 'tagId': tagId}; 9 | } 10 | 11 | static TagLink fromMap(Map map) { 12 | return TagLink( 13 | recipeId: map['recipeId'], 14 | tagId: map['tagId'], 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/screens/home.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:reciper/models/tag.dart'; 3 | import 'package:reciper/widgets/tags_selector.dart'; 4 | import '../utilities/database.dart'; 5 | import '../models/recipe.dart'; 6 | import '../widgets/recipes_list.dart'; 7 | 8 | class HomePage extends StatefulWidget { 9 | const HomePage({super.key}); 10 | 11 | @override 12 | State createState() => _HomePageState(); 13 | } 14 | 15 | class _HomePageState extends State { 16 | List recipes = []; 17 | List tags = []; 18 | 19 | List selectedTagsId = []; 20 | List selectedRecipes = []; 21 | bool displaySearchField = false; 22 | 23 | @override 24 | void initState() { 25 | loadRecipes(); 26 | loadTags().then((value) { 27 | selectedTagsId.clear(); 28 | selectedTagsId.addAll(tags.map((e) => e.id!).toList()); 29 | }); 30 | 31 | super.initState(); 32 | } 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | return Scaffold( 37 | appBar: AppBar( 38 | title: const Text("Reciper"), 39 | centerTitle: true, 40 | actions: [ 41 | IconButton( 42 | onPressed: () { 43 | setState(() { 44 | displaySearchField = !displaySearchField; 45 | loadRecipes(); 46 | }); 47 | }, 48 | icon: const Icon(Icons.search)), 49 | if (selectedRecipes.isNotEmpty) 50 | IconButton( 51 | onPressed: () => showDialog( 52 | context: context, 53 | builder: (context) => AlertDialog( 54 | title: const Text("Delete selected recipes"), 55 | content: const Text("Are you sure?"), 56 | actions: [ 57 | TextButton( 58 | onPressed: () => Navigator.pop(context), 59 | child: const Text("Cancel")), 60 | TextButton( 61 | onPressed: () => 62 | removeSelectedRecipes(selectedRecipes) 63 | .then((value) { 64 | loadRecipes(); 65 | Navigator.pop(context); 66 | }), 67 | child: const Text("Yes")), 68 | ], 69 | )), 70 | icon: const Icon(Icons.delete)), 71 | ], 72 | ), 73 | drawer: Drawer( 74 | child: TagsSelector( 75 | tags: tags, 76 | onTagsSelectionUpdate: onTagsSelectionUpdate, 77 | onTagsUpdate: loadTags, 78 | selectedTagsId: selectedTagsId), 79 | ), 80 | body: SingleChildScrollView( 81 | child: Column( 82 | children: [ 83 | const SizedBox( 84 | height: 20, 85 | ), 86 | Visibility( 87 | visible: displaySearchField, 88 | child: SizedBox( 89 | width: MediaQuery.of(context).size.width * 0.9, 90 | child: TextField( 91 | decoration: const InputDecoration( 92 | border: OutlineInputBorder(), 93 | hintText: 'Search', 94 | ), 95 | onChanged: (value) { 96 | loadRecipes(searchQuery: value); 97 | }, 98 | ))), 99 | const SizedBox( 100 | height: 20, 101 | ), 102 | Visibility( 103 | visible: selectedTagsId.length != tags.length, 104 | child: ElevatedButton( 105 | child: const Text("Remove filters"), 106 | onPressed: () { 107 | setState(() { 108 | selectedTagsId.clear(); 109 | selectedTagsId.addAll(tags.map((e) => e.id!).toList()); 110 | }); 111 | loadRecipes(); 112 | }, 113 | ), 114 | ), 115 | RecipeListView( 116 | reloadRecipes: loadRecipes, 117 | recipes: recipes, 118 | onRecipesSelectionUpdate: onRecipesSelectionUpdate, 119 | selectedRecipesID: selectedRecipes, 120 | ), 121 | ], 122 | ))); 123 | } 124 | 125 | Future loadRecipes({searchQuery = ""}) async { 126 | if (selectedTagsId.length == tags.length) { 127 | DatabaseService.getRecipes(searchQuery: searchQuery) 128 | .then((List result) { 129 | setState(() { 130 | recipes = result; 131 | }); 132 | }); 133 | } else { 134 | setState(() { 135 | recipes = []; 136 | }); 137 | 138 | for (var tagId in selectedTagsId) { 139 | DatabaseService.getRecipesFromTag(tagId, searchQuery: searchQuery) 140 | .then((values) { 141 | for (var recipe in values) { 142 | if (!recipes.contains(recipe)) { 143 | setState(() { 144 | recipes.add(recipe); 145 | }); 146 | } 147 | } 148 | }); 149 | } 150 | } 151 | } 152 | 153 | Future loadTags() async { 154 | List result = await DatabaseService.getTags(); 155 | result.sort((a, b) => (a.name ?? "").compareTo(b.name ?? "")); 156 | setState(() { 157 | tags = result; 158 | }); 159 | } 160 | 161 | Future onTagsSelectionUpdate(List values) async { 162 | print(values); 163 | setState(() { 164 | selectedTagsId = values; 165 | }); 166 | loadRecipes(); 167 | } 168 | 169 | Future onRecipesSelectionUpdate(List values) async { 170 | setState(() { 171 | selectedRecipes = values; 172 | }); 173 | } 174 | 175 | Future deleteRecipe(int id) async { 176 | DatabaseService.removeRecipe(id); 177 | } 178 | 179 | Future removeSelectedRecipes(List values) async { 180 | for (var recipeID in values) { 181 | deleteRecipe(recipeID); 182 | } 183 | setState(() { 184 | selectedRecipes = []; 185 | }); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /lib/screens/pages_layout.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:reciper/screens/home.dart'; 3 | import 'package:reciper/screens/recipe_editor.dart'; 4 | import 'package:reciper/screens/settings.dart'; 5 | import 'package:reciper/widgets/bottom_nav_bar.dart'; 6 | 7 | class PagesLayout extends StatefulWidget { 8 | final Widget child; 9 | final bool displayBottomNavBar; 10 | final int? currentSection; 11 | 12 | const PagesLayout({ 13 | super.key, 14 | required this.child, 15 | this.displayBottomNavBar = true, 16 | this.currentSection, 17 | }); 18 | 19 | @override 20 | State createState() => _PagesLayoutState(); 21 | } 22 | 23 | class _PagesLayoutState extends State { 24 | late int currentPageIndex; 25 | late Widget currentChild; 26 | List pages = [ 27 | const HomePage(), 28 | const RecipeEditorPage(), 29 | const Settings() 30 | ]; 31 | 32 | @override 33 | void initState() { 34 | super.initState(); 35 | currentPageIndex = widget.currentSection ?? 0; 36 | currentChild = widget.child; 37 | } 38 | 39 | @override 40 | Widget build(BuildContext context) { 41 | return Scaffold( 42 | body: currentChild, 43 | bottomNavigationBar: widget.displayBottomNavBar 44 | ? BottomNavBar( 45 | selectedIndex: currentPageIndex, 46 | onDestinationSelected: (index) => setState(() { 47 | currentPageIndex = index; 48 | currentChild = pages[currentPageIndex]; 49 | }), 50 | ) 51 | : null, 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/screens/recipe_editor.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:image_picker/image_picker.dart'; 5 | import 'package:reciper/models/tag.dart'; 6 | import 'package:reciper/screens/pages_layout.dart'; 7 | import 'package:reciper/utilities/database.dart'; 8 | import 'package:reciper/models/recipe.dart'; 9 | import 'package:reciper/screens/home.dart'; 10 | import 'package:reciper/widgets/extract_recipe_button.dart'; 11 | import 'package:reciper/widgets/recipe_tag_selector.dart'; 12 | 13 | class RecipeEditorPage extends StatefulWidget { 14 | // final String initialTitle; 15 | // final String initialIngredients; 16 | // final String initialSteps; 17 | // final int? recipeID; 18 | final Recipe? initialRecipe; 19 | final bool isUpdate; 20 | const RecipeEditorPage( 21 | {super.key, this.initialRecipe, this.isUpdate = false}); 22 | 23 | @override 24 | State createState() => _RecipeEditorPageState(); 25 | } 26 | 27 | class _RecipeEditorPageState extends State { 28 | final double fieldsMargin = 30.0; 29 | final formKey = GlobalKey(); 30 | String title = ""; 31 | String ingredients = ""; 32 | String steps = ""; 33 | String servings = ""; 34 | String source = ""; 35 | String image = ""; 36 | 37 | List tags = []; 38 | List selectedTagsId = []; 39 | 40 | @override 41 | void initState() { 42 | print(widget.initialRecipe); 43 | loadTags(); 44 | if (widget.initialRecipe != null) { 45 | if (widget.initialRecipe?.id != null) { 46 | DatabaseService.getTagsFromRecipe(widget.initialRecipe!.id!) 47 | .then((tags) { 48 | selectedTagsId = tags.map((e) => e.id!).toList(); 49 | }); 50 | } 51 | image = widget.initialRecipe?.image ?? ""; 52 | } 53 | super.initState(); 54 | } 55 | 56 | Future saveImage(XFile selectedImage) async { 57 | List imageBytes = await selectedImage.readAsBytes(); 58 | setState(() { 59 | image = base64Encode(imageBytes); 60 | }); 61 | } 62 | 63 | Future selectImage() async { 64 | showModalBottomSheet( 65 | context: context, 66 | builder: (BuildContext context) { 67 | return Container( 68 | // height: 200, 69 | child: ListView( 70 | children: [ 71 | ListTile( 72 | leading: Icon(Icons.camera), 73 | title: Text("Take a picture"), 74 | onTap: () async { 75 | final ImagePicker picker = ImagePicker(); 76 | final XFile? selectedImageX = 77 | await picker.pickImage(source: ImageSource.camera); 78 | 79 | if (selectedImageX != null) { 80 | await saveImage(selectedImageX); 81 | if (context.mounted) Navigator.pop(context); 82 | } 83 | }), 84 | ListTile( 85 | leading: Icon(Icons.image), 86 | title: Text("Select an image"), 87 | onTap: () async { 88 | final ImagePicker picker = ImagePicker(); 89 | final XFile? selectedImageX = 90 | await picker.pickImage(source: ImageSource.gallery); 91 | 92 | if (selectedImageX != null) { 93 | await saveImage(selectedImageX); 94 | if (context.mounted) Navigator.pop(context); 95 | } 96 | }, 97 | ) 98 | ], 99 | ), 100 | ); 101 | }); 102 | } 103 | 104 | @override 105 | Widget build(BuildContext context) { 106 | return Scaffold( 107 | resizeToAvoidBottomInset: true, 108 | appBar: AppBar( 109 | automaticallyImplyLeading: false, 110 | title: const Text("New Recipe"), 111 | centerTitle: true, 112 | actions: [ 113 | IconButton( 114 | onPressed: () { 115 | if (formKey.currentState!.validate()) { 116 | formKey.currentState!.save(); 117 | 118 | if (!widget.isUpdate) { 119 | DatabaseService.createRecipe(Recipe( 120 | title: title, 121 | servings: servings, 122 | steps: steps, 123 | ingredients: ingredients, 124 | source: source, 125 | image: image.isNotEmpty ? image : null, 126 | )).then((recipeId) { 127 | for (var tagId in selectedTagsId) { 128 | DatabaseService.createTagLink(tagId, recipeId); 129 | } 130 | }); 131 | } else { 132 | DatabaseService.removeTagLink( 133 | recipeId: widget.initialRecipe!.id) 134 | .then((value) { 135 | for (var tagId in selectedTagsId) { 136 | DatabaseService.createTagLink( 137 | tagId, widget.initialRecipe!.id!); 138 | } 139 | }); 140 | DatabaseService.updateRecipe(Recipe( 141 | id: widget.initialRecipe!.id, 142 | servings: servings, 143 | title: title, 144 | steps: steps, 145 | ingredients: ingredients, 146 | source: source, 147 | image: image.isNotEmpty ? image : null, 148 | )); 149 | } 150 | 151 | Navigator.of(context).push( 152 | MaterialPageRoute( 153 | builder: (context) => 154 | const PagesLayout(child: HomePage())), 155 | ); 156 | } 157 | }, 158 | icon: const Icon(Icons.check)) 159 | ], 160 | ), 161 | floatingActionButton: const ExtractRecipeButton(), 162 | body: SingleChildScrollView( 163 | child: Form( 164 | key: formKey, 165 | child: Padding( 166 | padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 56), 167 | child: Column( 168 | children: [ 169 | TextFormField( 170 | initialValue: widget.initialRecipe?.title, 171 | validator: (value) { 172 | if (value!.isEmpty) { 173 | return 'Please enter something.'; 174 | } 175 | return null; 176 | }, 177 | onSaved: (value) { 178 | title = value!; 179 | }, 180 | decoration: const InputDecoration( 181 | border: OutlineInputBorder(), 182 | hintText: 'Title', 183 | ), 184 | ), 185 | SizedBox(height: fieldsMargin), 186 | SizedBox( 187 | height: 50, 188 | child: Row( 189 | children: [ 190 | const Text("Tags : "), 191 | const SizedBox( 192 | width: 20, 193 | ), 194 | RecipeTagSelector( 195 | tags: tags, 196 | onTagsUpdate: loadTags, 197 | selectedTagsId: selectedTagsId, 198 | onTagsSelectionUpdate: onTagsSelectionUpdate) 199 | ], 200 | )), 201 | SizedBox(height: fieldsMargin), 202 | image.isEmpty 203 | ? ElevatedButton( 204 | onPressed: () { 205 | selectImage(); 206 | }, 207 | child: const Text("Add Image")) 208 | : Column(children: [ 209 | Image.memory(Base64Decoder().convert(image)), 210 | SizedBox(height: fieldsMargin), 211 | ElevatedButton( 212 | onPressed: () { 213 | setState(() { 214 | image = ""; 215 | }); 216 | }, 217 | child: const Text("Remove Image")) 218 | ]), 219 | SizedBox(height: fieldsMargin), 220 | TextFormField( 221 | initialValue: widget.initialRecipe?.servings, 222 | onSaved: (value) { 223 | servings = value!; 224 | }, 225 | decoration: const InputDecoration( 226 | border: OutlineInputBorder(), 227 | hintText: 'Servings', 228 | ), 229 | ), 230 | SizedBox(height: fieldsMargin), 231 | TextFormField( 232 | initialValue: widget.initialRecipe?.ingredients, 233 | onSaved: (value) { 234 | ingredients = value!; 235 | }, 236 | maxLines: 7, 237 | keyboardType: TextInputType.multiline, 238 | decoration: const InputDecoration( 239 | border: OutlineInputBorder(), 240 | hintText: 'Ingredients', 241 | ), 242 | ), 243 | SizedBox(height: fieldsMargin), 244 | TextFormField( 245 | initialValue: widget.initialRecipe?.steps, 246 | onSaved: (value) { 247 | steps = value!; 248 | }, 249 | maxLines: 7, 250 | keyboardType: TextInputType.multiline, 251 | decoration: const InputDecoration( 252 | border: OutlineInputBorder(), 253 | hintText: 'Steps', 254 | ), 255 | ), 256 | SizedBox(height: fieldsMargin), 257 | TextFormField( 258 | validator: (value) { 259 | if ((value ?? "").isNotEmpty && 260 | Uri.tryParse(value ?? "") == null) { 261 | return "Please enter a valid URL"; 262 | } 263 | return null; 264 | }, 265 | initialValue: widget.initialRecipe?.source, 266 | onSaved: (value) { 267 | source = value ?? ""; 268 | }, 269 | decoration: const InputDecoration( 270 | border: OutlineInputBorder(), 271 | hintText: 'Recipe source URL', 272 | ), 273 | ) 274 | ], 275 | ), 276 | ), 277 | ))); 278 | } 279 | 280 | Future loadTags() async { 281 | DatabaseService.getTags().then((List result) { 282 | setState(() { 283 | tags = result; 284 | tags.sort((a, b) => (a.name ?? "").compareTo(b.name ?? "")); 285 | }); 286 | }); 287 | } 288 | 289 | Future onTagsSelectionUpdate(List values) async { 290 | setState(() { 291 | selectedTagsId = values; 292 | }); 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /lib/screens/recipe_view.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:reciper/models/tag.dart'; 6 | import 'package:reciper/screens/pages_layout.dart'; 7 | import 'package:reciper/screens/recipe_editor.dart'; 8 | import 'package:reciper/utilities/database.dart'; 9 | import 'package:url_launcher/url_launcher.dart'; 10 | import '../models/recipe.dart'; 11 | import 'package:wakelock_plus/wakelock_plus.dart'; 12 | 13 | class RecipeViewPage extends StatefulWidget { 14 | final Recipe recipe; 15 | final Function reloadRecipes; 16 | const RecipeViewPage( 17 | {super.key, required this.recipe, required this.reloadRecipes}); 18 | @override 19 | State createState() => _RecipeViewPageState(); 20 | } 21 | 22 | class _RecipeViewPageState extends State { 23 | Map checkboxValuesIngredients = {}; 24 | Map checkboxValuesSteps = {}; 25 | bool wakeLock = false; 26 | List tags = []; 27 | Uint8List? imageBytes; 28 | 29 | @override 30 | void initState() { 31 | if (widget.recipe.image != null) { 32 | imageBytes = Base64Decoder().convert(widget.recipe.image!); 33 | } 34 | super.initState(); 35 | } 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | List ingredientsList = 40 | (widget.recipe.ingredients ?? "").split("\n"); 41 | ingredientsList.removeWhere((element) => element == ""); 42 | 43 | if (ingredientsList.isEmpty) { 44 | ingredientsList.add(widget.recipe.ingredients ?? ""); 45 | } 46 | List stepsList = (widget.recipe.steps ?? "").split("\n"); 47 | stepsList.removeWhere((element) => element == ""); 48 | if (stepsList.isEmpty) { 49 | stepsList.add(widget.recipe.ingredients ?? ""); 50 | } 51 | 52 | DatabaseService.getTagsFromRecipe(widget.recipe.id!).then((values) { 53 | setState(() { 54 | tags = values; 55 | }); 56 | }); 57 | 58 | return Scaffold( 59 | resizeToAvoidBottomInset: false, 60 | appBar: AppBar( 61 | title: const Text("Reciper"), 62 | centerTitle: true, 63 | actions: [ 64 | Tooltip( 65 | message: "Prevent your phone from going to sleep", 66 | child: IconButton( 67 | onPressed: () { 68 | setState(() { 69 | wakeLock = !wakeLock; 70 | WakelockPlus.toggle(enable: wakeLock); 71 | 72 | if (wakeLock) { 73 | SnackBar message = const SnackBar( 74 | content: 75 | Text("Your phone won't go into standby mode"), 76 | ); 77 | 78 | if (context.mounted) { 79 | ScaffoldMessenger.of(context).showSnackBar(message); 80 | } 81 | } 82 | }); 83 | }, 84 | icon: Icon(wakeLock ? Icons.lock : Icons.lock_open), 85 | )), 86 | IconButton( 87 | onPressed: () { 88 | Navigator.of(context) 89 | .push(MaterialPageRoute( 90 | builder: (context) => PagesLayout( 91 | currentSection: 1, 92 | child: RecipeEditorPage( 93 | initialRecipe: widget.recipe, 94 | isUpdate: true, 95 | )), 96 | )) 97 | .then((value) => widget.reloadRecipes()); 98 | }, 99 | icon: const Icon(Icons.edit)), 100 | IconButton( 101 | icon: const Icon(Icons.delete), 102 | onPressed: () { 103 | showDialog( 104 | context: context, 105 | builder: (context) => AlertDialog( 106 | title: const Text("Delete recipe"), 107 | content: const Text("Are you sure?"), 108 | actions: [ 109 | TextButton( 110 | onPressed: () => Navigator.pop(context), 111 | child: const Text("Cancel")), 112 | TextButton( 113 | onPressed: () { 114 | DatabaseService.removeRecipe( 115 | widget.recipe.id!); 116 | widget.reloadRecipes(); 117 | Navigator.pop(context); 118 | Navigator.pop(context); 119 | }, 120 | child: const Text("Yes")), 121 | ], 122 | )); 123 | }, 124 | ), 125 | ], 126 | ), 127 | body: SingleChildScrollView( 128 | child: Padding( 129 | padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5), 130 | child: Column( 131 | crossAxisAlignment: CrossAxisAlignment.start, 132 | children: [ 133 | Text( 134 | widget.recipe.title ?? "", 135 | style: const TextStyle( 136 | fontSize: 30, fontWeight: FontWeight.bold), 137 | ), 138 | if (imageBytes != null) const SizedBox(height: 25), 139 | if (imageBytes != null) 140 | ClipRRect( 141 | borderRadius: BorderRadius.circular(15), 142 | child: Image.memory( 143 | height: 200, 144 | imageBytes!, 145 | )), 146 | const SizedBox( 147 | height: 15, 148 | ), 149 | Visibility( 150 | visible: tags.isNotEmpty, 151 | child: SizedBox( 152 | height: 50, 153 | child: Row(children: [ 154 | const Text("Tags: "), 155 | const SizedBox( 156 | width: 20, 157 | ), 158 | ListView.builder( 159 | shrinkWrap: true, 160 | scrollDirection: Axis.horizontal, 161 | itemCount: tags.length, 162 | itemBuilder: (context, index) { 163 | return Chip( 164 | label: Text(tags[index].name ?? "")); 165 | }) 166 | ])), 167 | ), 168 | Text((widget.recipe.servings ?? "").isNotEmpty 169 | ? "Servings: ${widget.recipe.servings}" 170 | : ""), 171 | const SizedBox( 172 | height: 15, 173 | ), 174 | const Text( 175 | "Ingredients :", 176 | style: 177 | TextStyle(fontSize: 20, fontWeight: FontWeight.bold), 178 | ), 179 | ListView.builder( 180 | physics: const NeverScrollableScrollPhysics(), 181 | shrinkWrap: true, 182 | itemCount: ingredientsList.length, 183 | itemBuilder: (BuildContext context, int index) { 184 | return CheckboxListTile( 185 | visualDensity: const VisualDensity( 186 | vertical: VisualDensity.minimumDensity), 187 | controlAffinity: ListTileControlAffinity.leading, 188 | value: checkboxValuesIngredients[index] ?? false, 189 | title: Text(ingredientsList[index]), 190 | onChanged: (value) { 191 | setState(() { 192 | checkboxValuesIngredients[index] = value!; 193 | }); 194 | }); 195 | }), 196 | const Text( 197 | "Preparation :", 198 | style: 199 | TextStyle(fontSize: 20, fontWeight: FontWeight.bold), 200 | ), 201 | ListView.builder( 202 | physics: const NeverScrollableScrollPhysics(), 203 | shrinkWrap: true, 204 | itemCount: stepsList.length, 205 | itemBuilder: (BuildContext context, int index) { 206 | return CheckboxListTile( 207 | controlAffinity: ListTileControlAffinity.leading, 208 | value: checkboxValuesSteps[index] ?? false, 209 | title: Opacity( 210 | opacity: (checkboxValuesSteps[index] ?? false) 211 | ? 0.5 212 | : 1, 213 | child: Text( 214 | stepsList[index], 215 | ), 216 | ), 217 | onChanged: (value) { 218 | setState(() { 219 | checkboxValuesSteps[index] = value!; 220 | }); 221 | }); 222 | }), 223 | Visibility( 224 | visible: Uri.tryParse(widget.recipe.source ?? "") 225 | ?.isAbsolute ?? 226 | false, 227 | child: ListTile( 228 | title: Text( 229 | "Source: ${widget.recipe.source}", 230 | style: const TextStyle(color: Colors.blue), 231 | ), 232 | onTap: () { 233 | launchUrl(Uri.parse(widget.recipe.source!)); 234 | })) 235 | ], 236 | )))); 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /lib/screens/settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:reciper/screens/home.dart'; 3 | import 'package:reciper/screens/pages_layout.dart'; 4 | import 'package:reciper/utilities/utils.dart'; 5 | import 'package:url_launcher/url_launcher.dart'; 6 | 7 | class Settings extends StatelessWidget { 8 | const Settings({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Scaffold( 13 | appBar: AppBar( 14 | automaticallyImplyLeading: false, 15 | title: const Text('Settings'), 16 | centerTitle: true, 17 | ), 18 | body: SingleChildScrollView( 19 | padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), 20 | child: Column( 21 | crossAxisAlignment: CrossAxisAlignment.stretch, 22 | children: [ 23 | const Text( 24 | "Import", 25 | style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), 26 | ), 27 | const SizedBox(height: 20), 28 | ElevatedButton( 29 | onPressed: () { 30 | Utils.userImport().then((value) => Navigator.of(context).push( 31 | MaterialPageRoute( 32 | builder: (context) => 33 | const PagesLayout(child: HomePage())))); 34 | }, 35 | child: const Text("Import recipes"), 36 | ), 37 | const SizedBox(height: 20), 38 | const Text( 39 | "Export", 40 | style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), 41 | ), 42 | const SizedBox(height: 20), 43 | ElevatedButton( 44 | onPressed: () { 45 | Utils.userExport(); 46 | }, 47 | child: const Text("Export recipes"), 48 | ), 49 | const SizedBox(height: 20), 50 | ElevatedButton( 51 | onPressed: () { 52 | Utils.userPdfExport(); 53 | }, 54 | child: const Text("Export recipes to PDF"), 55 | ), 56 | const SizedBox(height: 20), 57 | const Text( 58 | "About", 59 | style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), 60 | ), 61 | Column( 62 | crossAxisAlignment: CrossAxisAlignment.start, 63 | children: [ 64 | const Text( 65 | "Reciper has been developed with ❤️ by Judemont", 66 | style: TextStyle(fontSize: 16), 67 | ), 68 | const SizedBox(height: 20), 69 | ElevatedButton.icon( 70 | onPressed: () => launchUrl( 71 | Uri.parse('https://github.com/judemont/reciper'), 72 | ), 73 | icon: const Icon(Icons.link), 74 | label: const Text("Source code (GitHub)"), 75 | ), 76 | const SizedBox(height: 20), 77 | ElevatedButton.icon( 78 | onPressed: () => launchUrl( 79 | Uri.parse( 80 | 'https://play.google.com/store/apps/details?id=jdm.apps.reciper'), 81 | ), 82 | icon: const Icon(Icons.star_rate_rounded), 83 | label: const Text("Rate Reciper on Google Play"), 84 | ), 85 | ], 86 | ), 87 | ], 88 | ), 89 | ), 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib/utilities/database.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:reciper/models/tag.dart'; 4 | import 'package:sqflite/sqflite.dart'; 5 | import 'package:path/path.dart'; 6 | import '../models/recipe.dart'; 7 | import 'package:path_provider/path_provider.dart'; 8 | 9 | class DatabaseService { 10 | static const String databaseName = "reciperDB.sqlite"; 11 | static Database? db; 12 | 13 | static const databaseVersion = 5; 14 | List tables = ["Recipes", "Tags", "TagsLinks"]; 15 | 16 | static Future initializeDb() async { 17 | final databasePath = (await getApplicationDocumentsDirectory()).path; 18 | final path = join(databasePath, databaseName); 19 | return db ?? 20 | await openDatabase( 21 | path, 22 | version: databaseVersion, 23 | onCreate: (Database db, int version) async { 24 | await createTables(db); 25 | }, 26 | onUpgrade: (db, oldVersion, newVersion) async { 27 | await updateTables(db, oldVersion, newVersion); 28 | }, 29 | onOpen: (db) async { 30 | await openDB(db); 31 | }, 32 | ); 33 | } 34 | 35 | static openDB(Database db) { 36 | db.rawQuery('SELECT * FROM sqlite_master ORDER BY name;').then((value) {}); 37 | } 38 | 39 | static updateTables(Database db, int oldVersion, int newVersion) { 40 | print(" DB Version : $newVersion"); 41 | print(oldVersion); 42 | if (oldVersion < newVersion) { 43 | if (oldVersion < 2) { 44 | db.execute("""ALTER TABLE Recipes ADD COLUMN servings TEXT """); 45 | } 46 | if (oldVersion < 3) { 47 | db.execute("""ALTER TABLE Recipes ADD COLUMN source TEXT """); 48 | } 49 | if (oldVersion < 4) { 50 | db.execute(""" 51 | CREATE TABLE Tags( 52 | id INTEGER PRIMARY KEY AUTOINCREMENT, 53 | name TEXT 54 | ) 55 | """); 56 | 57 | db.execute(""" 58 | CREATE TABLE TagsLinks( 59 | recipeId INTEGER, 60 | tagId INTEGER 61 | ) 62 | """); 63 | } 64 | if (oldVersion < 5) { 65 | db.execute("""ALTER TABLE Recipes ADD COLUMN image BLOB """); 66 | } 67 | } 68 | } 69 | 70 | static Future createTables(Database database) async { 71 | await database.execute(""" 72 | CREATE TABLE Recipes( 73 | id INTEGER PRIMARY KEY AUTOINCREMENT, 74 | steps TEXT, 75 | title TEXT NOT NULL, 76 | servings TEXT, 77 | ingredients TEXT, 78 | source TEXT, 79 | image BLOB 80 | ) 81 | """); 82 | 83 | await database.execute(""" 84 | CREATE TABLE Tags( 85 | id INTEGER PRIMARY KEY AUTOINCREMENT, 86 | name TEXT 87 | ) 88 | """); 89 | await database.execute(""" 90 | CREATE TABLE TagsLinks( 91 | recipeId INTEGER, 92 | tagId INTEGER 93 | ) 94 | """); 95 | } 96 | 97 | static Future createRecipe(Recipe recipe) async { 98 | final db = await DatabaseService.initializeDb(); 99 | 100 | final id = await db.insert( 101 | 'Recipes', 102 | Recipe( 103 | steps: recipe.steps, 104 | title: recipe.title, 105 | ingredients: recipe.ingredients, 106 | servings: recipe.servings, 107 | source: recipe.source, 108 | image: recipe.image) 109 | .toMap()); 110 | return id; 111 | } 112 | 113 | static Future createTag(Tag tag) async { 114 | final db = await DatabaseService.initializeDb(); 115 | 116 | final id = await db.insert('Tags', Tag(name: tag.name).toMap()); 117 | return id; 118 | } 119 | 120 | static Future createTagLink(int tagId, int recipeId) async { 121 | final db = await DatabaseService.initializeDb(); 122 | 123 | final id = 124 | await db.insert('TagsLinks', {"tagId": tagId, "recipeId": recipeId}); 125 | return id; 126 | } 127 | 128 | static Future> getRecipes({String searchQuery = ""}) async { 129 | final db = await DatabaseService.initializeDb(); 130 | 131 | List> queryResult = 132 | await db.query('Recipes', where: "title LIKE '%$searchQuery%'"); 133 | 134 | return queryResult.map((e) => Recipe.fromMap(e)).toList(); 135 | } 136 | 137 | static Future> getTags() async { 138 | final db = await DatabaseService.initializeDb(); 139 | 140 | List> queryResult = await db.query('Tags'); 141 | 142 | return queryResult.map((e) => Tag.fromMap(e)).toList(); 143 | } 144 | 145 | static Future> getTagsFromRecipe(int recipeId) async { 146 | final db = await DatabaseService.initializeDb(); 147 | 148 | List> recipeTagsLinks = 149 | await db.query('TagsLinks', where: "recipeId = $recipeId"); 150 | 151 | List recipeTags = []; 152 | 153 | for (var tagLink in recipeTagsLinks) { 154 | List> tag = 155 | await db.query('Tags', where: "id = ${tagLink['tagId']}"); 156 | 157 | tag.isNotEmpty ? recipeTags.add(Tag.fromMap(tag[0])) : null; 158 | } 159 | 160 | return recipeTags; 161 | } 162 | 163 | static Future> getRecipesFromTag(int tagId, 164 | {String searchQuery = ""}) async { 165 | final db = await DatabaseService.initializeDb(); 166 | 167 | List> tagRecipesLinks = 168 | await db.query('TagsLinks', where: "tagId = $tagId"); 169 | 170 | List tagsRecipe = []; 171 | 172 | for (var tagLink in tagRecipesLinks) { 173 | List> recipes = await db.query('Recipes', 174 | where: "id = ${tagLink['recipeId']} AND title LIKE '%$searchQuery%'"); 175 | 176 | recipes.isNotEmpty ? tagsRecipe.add(Recipe.fromMap(recipes[0])) : null; 177 | } 178 | 179 | return tagsRecipe; 180 | } 181 | 182 | static Future getRecipe(int id) async { 183 | final db = await DatabaseService.initializeDb(); 184 | 185 | final List> queryResult = 186 | await db.query('Recipes', where: "id = $id"); 187 | 188 | return Recipe( 189 | id: queryResult[0]["id"] as int, 190 | steps: queryResult[0]["steps"] as String?, 191 | title: queryResult[0]["title"] as String?, 192 | ingredients: queryResult[0]["ingredients"] as String?, 193 | source: queryResult[0]["source"] as String?, 194 | image: queryResult[0]["image"] as String?, 195 | ); 196 | } 197 | 198 | static Future removeRecipe(int recipeId) async { 199 | final db = await DatabaseService.initializeDb(); 200 | db.delete("Recipes", where: "id = $recipeId"); 201 | } 202 | 203 | static Future removeTag(int tagId) async { 204 | final db = await DatabaseService.initializeDb(); 205 | db.delete("Tags", where: "id = $tagId"); 206 | } 207 | 208 | static Future removeTagLink({int? tagId, int? recipeId}) async { 209 | final db = await DatabaseService.initializeDb(); 210 | db.delete("TagsLinks", 211 | where: "tagId = ${tagId ?? "tagId"} AND recipeId = ${recipeId ?? "*"}"); 212 | } 213 | 214 | static Future updateRecipe(Recipe recipe) async { 215 | final db = await DatabaseService.initializeDb(); 216 | db.update("Recipes", recipe.toMap(), 217 | where: 'id = ?', whereArgs: [recipe.id]); 218 | } 219 | 220 | static Future updateTag(Tag tag) async { 221 | final db = await DatabaseService.initializeDb(); 222 | 223 | db.update("Tags", tag.toMap(), where: 'id = ?', whereArgs: [tag.id]); 224 | } 225 | 226 | Future export({bool isEncrypted = false}) async { 227 | var dbs = await DatabaseService.initializeDb(); 228 | 229 | List data = []; 230 | 231 | List> listMaps = []; 232 | 233 | for (var i = 0; i < tables.length; i++) { 234 | listMaps = await dbs.query(tables[i]); 235 | 236 | data.add(listMaps); 237 | } 238 | 239 | List backups = [tables, data]; 240 | 241 | String jsonData = jsonEncode(backups); 242 | return jsonData; 243 | } 244 | 245 | Future import(String backup) async { 246 | var dbs = await DatabaseService.initializeDb(); 247 | 248 | Batch batch = dbs.batch(); 249 | 250 | List jsonData = jsonDecode(backup); 251 | List actualRecipes = await DatabaseService.getRecipes(); 252 | 253 | for (var i = 0; i < jsonData[0].length; i++) { 254 | for (var k = 0; k < jsonData[1][i].length; k++) { 255 | if (actualRecipes 256 | .where((recipe) => recipe.title == jsonData[1][i][k]["title"]) 257 | .isEmpty) { 258 | if (jsonData[1][i][k]["id"] != null) { 259 | jsonData[1][i][k]["id"] = null; 260 | } 261 | batch.insert(jsonData[0][i], jsonData[1][i][k]); 262 | } 263 | } 264 | } 265 | 266 | await batch.commit(continueOnError: false, noResult: true); 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /lib/utilities/utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | import 'package:pdf/pdf.dart'; 4 | import 'package:pdf/widgets.dart' as pw; 5 | import 'package:file_selector/file_selector.dart'; 6 | import 'package:path_provider/path_provider.dart'; 7 | import 'package:reciper/models/recipe.dart'; 8 | import 'package:reciper/utilities/database.dart'; 9 | import 'package:share_plus/share_plus.dart'; 10 | 11 | class Utils { 12 | static const Encoding _textFileEncoding = utf8; 13 | 14 | static Future userExport() async { 15 | Directory directory = await getTemporaryDirectory(); 16 | String appDocumentsPath = directory.path; 17 | String filePath = '$appDocumentsPath/Reciper_Export.json'; 18 | 19 | File file = File(filePath); 20 | 21 | DatabaseService db = DatabaseService(); 22 | String result = await db.export(); 23 | var fileBytes = _textFileEncoding.encode(result); 24 | await file.writeAsBytes(fileBytes); 25 | await Share.shareXFiles([XFile(filePath)]); 26 | } 27 | 28 | static Future userImport() async { 29 | const XTypeGroup typeGroup = XTypeGroup( 30 | label: 'Reciper export', 31 | extensions: ['json'], 32 | ); 33 | final XFile? file = 34 | await openFile(acceptedTypeGroups: [typeGroup]); 35 | 36 | if (file != null) { 37 | var fileBytes = await file.readAsBytes(); 38 | String backupContent = _textFileEncoding.decode(fileBytes); 39 | DatabaseService db = DatabaseService(); 40 | await db.import(backupContent); 41 | } 42 | return 1; 43 | } 44 | 45 | static Future userPdfExport() async { 46 | Directory directory = await getTemporaryDirectory(); 47 | String appDocumentsPath = directory.path; 48 | String filePath = '$appDocumentsPath/recipes.pdf'; 49 | 50 | File file = File(filePath); 51 | 52 | final pdf = pw.Document(); 53 | List recipes = await DatabaseService.getRecipes(); 54 | 55 | for (var recipe in recipes) { 56 | pdf.addPage(pw.MultiPage( 57 | pageFormat: PdfPageFormat.a4, 58 | build: (pw.Context context) => [ 59 | pw.ListView(children: [ 60 | pw.Text(recipe.title ?? "", 61 | style: pw.TextStyle( 62 | fontSize: 35, fontWeight: pw.FontWeight.bold)), 63 | pw.SizedBox(height: 20), 64 | if (recipe.image != null) 65 | pw.Image( 66 | pw.MemoryImage(Base64Decoder().convert(recipe.image!)), 67 | width: 300, 68 | height: 300), 69 | pw.SizedBox(height: 60), 70 | pw.Text("Ingredients : ", 71 | style: pw.TextStyle( 72 | fontSize: 20, fontWeight: pw.FontWeight.bold)), 73 | pw.SizedBox(height: 20), 74 | pw.Text(recipe.ingredients ?? "", 75 | overflow: pw.TextOverflow.span), 76 | pw.SizedBox(height: 40), 77 | pw.Text("Instructions : ", 78 | style: pw.TextStyle( 79 | fontSize: 20, fontWeight: pw.FontWeight.bold)), 80 | pw.SizedBox(height: 20), 81 | pw.Text(recipe.steps ?? "", overflow: pw.TextOverflow.span), 82 | ]) 83 | ])); 84 | } 85 | 86 | await file.writeAsBytes(await pdf.save()); 87 | Share.shareXFiles([XFile(filePath)]); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/widgets/bottom_nav_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class BottomNavBar extends StatefulWidget { 4 | final int selectedIndex; 5 | final Function(int) onDestinationSelected; 6 | const BottomNavBar( 7 | {super.key, this.selectedIndex = 0, required this.onDestinationSelected}); 8 | 9 | @override 10 | State createState() => _BottomNavBarState(); 11 | } 12 | 13 | class _BottomNavBarState extends State { 14 | @override 15 | Widget build(BuildContext context) { 16 | return NavigationBar( 17 | selectedIndex: widget.selectedIndex, 18 | onDestinationSelected: widget.onDestinationSelected, 19 | destinations: const [ 20 | NavigationDestination(icon: Icon(Icons.home), label: "Home"), 21 | NavigationDestination(icon: Icon(Icons.add_circle), label: "New"), 22 | NavigationDestination(icon: Icon(Icons.settings), label: "Settings"), 23 | ], 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/widgets/extract_recipe_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:reciper/widgets/extract_recipe_dialog.dart'; 3 | 4 | class ExtractRecipeButton extends StatefulWidget { 5 | const ExtractRecipeButton({super.key}); 6 | 7 | @override 8 | State createState() => _ExtractRecipeButtonState(); 9 | } 10 | 11 | class _ExtractRecipeButtonState extends State { 12 | @override 13 | Widget build(BuildContext context) { 14 | return FloatingActionButton( 15 | child: const Icon(Icons.add_link), 16 | onPressed: () => showDialog( 17 | context: context, builder: (context) => const ExtractRecipeDialog()), 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/widgets/extract_recipe_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:recipe_extractor/recipe_extractor.dart'; 5 | import 'package:reciper/models/recipe.dart'; 6 | import 'package:reciper/screens/pages_layout.dart'; 7 | import 'package:reciper/screens/recipe_editor.dart'; 8 | import 'package:http/http.dart' as http; 9 | 10 | class ExtractRecipeDialog extends StatefulWidget { 11 | const ExtractRecipeDialog({super.key}); 12 | 13 | @override 14 | State createState() => _ExtractRecipeDialogState(); 15 | } 16 | 17 | class _ExtractRecipeDialogState extends State { 18 | @override 19 | Widget build(BuildContext context) { 20 | TextEditingController recipeSiteUrlController = TextEditingController(); 21 | 22 | return AlertDialog( 23 | title: const Text("Extract recipe from a website"), 24 | content: Column( 25 | mainAxisSize: MainAxisSize.min, 26 | children: [ 27 | TextField( 28 | controller: recipeSiteUrlController, 29 | decoration: 30 | const InputDecoration(hintText: "Enter Recipe website url"), 31 | ), 32 | ], 33 | ), 34 | actions: [ 35 | TextButton( 36 | onPressed: () => Navigator.pop(context, 'Cancel'), 37 | child: const Text('Cancel'), 38 | ), 39 | TextButton( 40 | onPressed: () async { 41 | String recipeUrl = recipeSiteUrlController.text; 42 | RecipeData recipeData = RecipeData(); 43 | try { 44 | recipeData = await extractRecipe(recipeUrl); 45 | } on Exception { 46 | SnackBar errorBar = const SnackBar( 47 | content: Text("Failed to extract recipe"), 48 | ); 49 | 50 | if (context.mounted) { 51 | ScaffoldMessenger.of(context).showSnackBar(errorBar); 52 | } 53 | } 54 | if (recipeData.name == null && 55 | recipeData.ingredients == null && 56 | recipeData.instructions == null) { 57 | print("Failed to extract recipe"); 58 | SnackBar errorBar = const SnackBar( 59 | content: Text("Failed to extract recipe"), 60 | ); 61 | 62 | if (context.mounted) { 63 | ScaffoldMessenger.of(context).showSnackBar(errorBar); 64 | } 65 | } else { 66 | String? imageBase64; 67 | final response = 68 | await http.get(Uri.parse(recipeData.image ?? "")); 69 | print(recipeData.image); 70 | if (response.statusCode == 200) { 71 | imageBase64 = base64Encode(response.bodyBytes); 72 | } else { 73 | print("Failed to load image"); 74 | } 75 | if (context.mounted) { 76 | Navigator.push( 77 | context, 78 | MaterialPageRoute( 79 | builder: (context) => PagesLayout( 80 | currentSection: 1, 81 | child: RecipeEditorPage( 82 | initialRecipe: Recipe( 83 | title: recipeData.name ?? "", 84 | servings: recipeData.servings ?? "", 85 | ingredients: 86 | (recipeData.ingredients ?? []).join("\n"), 87 | steps: 88 | (recipeData.instructions ?? []).join("\n"), 89 | source: recipeData.source, 90 | image: imageBase64, 91 | ), 92 | ), 93 | )), 94 | ); 95 | } 96 | } 97 | }, 98 | child: const Text('OK'), 99 | ), 100 | ], 101 | ); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /lib/widgets/new_tag_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:reciper/models/tag.dart'; 3 | import 'package:reciper/utilities/database.dart'; 4 | 5 | class NewTagDialog extends StatelessWidget { 6 | const NewTagDialog({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | TextEditingController newTagInputController = TextEditingController(); 11 | 12 | return AlertDialog( 13 | title: const Text("New Tag"), 14 | content: TextField( 15 | controller: newTagInputController, 16 | decoration: const InputDecoration(hintText: "Tag name"), 17 | ), 18 | actions: [ 19 | TextButton( 20 | child: const Text("cancel"), 21 | onPressed: () { 22 | Navigator.pop(context); 23 | }), 24 | TextButton( 25 | child: const Text("save"), 26 | onPressed: () { 27 | DatabaseService.createTag(Tag(name: newTagInputController.text)); 28 | 29 | Navigator.pop(context); 30 | }, 31 | ), 32 | ]); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/widgets/recipe_tag_selector.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:reciper/models/tag.dart'; 3 | import 'package:reciper/widgets/new_tag_dialog.dart'; 4 | 5 | class RecipeTagSelector extends StatefulWidget { 6 | final List tags; 7 | final List selectedTagsId; 8 | 9 | final Function onTagsSelectionUpdate; 10 | final Function onTagsUpdate; 11 | 12 | const RecipeTagSelector( 13 | {super.key, 14 | required this.tags, 15 | required this.onTagsUpdate, 16 | required this.selectedTagsId, 17 | required this.onTagsSelectionUpdate}); 18 | 19 | @override 20 | State createState() => _RecipeTagSelectorState(); 21 | } 22 | 23 | class _RecipeTagSelectorState extends State { 24 | @override 25 | Widget build(BuildContext context) { 26 | return Row(children: [ 27 | ListView.builder( 28 | itemCount: widget.tags.length, 29 | shrinkWrap: true, 30 | scrollDirection: Axis.horizontal, 31 | itemBuilder: ((context, index) { 32 | return FilledButton( 33 | style: FilledButton.styleFrom( 34 | backgroundColor: 35 | widget.selectedTagsId.contains(widget.tags[index].id) 36 | ? Theme.of(context).colorScheme.primary 37 | : Theme.of(context).colorScheme.secondary), 38 | // : 39 | // ? Theme.of(context).primaryColor 40 | // : Colors.transparent, 41 | child: Row(children: [ 42 | widget.selectedTagsId.contains(widget.tags[index].id) 43 | ? const Icon(Icons.check) 44 | : const Text(""), 45 | Text(widget.tags[index].name ?? "") 46 | ]), 47 | onPressed: () { 48 | bool value = 49 | !widget.selectedTagsId.contains(widget.tags[index].id); 50 | if (value) { 51 | widget.selectedTagsId.add(widget.tags[index].id!); 52 | } else { 53 | widget.selectedTagsId.remove(widget.tags[index].id); 54 | } 55 | widget.onTagsSelectionUpdate(widget.selectedTagsId); 56 | widget.onTagsUpdate(widget.tags); 57 | }); 58 | })), 59 | IconButton( 60 | icon: const Icon( 61 | Icons.add, 62 | ), 63 | onPressed: () { 64 | showDialog( 65 | context: context, 66 | builder: (context) => const NewTagDialog()) 67 | .then((value) => widget.onTagsUpdate()); 68 | }) 69 | ]); 70 | } 71 | } 72 | 73 | /* 74 | DropdownButton( 75 | onChanged: (value) {}, 76 | items: widget.tags.map>((Tag tag) { 77 | return DropdownMenuItem( 78 | value: tag.id.toString(), 79 | child: Row( 80 | children: [ 81 | Checkbox( 82 | value: widget.selectedTagsId.contains(tag.id), 83 | onChanged: (value) { 84 | if (value ?? false) { 85 | widget.selectedTagsId.add(tag.id!); 86 | } else { 87 | widget.selectedTagsId.remove(tag.id); 88 | } 89 | widget.onTagsSelectionUpdate(widget.selectedTagsId); 90 | widget.onTagsUpdate(widget.tags); 91 | }), 92 | Text(tag.name ?? "") 93 | ], 94 | ), 95 | ); 96 | }).toList(), 97 | ); 98 | */ 99 | -------------------------------------------------------------------------------- /lib/widgets/reciper_list_tile.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:reciper/models/recipe.dart'; 5 | import 'package:reciper/screens/pages_layout.dart'; 6 | import 'package:reciper/screens/recipe_view.dart'; 7 | 8 | class RecipeListTile extends StatefulWidget { 9 | final List selectedRecipesID; 10 | final Recipe recipe; 11 | final Function(List) onRecipesSelectionUpdate; 12 | final Function reloadRecipes; 13 | const RecipeListTile( 14 | {super.key, 15 | required this.recipe, 16 | required this.selectedRecipesID, 17 | required this.onRecipesSelectionUpdate, 18 | required this.reloadRecipes}); 19 | 20 | @override 21 | State createState() => _RecipeListTileState(); 22 | } 23 | 24 | class _RecipeListTileState extends State { 25 | @override 26 | Widget build(BuildContext context) { 27 | return ListTile( 28 | contentPadding: const EdgeInsets.all(8), 29 | title: Text(widget.recipe.title ?? ""), 30 | selected: widget.selectedRecipesID.contains(widget.recipe.id), 31 | leading: widget.recipe.image != null 32 | ? Image.memory( 33 | Base64Decoder().convert(widget.recipe.image!), 34 | width: 120, 35 | height: 120, 36 | fit: BoxFit.cover, 37 | ) 38 | : SizedBox(width: 120, child: Icon(Icons.cake, size: 50)), 39 | onLongPress: () { 40 | if (widget.selectedRecipesID.contains(widget.recipe.id)) { 41 | widget.selectedRecipesID.remove(widget.recipe.id); 42 | } else { 43 | widget.selectedRecipesID.add(widget.recipe.id!); 44 | } 45 | widget.onRecipesSelectionUpdate(widget.selectedRecipesID); 46 | }, 47 | onTap: () { 48 | if (widget.selectedRecipesID.contains(widget.recipe.id!)) { 49 | widget.selectedRecipesID.remove(widget.recipe.id!); 50 | } else if (widget.selectedRecipesID.isNotEmpty) { 51 | widget.selectedRecipesID.add(widget.recipe.id!); 52 | } else { 53 | Navigator.of(context).push( 54 | MaterialPageRoute( 55 | builder: (context) => PagesLayout( 56 | displayBottomNavBar: false, 57 | child: RecipeViewPage( 58 | recipe: widget.recipe, 59 | reloadRecipes: widget.reloadRecipes, 60 | ), 61 | )), 62 | ); 63 | } 64 | 65 | widget.onRecipesSelectionUpdate(widget.selectedRecipesID); 66 | }, 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/widgets/recipes_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:reciper/widgets/reciper_list_tile.dart'; 3 | import '../models/recipe.dart'; 4 | 5 | class RecipeListView extends StatefulWidget { 6 | final List recipes; 7 | final List selectedRecipesID; 8 | final Function(List) onRecipesSelectionUpdate; 9 | final Function reloadRecipes; 10 | const RecipeListView( 11 | {super.key, 12 | required this.recipes, 13 | required this.onRecipesSelectionUpdate, 14 | required this.selectedRecipesID, 15 | required this.reloadRecipes}); 16 | 17 | @override 18 | State createState() => _RecipeListViewState(); 19 | } 20 | 21 | class _RecipeListViewState extends State { 22 | @override 23 | Widget build(BuildContext context) { 24 | List selectedRecipesID = widget.selectedRecipesID; 25 | return ListView.builder( 26 | shrinkWrap: true, 27 | itemCount: widget.recipes.length, 28 | itemBuilder: (BuildContext context, int index) { 29 | return RecipeListTile( 30 | recipe: widget.recipes[index], 31 | selectedRecipesID: selectedRecipesID, 32 | onRecipesSelectionUpdate: widget.onRecipesSelectionUpdate, 33 | reloadRecipes: widget.reloadRecipes); 34 | }, 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/widgets/tag_actions_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:reciper/models/tag.dart'; 3 | import 'package:reciper/utilities/database.dart'; 4 | 5 | class TagActionDialog extends StatelessWidget { 6 | final Tag tag; 7 | const TagActionDialog({super.key, required this.tag}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | TextEditingController newTagInputController = 12 | TextEditingController(text: tag.name); 13 | 14 | return AlertDialog( 15 | title: const Text("Tag options"), 16 | content: TextField( 17 | controller: newTagInputController, 18 | decoration: const InputDecoration(hintText: "Rename Tag"), 19 | ), 20 | actions: [ 21 | TextButton( 22 | child: const Text("cancel"), 23 | onPressed: () { 24 | Navigator.pop(context); 25 | }), 26 | TextButton( 27 | child: const Text("delete tag"), 28 | onPressed: () { 29 | DatabaseService.removeTag(tag.id!); 30 | Navigator.pop(context); 31 | }), 32 | TextButton( 33 | child: const Text("save"), 34 | onPressed: () { 35 | tag.name = newTagInputController.text; 36 | DatabaseService.updateTag(tag); 37 | 38 | Navigator.pop(context); 39 | }, 40 | ), 41 | ]); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/widgets/tags_selector.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:reciper/models/tag.dart'; 3 | import 'package:reciper/widgets/new_tag_dialog.dart'; 4 | import 'package:reciper/widgets/tag_actions_dialog.dart'; 5 | 6 | class TagsSelector extends StatefulWidget { 7 | final List tags; 8 | final List selectedTagsId; 9 | 10 | final Function onTagsSelectionUpdate; 11 | final Function onTagsUpdate; 12 | 13 | const TagsSelector( 14 | {super.key, 15 | required this.tags, 16 | required this.onTagsUpdate, 17 | required this.selectedTagsId, 18 | required this.onTagsSelectionUpdate}); 19 | 20 | @override 21 | State createState() => _TagsSelectorState(); 22 | } 23 | 24 | class _TagsSelectorState extends State { 25 | bool isAllChecked = true; 26 | 27 | // @override 28 | // void initState() { 29 | // super.initState(); 30 | // List allTagIds = widget.tags.map((e) => e.id!).toList(); 31 | // widget.selectedTagsId.removeWhere((element) => true); 32 | // widget.selectedTagsId.addAll(allTagIds); 33 | // widget.onTagsSelectionUpdate(widget.selectedTagsId); 34 | // } 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | return Drawer( 39 | child: ListView( 40 | padding: EdgeInsets.zero, 41 | children: [ 42 | DrawerHeader( 43 | decoration: BoxDecoration( 44 | color: Theme.of(context).colorScheme.primaryContainer, 45 | ), 46 | child: const Text( 47 | 'Tags', 48 | style: TextStyle( 49 | color: Colors.white, 50 | fontSize: 24, 51 | ), 52 | ), 53 | ), 54 | CheckboxListTile( 55 | title: const Text("All"), 56 | value: widget.selectedTagsId.length == widget.tags.length, 57 | secondary: const Icon(Icons.select_all), 58 | onChanged: (value) { 59 | setState(() { 60 | isAllChecked = value ?? false; 61 | if (isAllChecked) { 62 | List allTagIds = widget.tags.map((e) => e.id!).toList(); 63 | widget.selectedTagsId.clear(); 64 | widget.selectedTagsId.addAll(allTagIds); 65 | } else { 66 | widget.selectedTagsId.clear(); 67 | } 68 | widget.onTagsSelectionUpdate(widget.selectedTagsId); 69 | }); 70 | }, 71 | ), 72 | ListView.builder( 73 | shrinkWrap: true, 74 | itemCount: widget.tags.length, 75 | itemBuilder: (BuildContext context, int index) { 76 | return GestureDetector( 77 | onLongPress: () => showDialog( 78 | context: context, 79 | builder: (context) => 80 | TagActionDialog(tag: widget.tags[index])).then((value) { 81 | widget.onTagsUpdate(); 82 | widget.selectedTagsId.clear(); 83 | }), 84 | child: CheckboxListTile( 85 | value: widget.selectedTagsId.contains(widget.tags[index].id), 86 | title: Text(widget.tags[index].name ?? ""), 87 | onChanged: (value) { 88 | setState(() { 89 | if (value ?? false) { 90 | !widget.selectedTagsId.contains(widget.tags[index].id!) 91 | ? widget.selectedTagsId.add(widget.tags[index].id!) 92 | : null; 93 | } else { 94 | widget.selectedTagsId.remove(widget.tags[index].id); 95 | } 96 | widget.onTagsSelectionUpdate(widget.selectedTagsId); 97 | widget.onTagsUpdate(); 98 | }); 99 | }, 100 | ), 101 | ); 102 | }, 103 | ), 104 | ListTile( 105 | leading: const Icon(Icons.add), 106 | title: const Text('New Tag'), 107 | onTap: () { 108 | showDialog( 109 | context: context, 110 | builder: (context) => const NewTagDialog()).then((value) { 111 | widget.onTagsUpdate().then((value) { 112 | widget.onTagsSelectionUpdate([]); 113 | }); 114 | }); 115 | }, 116 | ), 117 | ], 118 | ), 119 | ); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "76.0.0" 12 | _macros: 13 | dependency: transitive 14 | description: dart 15 | source: sdk 16 | version: "0.3.3" 17 | analyzer: 18 | dependency: transitive 19 | description: 20 | name: analyzer 21 | sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" 22 | url: "https://pub.dev" 23 | source: hosted 24 | version: "6.11.0" 25 | archive: 26 | dependency: transitive 27 | description: 28 | name: archive 29 | sha256: "7dcbd0f87fe5f61cb28da39a1a8b70dbc106e2fe0516f7836eb7bb2948481a12" 30 | url: "https://pub.dev" 31 | source: hosted 32 | version: "4.0.5" 33 | args: 34 | dependency: transitive 35 | description: 36 | name: args 37 | sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 38 | url: "https://pub.dev" 39 | source: hosted 40 | version: "2.7.0" 41 | async: 42 | dependency: transitive 43 | description: 44 | name: async 45 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 46 | url: "https://pub.dev" 47 | source: hosted 48 | version: "2.11.0" 49 | barcode: 50 | dependency: transitive 51 | description: 52 | name: barcode 53 | sha256: "7b6729c37e3b7f34233e2318d866e8c48ddb46c1f7ad01ff7bb2a8de1da2b9f4" 54 | url: "https://pub.dev" 55 | source: hosted 56 | version: "2.2.9" 57 | bidi: 58 | dependency: transitive 59 | description: 60 | name: bidi 61 | sha256: "77f475165e94b261745cf1032c751e2032b8ed92ccb2bf5716036db79320637d" 62 | url: "https://pub.dev" 63 | source: hosted 64 | version: "2.0.13" 65 | boolean_selector: 66 | dependency: transitive 67 | description: 68 | name: boolean_selector 69 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 70 | url: "https://pub.dev" 71 | source: hosted 72 | version: "2.1.1" 73 | characters: 74 | dependency: transitive 75 | description: 76 | name: characters 77 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 78 | url: "https://pub.dev" 79 | source: hosted 80 | version: "1.3.0" 81 | checked_yaml: 82 | dependency: transitive 83 | description: 84 | name: checked_yaml 85 | sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff 86 | url: "https://pub.dev" 87 | source: hosted 88 | version: "2.0.3" 89 | cli_config: 90 | dependency: transitive 91 | description: 92 | name: cli_config 93 | sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec 94 | url: "https://pub.dev" 95 | source: hosted 96 | version: "0.2.0" 97 | cli_util: 98 | dependency: transitive 99 | description: 100 | name: cli_util 101 | sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c 102 | url: "https://pub.dev" 103 | source: hosted 104 | version: "0.4.2" 105 | clock: 106 | dependency: transitive 107 | description: 108 | name: clock 109 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 110 | url: "https://pub.dev" 111 | source: hosted 112 | version: "1.1.1" 113 | collection: 114 | dependency: transitive 115 | description: 116 | name: collection 117 | sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf 118 | url: "https://pub.dev" 119 | source: hosted 120 | version: "1.19.0" 121 | convert: 122 | dependency: transitive 123 | description: 124 | name: convert 125 | sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 126 | url: "https://pub.dev" 127 | source: hosted 128 | version: "3.1.2" 129 | coverage: 130 | dependency: transitive 131 | description: 132 | name: coverage 133 | sha256: "9086475ef2da7102a0c0a4e37e1e30707e7fb7b6d28c209f559a9c5f8ce42016" 134 | url: "https://pub.dev" 135 | source: hosted 136 | version: "1.12.0" 137 | cross_file: 138 | dependency: transitive 139 | description: 140 | name: cross_file 141 | sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" 142 | url: "https://pub.dev" 143 | source: hosted 144 | version: "0.3.4+2" 145 | crypto: 146 | dependency: transitive 147 | description: 148 | name: crypto 149 | sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" 150 | url: "https://pub.dev" 151 | source: hosted 152 | version: "3.0.6" 153 | csslib: 154 | dependency: transitive 155 | description: 156 | name: csslib 157 | sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" 158 | url: "https://pub.dev" 159 | source: hosted 160 | version: "1.0.2" 161 | dbus: 162 | dependency: transitive 163 | description: 164 | name: dbus 165 | sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" 166 | url: "https://pub.dev" 167 | source: hosted 168 | version: "0.7.11" 169 | fake_async: 170 | dependency: transitive 171 | description: 172 | name: fake_async 173 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 174 | url: "https://pub.dev" 175 | source: hosted 176 | version: "1.3.1" 177 | ffi: 178 | dependency: transitive 179 | description: 180 | name: ffi 181 | sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" 182 | url: "https://pub.dev" 183 | source: hosted 184 | version: "2.1.3" 185 | file: 186 | dependency: transitive 187 | description: 188 | name: file 189 | sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 190 | url: "https://pub.dev" 191 | source: hosted 192 | version: "7.0.1" 193 | file_selector: 194 | dependency: "direct main" 195 | description: 196 | name: file_selector 197 | sha256: "5019692b593455127794d5718304ff1ae15447dea286cdda9f0db2a796a1b828" 198 | url: "https://pub.dev" 199 | source: hosted 200 | version: "1.0.3" 201 | file_selector_android: 202 | dependency: transitive 203 | description: 204 | name: file_selector_android 205 | sha256: "98ac58e878b05ea2fdb204e7f4fc4978d90406c9881874f901428e01d3b18fbc" 206 | url: "https://pub.dev" 207 | source: hosted 208 | version: "0.5.1+12" 209 | file_selector_ios: 210 | dependency: transitive 211 | description: 212 | name: file_selector_ios 213 | sha256: "94b98ad950b8d40d96fee8fa88640c2e4bd8afcdd4817993bd04e20310f45420" 214 | url: "https://pub.dev" 215 | source: hosted 216 | version: "0.5.3+1" 217 | file_selector_linux: 218 | dependency: transitive 219 | description: 220 | name: file_selector_linux 221 | sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" 222 | url: "https://pub.dev" 223 | source: hosted 224 | version: "0.9.3+2" 225 | file_selector_macos: 226 | dependency: transitive 227 | description: 228 | name: file_selector_macos 229 | sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" 230 | url: "https://pub.dev" 231 | source: hosted 232 | version: "0.9.4+2" 233 | file_selector_platform_interface: 234 | dependency: transitive 235 | description: 236 | name: file_selector_platform_interface 237 | sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b 238 | url: "https://pub.dev" 239 | source: hosted 240 | version: "2.6.2" 241 | file_selector_web: 242 | dependency: transitive 243 | description: 244 | name: file_selector_web 245 | sha256: c4c0ea4224d97a60a7067eca0c8fd419e708ff830e0c83b11a48faf566cec3e7 246 | url: "https://pub.dev" 247 | source: hosted 248 | version: "0.9.4+2" 249 | file_selector_windows: 250 | dependency: transitive 251 | description: 252 | name: file_selector_windows 253 | sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" 254 | url: "https://pub.dev" 255 | source: hosted 256 | version: "0.9.3+4" 257 | fixnum: 258 | dependency: transitive 259 | description: 260 | name: fixnum 261 | sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be 262 | url: "https://pub.dev" 263 | source: hosted 264 | version: "1.1.1" 265 | flex_color_scheme: 266 | dependency: "direct main" 267 | description: 268 | name: flex_color_scheme 269 | sha256: "90f4fe67b9561ae8a4af117df65a8ce9988624025667c54e6d304e65cff77d52" 270 | url: "https://pub.dev" 271 | source: hosted 272 | version: "8.0.2" 273 | flex_seed_scheme: 274 | dependency: transitive 275 | description: 276 | name: flex_seed_scheme 277 | sha256: "7639d2c86268eff84a909026eb169f008064af0fb3696a651b24b0fa24a40334" 278 | url: "https://pub.dev" 279 | source: hosted 280 | version: "3.4.1" 281 | flutter: 282 | dependency: "direct main" 283 | description: flutter 284 | source: sdk 285 | version: "0.0.0" 286 | flutter_launcher_icons: 287 | dependency: "direct main" 288 | description: 289 | name: flutter_launcher_icons 290 | sha256: bfa04787c85d80ecb3f8777bde5fc10c3de809240c48fa061a2c2bf15ea5211c 291 | url: "https://pub.dev" 292 | source: hosted 293 | version: "0.14.3" 294 | flutter_lints: 295 | dependency: "direct dev" 296 | description: 297 | name: flutter_lints 298 | sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" 299 | url: "https://pub.dev" 300 | source: hosted 301 | version: "5.0.0" 302 | flutter_plugin_android_lifecycle: 303 | dependency: transitive 304 | description: 305 | name: flutter_plugin_android_lifecycle 306 | sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e 307 | url: "https://pub.dev" 308 | source: hosted 309 | version: "2.0.28" 310 | flutter_test: 311 | dependency: "direct dev" 312 | description: flutter 313 | source: sdk 314 | version: "0.0.0" 315 | flutter_web_plugins: 316 | dependency: transitive 317 | description: flutter 318 | source: sdk 319 | version: "0.0.0" 320 | frontend_server_client: 321 | dependency: transitive 322 | description: 323 | name: frontend_server_client 324 | sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 325 | url: "https://pub.dev" 326 | source: hosted 327 | version: "4.0.0" 328 | glob: 329 | dependency: transitive 330 | description: 331 | name: glob 332 | sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de 333 | url: "https://pub.dev" 334 | source: hosted 335 | version: "2.1.3" 336 | html: 337 | dependency: transitive 338 | description: 339 | name: html 340 | sha256: "9475be233c437f0e3637af55e7702cbbe5c23a68bd56e8a5fa2d426297b7c6c8" 341 | url: "https://pub.dev" 342 | source: hosted 343 | version: "0.15.5+1" 344 | http: 345 | dependency: transitive 346 | description: 347 | name: http 348 | sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f 349 | url: "https://pub.dev" 350 | source: hosted 351 | version: "1.3.0" 352 | http_multi_server: 353 | dependency: transitive 354 | description: 355 | name: http_multi_server 356 | sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 357 | url: "https://pub.dev" 358 | source: hosted 359 | version: "3.2.2" 360 | http_parser: 361 | dependency: transitive 362 | description: 363 | name: http_parser 364 | sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" 365 | url: "https://pub.dev" 366 | source: hosted 367 | version: "4.0.2" 368 | image: 369 | dependency: transitive 370 | description: 371 | name: image 372 | sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" 373 | url: "https://pub.dev" 374 | source: hosted 375 | version: "4.5.4" 376 | image_picker: 377 | dependency: "direct main" 378 | description: 379 | name: image_picker 380 | sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" 381 | url: "https://pub.dev" 382 | source: hosted 383 | version: "1.1.2" 384 | image_picker_android: 385 | dependency: transitive 386 | description: 387 | name: image_picker_android 388 | sha256: "317a5d961cec5b34e777b9252393f2afbd23084aa6e60fcf601dcf6341b9ebeb" 389 | url: "https://pub.dev" 390 | source: hosted 391 | version: "0.8.12+23" 392 | image_picker_for_web: 393 | dependency: transitive 394 | description: 395 | name: image_picker_for_web 396 | sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83" 397 | url: "https://pub.dev" 398 | source: hosted 399 | version: "3.0.6" 400 | image_picker_ios: 401 | dependency: transitive 402 | description: 403 | name: image_picker_ios 404 | sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100" 405 | url: "https://pub.dev" 406 | source: hosted 407 | version: "0.8.12+2" 408 | image_picker_linux: 409 | dependency: transitive 410 | description: 411 | name: image_picker_linux 412 | sha256: "34a65f6740df08bbbeb0a1abd8e6d32107941fd4868f67a507b25601651022c9" 413 | url: "https://pub.dev" 414 | source: hosted 415 | version: "0.2.1+2" 416 | image_picker_macos: 417 | dependency: transitive 418 | description: 419 | name: image_picker_macos 420 | sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1" 421 | url: "https://pub.dev" 422 | source: hosted 423 | version: "0.2.1+2" 424 | image_picker_platform_interface: 425 | dependency: transitive 426 | description: 427 | name: image_picker_platform_interface 428 | sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0" 429 | url: "https://pub.dev" 430 | source: hosted 431 | version: "2.10.1" 432 | image_picker_windows: 433 | dependency: transitive 434 | description: 435 | name: image_picker_windows 436 | sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" 437 | url: "https://pub.dev" 438 | source: hosted 439 | version: "0.2.1+1" 440 | io: 441 | dependency: transitive 442 | description: 443 | name: io 444 | sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b 445 | url: "https://pub.dev" 446 | source: hosted 447 | version: "1.0.5" 448 | js: 449 | dependency: transitive 450 | description: 451 | name: js 452 | sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf 453 | url: "https://pub.dev" 454 | source: hosted 455 | version: "0.7.1" 456 | json_annotation: 457 | dependency: transitive 458 | description: 459 | name: json_annotation 460 | sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" 461 | url: "https://pub.dev" 462 | source: hosted 463 | version: "4.9.0" 464 | leak_tracker: 465 | dependency: transitive 466 | description: 467 | name: leak_tracker 468 | sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" 469 | url: "https://pub.dev" 470 | source: hosted 471 | version: "10.0.7" 472 | leak_tracker_flutter_testing: 473 | dependency: transitive 474 | description: 475 | name: leak_tracker_flutter_testing 476 | sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" 477 | url: "https://pub.dev" 478 | source: hosted 479 | version: "3.0.8" 480 | leak_tracker_testing: 481 | dependency: transitive 482 | description: 483 | name: leak_tracker_testing 484 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" 485 | url: "https://pub.dev" 486 | source: hosted 487 | version: "3.0.1" 488 | lints: 489 | dependency: transitive 490 | description: 491 | name: lints 492 | sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413" 493 | url: "https://pub.dev" 494 | source: hosted 495 | version: "5.0.0" 496 | logging: 497 | dependency: transitive 498 | description: 499 | name: logging 500 | sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 501 | url: "https://pub.dev" 502 | source: hosted 503 | version: "1.3.0" 504 | macros: 505 | dependency: transitive 506 | description: 507 | name: macros 508 | sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" 509 | url: "https://pub.dev" 510 | source: hosted 511 | version: "0.1.3-main.0" 512 | matcher: 513 | dependency: transitive 514 | description: 515 | name: matcher 516 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb 517 | url: "https://pub.dev" 518 | source: hosted 519 | version: "0.12.16+1" 520 | material_color_utilities: 521 | dependency: transitive 522 | description: 523 | name: material_color_utilities 524 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec 525 | url: "https://pub.dev" 526 | source: hosted 527 | version: "0.11.1" 528 | meta: 529 | dependency: transitive 530 | description: 531 | name: meta 532 | sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 533 | url: "https://pub.dev" 534 | source: hosted 535 | version: "1.15.0" 536 | mime: 537 | dependency: transitive 538 | description: 539 | name: mime 540 | sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" 541 | url: "https://pub.dev" 542 | source: hosted 543 | version: "1.0.6" 544 | node_preamble: 545 | dependency: transitive 546 | description: 547 | name: node_preamble 548 | sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" 549 | url: "https://pub.dev" 550 | source: hosted 551 | version: "2.0.2" 552 | package_config: 553 | dependency: transitive 554 | description: 555 | name: package_config 556 | sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc 557 | url: "https://pub.dev" 558 | source: hosted 559 | version: "2.2.0" 560 | package_info_plus: 561 | dependency: transitive 562 | description: 563 | name: package_info_plus 564 | sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" 565 | url: "https://pub.dev" 566 | source: hosted 567 | version: "8.3.0" 568 | package_info_plus_platform_interface: 569 | dependency: transitive 570 | description: 571 | name: package_info_plus_platform_interface 572 | sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" 573 | url: "https://pub.dev" 574 | source: hosted 575 | version: "3.2.0" 576 | path: 577 | dependency: "direct main" 578 | description: 579 | name: path 580 | sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" 581 | url: "https://pub.dev" 582 | source: hosted 583 | version: "1.9.0" 584 | path_parsing: 585 | dependency: transitive 586 | description: 587 | name: path_parsing 588 | sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" 589 | url: "https://pub.dev" 590 | source: hosted 591 | version: "1.1.0" 592 | path_provider: 593 | dependency: "direct main" 594 | description: 595 | name: path_provider 596 | sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" 597 | url: "https://pub.dev" 598 | source: hosted 599 | version: "2.1.5" 600 | path_provider_android: 601 | dependency: transitive 602 | description: 603 | name: path_provider_android 604 | sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" 605 | url: "https://pub.dev" 606 | source: hosted 607 | version: "2.2.15" 608 | path_provider_foundation: 609 | dependency: transitive 610 | description: 611 | name: path_provider_foundation 612 | sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" 613 | url: "https://pub.dev" 614 | source: hosted 615 | version: "2.4.1" 616 | path_provider_linux: 617 | dependency: transitive 618 | description: 619 | name: path_provider_linux 620 | sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 621 | url: "https://pub.dev" 622 | source: hosted 623 | version: "2.2.1" 624 | path_provider_platform_interface: 625 | dependency: transitive 626 | description: 627 | name: path_provider_platform_interface 628 | sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" 629 | url: "https://pub.dev" 630 | source: hosted 631 | version: "2.1.2" 632 | path_provider_windows: 633 | dependency: transitive 634 | description: 635 | name: path_provider_windows 636 | sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 637 | url: "https://pub.dev" 638 | source: hosted 639 | version: "2.3.0" 640 | pdf: 641 | dependency: "direct main" 642 | description: 643 | name: pdf 644 | sha256: "28eacad99bffcce2e05bba24e50153890ad0255294f4dd78a17075a2ba5c8416" 645 | url: "https://pub.dev" 646 | source: hosted 647 | version: "3.11.3" 648 | petitparser: 649 | dependency: transitive 650 | description: 651 | name: petitparser 652 | sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 653 | url: "https://pub.dev" 654 | source: hosted 655 | version: "6.0.2" 656 | platform: 657 | dependency: transitive 658 | description: 659 | name: platform 660 | sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" 661 | url: "https://pub.dev" 662 | source: hosted 663 | version: "3.1.6" 664 | plugin_platform_interface: 665 | dependency: transitive 666 | description: 667 | name: plugin_platform_interface 668 | sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" 669 | url: "https://pub.dev" 670 | source: hosted 671 | version: "2.1.8" 672 | pool: 673 | dependency: transitive 674 | description: 675 | name: pool 676 | sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" 677 | url: "https://pub.dev" 678 | source: hosted 679 | version: "1.5.1" 680 | posix: 681 | dependency: transitive 682 | description: 683 | name: posix 684 | sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a 685 | url: "https://pub.dev" 686 | source: hosted 687 | version: "6.0.1" 688 | pub_semver: 689 | dependency: transitive 690 | description: 691 | name: pub_semver 692 | sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" 693 | url: "https://pub.dev" 694 | source: hosted 695 | version: "2.2.0" 696 | qr: 697 | dependency: transitive 698 | description: 699 | name: qr 700 | sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445" 701 | url: "https://pub.dev" 702 | source: hosted 703 | version: "3.0.2" 704 | recipe_extractor: 705 | dependency: "direct main" 706 | description: 707 | name: recipe_extractor 708 | sha256: "7e473fc763f1ca66f937c2f103fa8ed37dadb4f404b284a9f5633f2df63dd7e6" 709 | url: "https://pub.dev" 710 | source: hosted 711 | version: "2.8.0" 712 | share_plus: 713 | dependency: "direct main" 714 | description: 715 | name: share_plus 716 | sha256: "59dfd53f497340a0c3a81909b220cfdb9b8973a91055c4e5ab9b9b9ad7c513c0" 717 | url: "https://pub.dev" 718 | source: hosted 719 | version: "10.0.0" 720 | share_plus_platform_interface: 721 | dependency: transitive 722 | description: 723 | name: share_plus_platform_interface 724 | sha256: cc012a23fc2d479854e6c80150696c4a5f5bb62cb89af4de1c505cf78d0a5d0b 725 | url: "https://pub.dev" 726 | source: hosted 727 | version: "5.0.2" 728 | shelf: 729 | dependency: transitive 730 | description: 731 | name: shelf 732 | sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 733 | url: "https://pub.dev" 734 | source: hosted 735 | version: "1.4.1" 736 | shelf_packages_handler: 737 | dependency: transitive 738 | description: 739 | name: shelf_packages_handler 740 | sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" 741 | url: "https://pub.dev" 742 | source: hosted 743 | version: "3.0.2" 744 | shelf_static: 745 | dependency: transitive 746 | description: 747 | name: shelf_static 748 | sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 749 | url: "https://pub.dev" 750 | source: hosted 751 | version: "1.1.3" 752 | shelf_web_socket: 753 | dependency: transitive 754 | description: 755 | name: shelf_web_socket 756 | sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 757 | url: "https://pub.dev" 758 | source: hosted 759 | version: "2.0.1" 760 | sky_engine: 761 | dependency: transitive 762 | description: flutter 763 | source: sdk 764 | version: "0.0.0" 765 | source_map_stack_trace: 766 | dependency: transitive 767 | description: 768 | name: source_map_stack_trace 769 | sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b 770 | url: "https://pub.dev" 771 | source: hosted 772 | version: "2.1.2" 773 | source_maps: 774 | dependency: transitive 775 | description: 776 | name: source_maps 777 | sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" 778 | url: "https://pub.dev" 779 | source: hosted 780 | version: "0.10.13" 781 | source_span: 782 | dependency: transitive 783 | description: 784 | name: source_span 785 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 786 | url: "https://pub.dev" 787 | source: hosted 788 | version: "1.10.0" 789 | sprintf: 790 | dependency: transitive 791 | description: 792 | name: sprintf 793 | sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" 794 | url: "https://pub.dev" 795 | source: hosted 796 | version: "7.0.0" 797 | sqflite: 798 | dependency: "direct main" 799 | description: 800 | name: sqflite 801 | sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb" 802 | url: "https://pub.dev" 803 | source: hosted 804 | version: "2.4.1" 805 | sqflite_android: 806 | dependency: transitive 807 | description: 808 | name: sqflite_android 809 | sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3" 810 | url: "https://pub.dev" 811 | source: hosted 812 | version: "2.4.0" 813 | sqflite_common: 814 | dependency: "direct main" 815 | description: 816 | name: sqflite_common 817 | sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709" 818 | url: "https://pub.dev" 819 | source: hosted 820 | version: "2.5.4+6" 821 | sqflite_common_ffi: 822 | dependency: "direct main" 823 | description: 824 | name: sqflite_common_ffi 825 | sha256: "883dd810b2b49e6e8c3b980df1829ef550a94e3f87deab5d864917d27ca6bf36" 826 | url: "https://pub.dev" 827 | source: hosted 828 | version: "2.3.4+4" 829 | sqflite_darwin: 830 | dependency: transitive 831 | description: 832 | name: sqflite_darwin 833 | sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c" 834 | url: "https://pub.dev" 835 | source: hosted 836 | version: "2.4.1+1" 837 | sqflite_platform_interface: 838 | dependency: transitive 839 | description: 840 | name: sqflite_platform_interface 841 | sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" 842 | url: "https://pub.dev" 843 | source: hosted 844 | version: "2.4.0" 845 | sqlite3: 846 | dependency: transitive 847 | description: 848 | name: sqlite3 849 | sha256: fde692580bee3379374af1f624eb3e113ab2865ecb161dbe2d8ac2de9735dbdb 850 | url: "https://pub.dev" 851 | source: hosted 852 | version: "2.4.5" 853 | sqlite3_flutter_libs: 854 | dependency: "direct main" 855 | description: 856 | name: sqlite3_flutter_libs 857 | sha256: "1a96b59227828d9eb1463191d684b37a27d66ee5ed7597fcf42eee6452c88a14" 858 | url: "https://pub.dev" 859 | source: hosted 860 | version: "0.5.32" 861 | stack_trace: 862 | dependency: transitive 863 | description: 864 | name: stack_trace 865 | sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" 866 | url: "https://pub.dev" 867 | source: hosted 868 | version: "1.12.0" 869 | stream_channel: 870 | dependency: transitive 871 | description: 872 | name: stream_channel 873 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 874 | url: "https://pub.dev" 875 | source: hosted 876 | version: "2.1.2" 877 | string_scanner: 878 | dependency: transitive 879 | description: 880 | name: string_scanner 881 | sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" 882 | url: "https://pub.dev" 883 | source: hosted 884 | version: "1.3.0" 885 | synchronized: 886 | dependency: transitive 887 | description: 888 | name: synchronized 889 | sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" 890 | url: "https://pub.dev" 891 | source: hosted 892 | version: "3.3.0+3" 893 | term_glyph: 894 | dependency: transitive 895 | description: 896 | name: term_glyph 897 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 898 | url: "https://pub.dev" 899 | source: hosted 900 | version: "1.2.1" 901 | test: 902 | dependency: "direct main" 903 | description: 904 | name: test 905 | sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f" 906 | url: "https://pub.dev" 907 | source: hosted 908 | version: "1.25.8" 909 | test_api: 910 | dependency: transitive 911 | description: 912 | name: test_api 913 | sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" 914 | url: "https://pub.dev" 915 | source: hosted 916 | version: "0.7.3" 917 | test_core: 918 | dependency: transitive 919 | description: 920 | name: test_core 921 | sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d" 922 | url: "https://pub.dev" 923 | source: hosted 924 | version: "0.6.5" 925 | typed_data: 926 | dependency: transitive 927 | description: 928 | name: typed_data 929 | sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 930 | url: "https://pub.dev" 931 | source: hosted 932 | version: "1.4.0" 933 | url_launcher: 934 | dependency: "direct main" 935 | description: 936 | name: url_launcher 937 | sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" 938 | url: "https://pub.dev" 939 | source: hosted 940 | version: "6.3.1" 941 | url_launcher_android: 942 | dependency: transitive 943 | description: 944 | name: url_launcher_android 945 | sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" 946 | url: "https://pub.dev" 947 | source: hosted 948 | version: "6.3.14" 949 | url_launcher_ios: 950 | dependency: transitive 951 | description: 952 | name: url_launcher_ios 953 | sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" 954 | url: "https://pub.dev" 955 | source: hosted 956 | version: "6.3.3" 957 | url_launcher_linux: 958 | dependency: transitive 959 | description: 960 | name: url_launcher_linux 961 | sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" 962 | url: "https://pub.dev" 963 | source: hosted 964 | version: "3.2.1" 965 | url_launcher_macos: 966 | dependency: transitive 967 | description: 968 | name: url_launcher_macos 969 | sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" 970 | url: "https://pub.dev" 971 | source: hosted 972 | version: "3.2.2" 973 | url_launcher_platform_interface: 974 | dependency: transitive 975 | description: 976 | name: url_launcher_platform_interface 977 | sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" 978 | url: "https://pub.dev" 979 | source: hosted 980 | version: "2.3.2" 981 | url_launcher_web: 982 | dependency: transitive 983 | description: 984 | name: url_launcher_web 985 | sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" 986 | url: "https://pub.dev" 987 | source: hosted 988 | version: "2.3.3" 989 | url_launcher_windows: 990 | dependency: transitive 991 | description: 992 | name: url_launcher_windows 993 | sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" 994 | url: "https://pub.dev" 995 | source: hosted 996 | version: "3.1.4" 997 | uuid: 998 | dependency: transitive 999 | description: 1000 | name: uuid 1001 | sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff 1002 | url: "https://pub.dev" 1003 | source: hosted 1004 | version: "4.5.1" 1005 | vector_math: 1006 | dependency: transitive 1007 | description: 1008 | name: vector_math 1009 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 1010 | url: "https://pub.dev" 1011 | source: hosted 1012 | version: "2.1.4" 1013 | vm_service: 1014 | dependency: transitive 1015 | description: 1016 | name: vm_service 1017 | sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b 1018 | url: "https://pub.dev" 1019 | source: hosted 1020 | version: "14.3.0" 1021 | wakelock_plus: 1022 | dependency: "direct main" 1023 | description: 1024 | name: wakelock_plus 1025 | sha256: b90fbcc8d7bdf3b883ea9706d9d76b9978cb1dfa4351fcc8014d6ec31a493354 1026 | url: "https://pub.dev" 1027 | source: hosted 1028 | version: "1.2.11" 1029 | wakelock_plus_platform_interface: 1030 | dependency: transitive 1031 | description: 1032 | name: wakelock_plus_platform_interface 1033 | sha256: "70e780bc99796e1db82fe764b1e7dcb89a86f1e5b3afb1db354de50f2e41eb7a" 1034 | url: "https://pub.dev" 1035 | source: hosted 1036 | version: "1.2.2" 1037 | watcher: 1038 | dependency: transitive 1039 | description: 1040 | name: watcher 1041 | sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" 1042 | url: "https://pub.dev" 1043 | source: hosted 1044 | version: "1.1.1" 1045 | web: 1046 | dependency: transitive 1047 | description: 1048 | name: web 1049 | sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" 1050 | url: "https://pub.dev" 1051 | source: hosted 1052 | version: "0.5.1" 1053 | web_socket: 1054 | dependency: transitive 1055 | description: 1056 | name: web_socket 1057 | sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" 1058 | url: "https://pub.dev" 1059 | source: hosted 1060 | version: "0.1.6" 1061 | web_socket_channel: 1062 | dependency: transitive 1063 | description: 1064 | name: web_socket_channel 1065 | sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" 1066 | url: "https://pub.dev" 1067 | source: hosted 1068 | version: "3.0.2" 1069 | webkit_inspection_protocol: 1070 | dependency: transitive 1071 | description: 1072 | name: webkit_inspection_protocol 1073 | sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" 1074 | url: "https://pub.dev" 1075 | source: hosted 1076 | version: "1.2.1" 1077 | win32: 1078 | dependency: transitive 1079 | description: 1080 | name: win32 1081 | sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e 1082 | url: "https://pub.dev" 1083 | source: hosted 1084 | version: "5.10.1" 1085 | xdg_directories: 1086 | dependency: transitive 1087 | description: 1088 | name: xdg_directories 1089 | sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" 1090 | url: "https://pub.dev" 1091 | source: hosted 1092 | version: "1.1.0" 1093 | xml: 1094 | dependency: transitive 1095 | description: 1096 | name: xml 1097 | sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 1098 | url: "https://pub.dev" 1099 | source: hosted 1100 | version: "6.5.0" 1101 | yaml: 1102 | dependency: transitive 1103 | description: 1104 | name: yaml 1105 | sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce 1106 | url: "https://pub.dev" 1107 | source: hosted 1108 | version: "3.1.3" 1109 | sdks: 1110 | dart: ">=3.6.0 <4.0.0" 1111 | flutter: ">=3.27.0" 1112 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: reciper 2 | description: " 🍳 Your Ultimate Kitchen Companion! 📱 " 3 | # The following line prevents the package from being accidentally published to 4 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 5 | publish_to: "none" # Remove this line if you wish to publish to pub.dev 6 | 7 | # The following defines the version and build number for your application. 8 | # A version number is three numbers separated by dots, like 1.2.43 9 | # followed by an optional build number separated by a +. 10 | # Both the version and the builder number may be overridden in flutter 11 | # build by specifying --build-name and --build-number, respectively. 12 | # In Android, build-name is used as versionName while build-number used as versionCode. 13 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 14 | # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. 15 | # Read more about iOS versioning at 16 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 17 | # In Windows, build-name is used as the major, minor, and patch parts 18 | # of the product and file versions while build-number is used as the build suffix. 19 | version: 2.7.0+265 20 | 21 | environment: 22 | sdk: ">=3.3.1 <4.0.0" 23 | 24 | # Dependencies specify other packages that your package needs in order to work. 25 | # To automatically upgrade your package dependencies to the latest versions 26 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 27 | # dependencies can be manually updated by changing the version numbers below to 28 | # the latest version available on pub.dev. To see which dependencies have newer 29 | # versions available, run `flutter pub outdated`. 30 | dependencies: 31 | file_selector: ^1.0.3 32 | flex_color_scheme: ^8.0.2 33 | flutter: 34 | sdk: flutter 35 | flutter_launcher_icons: ^0.14.3 36 | image_picker: ^1.1.2 37 | path: ^1.9.0 38 | path_provider: ^2.1.2 39 | pdf: ^3.10.8 40 | recipe_extractor: ^2.8.0 41 | share_plus: ^10.0.0 42 | sqflite: ^2.3.3 43 | sqflite_common: ^2.5.4 44 | sqflite_common_ffi: ^2.3.3 45 | sqlite3_flutter_libs: ^0.5.20 46 | test: ^1.24.9 47 | url_launcher: ^6.2.5 48 | wakelock_plus: ^1.2.4 49 | 50 | # The following adds the Cupertino Icons font to your application. 51 | # Use with the CupertinoIcons class for iOS style icons. 52 | 53 | flutter_icons: 54 | android: "launcher_icon" 55 | ios: true 56 | image_path: "assets/icon.png" 57 | 58 | dev_dependencies: 59 | flutter_test: 60 | sdk: flutter 61 | 62 | # The "flutter_lints" package below contains a set of recommended lints to 63 | # encourage good coding practices. The lint set provided by the package is 64 | # activated in the `analysis_options.yaml` file located at the root of your 65 | # package. See that file for information about deactivating specific lint 66 | # rules and activating additional ones. 67 | flutter_lints: ^5.0.0 68 | 69 | # For information on the generic Dart part of this file, see the 70 | # following page: https://dart.dev/tools/pub/pubspec 71 | 72 | # The following section is specific to Flutter packages. 73 | flutter: 74 | # The following line ensures that the Material Icons font is 75 | # included with your application, so that you can use the icons in 76 | # the material Icons class. 77 | uses-material-design: true 78 | 79 | # To add assets to your application, add an assets section, like this: 80 | # assets: 81 | # - images/a_dot_burr.jpeg 82 | # - images/a_dot_ham.jpeg 83 | 84 | # An image asset can refer to one or more resolution-specific "variants", see 85 | # https://flutter.dev/assets-and-images/#resolution-aware 86 | 87 | # For details regarding adding assets from package dependencies, see 88 | # https://flutter.dev/assets-and-images/#from-packages 89 | 90 | # To add custom fonts to your application, add a fonts section here, 91 | # in this "flutter" section. Each entry in this list should have a 92 | # "family" key with the font family name, and a "fonts" key with a 93 | # list giving the asset and other descriptors for the font. For 94 | # example: 95 | # fonts: 96 | # - family: Schyler 97 | # fonts: 98 | # - asset: fonts/Schyler-Regular.ttf 99 | # - asset: fonts/Schyler-Italic.ttf 100 | # style: italic 101 | # - family: Trajan Pro 102 | # fonts: 103 | # - asset: fonts/TrajanPro.ttf 104 | # - asset: fonts/TrajanPro_Bold.ttf 105 | # weight: 700 106 | # 107 | # For details regarding fonts from package dependencies, 108 | # see https://flutter.dev/custom-fonts/#from-packages 109 | --------------------------------------------------------------------------------