├── .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 |
20 |
21 |
22 |
23 |
24 |
25 |
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 |
--------------------------------------------------------------------------------