Merge branch 'main' of https://github.com/flutter/samples into flutter3.27

pull/2483/head
Eric Windmill 2 weeks ago
commit 0eae8e2a6b

@ -8,8 +8,9 @@ reduce redundant work.*
- [ ] I read the [Flutter Style Guide] _recently_, and have followed its advice.
- [ ] I signed the [CLA].
- [ ] I read the [Contributors Guide].
- [ ] I have added sample code updates to the [changelog].
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] All existing and new tests are passing.
If you need help, consider asking for advice on the #hackers-devrel channel on [Discord].
@ -18,3 +19,4 @@ If you need help, consider asking for advice on the #hackers-devrel channel on [
[CLA]: https://cla.developers.google.com/
[Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md
[Contributors Guide]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md
[changelog]: ../CHANGELOG.md

@ -31,7 +31,7 @@ jobs:
with:
distribution: 'zulu'
java-version: '17'
- uses: subosito/flutter-action@44ac965b96f18d999802d4b807e3256d5a3f9fa1
- uses: subosito/flutter-action@f2c4f6686ca8e8d6e6d0f28410eeef506ed66aff
with:
channel: beta
- run: ./tool/flutter_ci_script_stable.sh
@ -47,7 +47,7 @@ jobs:
# with:
# distribution: 'zulu'
# java-version: '17'
# - uses: subosito/flutter-action@44ac965b96f18d999802d4b807e3256d5a3f9fa1
# - uses: subosito/flutter-action@f2c4f6686ca8e8d6e6d0f28410eeef506ed66aff
# with:
# channel: beta
# - run: ./tool/android_ci_script.sh
@ -63,7 +63,7 @@ jobs:
with:
distribution: 'zulu'
java-version: '17'
- uses: subosito/flutter-action@44ac965b96f18d999802d4b807e3256d5a3f9fa1
- uses: subosito/flutter-action@f2c4f6686ca8e8d6e6d0f28410eeef506ed66aff
with:
channel: beta
- run: ./tool/ios_ci_script.sh

@ -21,7 +21,7 @@ jobs:
submodules: true
fetch-depth: 0
- uses: subosito/flutter-action@44ac965b96f18d999802d4b807e3256d5a3f9fa1
- uses: subosito/flutter-action@f2c4f6686ca8e8d6e6d0f28410eeef506ed66aff
- name: Init scripts
run: dart pub get

@ -32,7 +32,7 @@ jobs:
with:
distribution: 'zulu'
java-version: '17'
- uses: subosito/flutter-action@44ac965b96f18d999802d4b807e3256d5a3f9fa1
- uses: subosito/flutter-action@f2c4f6686ca8e8d6e6d0f28410eeef506ed66aff
with:
channel: ${{ matrix.flutter_version }}
- run: ./tool/flutter_ci_script_${{ matrix.flutter_version }}.sh
@ -42,7 +42,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- uses: subosito/flutter-action@44ac965b96f18d999802d4b807e3256d5a3f9fa1
- uses: subosito/flutter-action@f2c4f6686ca8e8d6e6d0f28410eeef506ed66aff
- run: |
dart pub get
dart run grinder generate
@ -57,7 +57,7 @@ jobs:
# with:
# distribution: 'zulu'
# java-version: '17'
# - uses: subosito/flutter-action@44ac965b96f18d999802d4b807e3256d5a3f9fa1
# - uses: subosito/flutter-action@f2c4f6686ca8e8d6e6d0f28410eeef506ed66aff
# with:
# channel: stable
# - run: ./tool/android_ci_script.sh
@ -71,7 +71,7 @@ jobs:
with:
distribution: 'zulu'
java-version: '17'
- uses: subosito/flutter-action@44ac965b96f18d999802d4b807e3256d5a3f9fa1
- uses: subosito/flutter-action@f2c4f6686ca8e8d6e6d0f28410eeef506ed66aff
with:
channel: stable
- run: ./tool/ios_ci_script.sh

@ -25,7 +25,7 @@ jobs:
with:
submodules: true
fetch-depth: 0
- uses: subosito/flutter-action@44ac965b96f18d999802d4b807e3256d5a3f9fa1
- uses: subosito/flutter-action@f2c4f6686ca8e8d6e6d0f28410eeef506ed66aff
with:
channel: ${{ matrix.flutter_version }}
- name: Init scripts

@ -0,0 +1,32 @@
# Changelog
The purpose of this changelog is to track the freshness of samples and which
samples reflect *current best practices*. It describes **human-made, significant
** changes made to the repository or samples in the repository.
While all samples in this repository build and run, some of them were written
long ago, and no longer reflect what we want developers to learn. For example,
samples should have been refactored when Dart 3 released to include patterns and
records, where appropriate.
* **DO include:**
* The addition of new samples.
* The removal of existing samples.
* Considerable refactoring of any given sample.
* **DO NOT include:**
* Simple changes that reflect minor version bumps in Flutter. For example,
in a recent Flutter update, `Color.red` became `Color.r`.
* Dependency updates.
* Bug fixes.
* Any changes made to simply 'keep the lights on'.
# Log
| DATE (YYYY-MM-DD) | Sample(s) | author | Changes |
|-------------------|-------------------|--------------|-----------------------------------------------|
| NEXT GOES HERE | | | |
| | | | |
| 2024-12-04 | N/A - repo change | ericwindmill | Added changelog |
| 2024-11-27 | fake_sample | ericwindmill | Refactored fake_sample to use Dart 3 features |
| 2020-04-17 | fake_sample | ericwindmill | Created fake_sample |

@ -1,46 +1,45 @@
# Contributing
_See also: [Flutter's code of conduct](https://github.com/flutter/flutter/blob/master/CODE_OF_CONDUCT.md)_
Want to contribute to the Flutter sample ecosystem? Great! First, read this
page (including the small print at the end).
## Is this the right place for your contribution?
Do you work at Google? Great! You should **definitely** read this page before submitting a PR :)
This repo is used by members of the Flutter team and a few partners as a place
to store example apps and demos. It's not meant to be the one and only source of
truth for Flutter samples or the only place people go to learn about the best
ways to build with Flutter. What that means in practice is that if you've
written a great example app, it doesn't need to be maintained here in order to
get noticed, be of help to new Flutter devs, and have an impact on the
community.
* [Before you contribute]
* [Contribute enhancements and fixes]
* [Write a new sample]
* [Sample checklist]
You can maintain your sample app in your own repo (or with another source
control provider) and still be as important a part of the Flutter-verse as
anything you see here. You can let us know on the
[FlutterDev Google Group](https://groups.google.com/forum/#!forum/flutter-dev)
when you've published something and Tweet about it with the
[#FlutterDev](https://twitter.com/search?q=%23FlutterDev) hashtag.
_See also: [Flutter's code of conduct]_
## So what should be contributed here, then?
<br />
Fixes and necessary improvements to the existing samples, mostly.
# Before you contribute
## Is this the right place for your sample?
## Before you contribute
In most cases, if you've written a great sample app, it should be maintained
in your own repoistory. You can maintain your sample app in your own repo
(or with another source control provider) and still be as important a part of
the Flutter-verse as anything you see here.
### File an issue first!
**What should be contributed here, then?**
Fixes and necessary improvements to the existing samples, mostly.
## File an issue first!
If you see a bug or have an idea for a feature that you feel would improve one
of the samples already in the repo, **please
[file an issue](https://github.com/flutter/samples/issues/new) before you begin
of the samples already in the repo, **please [file an issue] before you begin
coding or send a PR**. This will help prevent duplicate work by letting us know
what you're up to. It will help avoid a situation in which you spend a lot of
time coding something that's not quite right for the repo or its goals.
### Sign the license agreement.
## Sign the license agreement.
Before we can use your code, you must sign the
[Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual)
[Google Individual Contributor License Agreement]
(CLA), which you can do online. The CLA is necessary mainly because you own the
copyright to your changes, even after your contribution becomes part of our
codebase, so we need your permission to use and distribute your code. We also
@ -54,47 +53,177 @@ us first through the issue tracker with your idea so that we can help out and
possibly guide you. Coordinating up front makes it much easier to avoid
frustration later on.
## A few ground rules
This is monorepo containing a bunch of projects. While different codebases have
different needs, there are a few basic conventions that every project here is
expected to follow. All of them are based on our experience over the last
couple years keeping the repo tidy and running smooth.
Each app should:
* Be designed to build against the current
[stable](https://github.com/flutter/flutter/blob/master/docs/releases/Flutter-build-release-channels.md)
release of the Flutter SDK.
* Include the top level
[`analysis_options.yaml`](analysis_options.yaml)
file used throughout the repo. This file include a base set of analyzer
conventions and lints.
* Have no analyzer errors or warnings.
* Be formatted with `dart format`.
* Include at least one working test in its `test` folder.
* Be wired into the list of projects in the CI scripts for [stable](tool/flutter_ci_script_stable.sh),
[beta](tool/flutter_ci_script_beta.sh), and [master](tool/flutter_ci_script_master.sh),
which runs the analyzer, the formatter, and `flutter test`.
* Add the new project directory to the [dependabot](.github/dependabot.yaml) configuration
to keep dependencies updated in an on-going basis.
* Avoid adding an onerous amount of blobs (typically images or other assets) to
the repo.
In addition, sample code is, at the end of the day, still code. It should be
written with at least as much care as the Flutter code you'd find in the SDK
itself. For that reason, most of the
[Flutter style guide](https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md)
also applies to code in this repo.
<br />
# Contribute enhancements and fixes
Enhancements and bug fixes are welcome and appreciated.
If you spot an [issue] that you might want to fix but aren't sure how to get started,
feel free to comment on the issue and tag @ericwindmill with questions.
## Filing issues and evaluating existing samples
You can also contribute by filing issues against existing samples. This is thankless work
and is _greatly_ appreciated. It's also a good way to familiarize yourself with the repository.
To evaluate a sample for possible issues, use the [Sample checklist] below.
<br />
# Writing a new sample
Before filing an issue for a new sample or submitting a PR, you must read the rest of this page.
## What is a sample?
Samples are reference materials that teach developers how to solve a specific problem.
Samples contain **correct and concise code** that developers can
**quickly understand** and **easily reuse** with minimal side effects.
### What samples should we have?
Samples in this repo demonstrates how to **use current features** of the **Flutter and Dart SDKs** using **best practices**.
In other words, a sample should meet the following criteria:
* Demonstrates _how to use the SDKS_ rather than show an end-product.
* Covers a critical developer journey in the SDK that is useful to >80% of developers.
* Prefers primitives in the SDK over libraries.
* Demonstrates features available on the stable channel of Flutter.
These guidelines can be broken in rare cases. For example, this is likely the best place in the
Flutter github org for demo apps, but those demo apps must be justified.
## Is this the right place for your sample? (revisited)
Validate your idea against the following criteria:
1. **The sample solves a single problem or set of tightly coupled problems.** <br/>
If not, you're either writing a Demo app or an extended sample. These might
still be appropriate for the samples repos, but need to be justified.
2. **The target audience for the sample isnt beginners.**
3. **The sample code isnt so complex that it needs video or text explanation.** <br />
Samples are reference materials, and the code should speak for itself. If either of these aren't true,
you may prefer to write a blogpost or tutorial.
5. **The sample isnt tied to an event.** <br />
If it is, you should likely use a personal repository.
5. **The sample isnt tied to a library or package.** <br />
If it is, the usage example should likely be in the [repository of that package].
6. **The topic doesnt have crossover with any existing samples.** <br />
If it does, ensure that it wouldn't be better to update the existing sample.
## What are your ongoing maintenance resposibilities?
All sample code has an unbound maintenance cost. If you cannot commit to maintaining a sample
(for the forseeable future, barring changes in life circumstances), then you should talk to the
[primary maintainers] of this project, particularly @ericwindmill, before submitting a PR.
Any new sample **must** have a CODEOWNER, and that owner is most likely you (the author).
## What type of sample are your writing?
This repository contains two types of sample apps: **quickstarts** and **demo apps**.
### Quickstarts
* **Purpose**: Provides starting points that a developer can extend.
Solves specific problems or implements generic app features.
* **Size**: <= 200 lines of code, not including boiler plate (guide, not rule)
* **Qualities:**
* Contains one feature or a small set of related features, but generally runnable.
* Generic and un-opinionated about the context in which it is used.
* Prioritizes being generic over best practices.
* The code is Copy+paste-able.
* In Flutter, this means that the code that is being demonstrated should be separated from and boiler plate (i.e. `runApp`)
* **Audience:**
* Developers with Flutter and Dart experience that don't need code explained to them.
* **Answers the questions:**
* “Whats the minimal amount of code needed to implement this feature…”
* “... so I can understand how it works?”
* “... so I can understand how to add it to my app?”
* “How can I make sure I know how to extend this feature without missing anything?”
* "What code should I use as a starting point given I want to solve a very similar problem?"
### Demo apps
* **Purpose**: Meant to be built and ran. Demos the _product_, not how to write code.
* **Criteria**: Demo apps _only_ belong here if they...
* ...aren't tied to an event or moment in time.
* ...aren't tied to another resource (website, workshop, etc).
* ...don't require explanation (other than a short README).
* ...aren't primarily used to demonstrate an SDK feature.
### Other sample types
Other sample types, like demo apps that accompany docs or events, do not belong in this repository.
They should be maintained alongside the accompanying resources (i.e. in the website repository), or in a personal repository.
### The `experimental` folder
Projects in the repo's top-level `experimental` directory are allowed to skirt
some of the above rules. These apps are either experimental in nature or use
APIs that haven't landed in the SDK's `stable` channel. They build against
`main`, and aren't (by default) wired into our CI system.
The experimental folder is being deprecated. If you have an experiemental sample project,
you should manage it in a personal repository until it runs on the stable channel.
<br />
# Sample checklist
Every piece of code will have value to someone, but it is easy for maintenance costs to grow over time.
The more items checked on this list, the higher the value and lower the maintenance costs.
Use this checklist to write new samples and evaluate existing ones.
**NB:** Demo apps have looser requirements. Use your best judgement when following the guidelines.
* Code
- [ ] Be designed to build against the current [stable]
release of the Flutter SDK.
- [ ] Sample is the only one for the API in question (across all Dash samples)
- [ ] Repository and pubspec name are titled for its ingredients (i.e. mvvm_architecture instead of compass_app)
- [ ] Only includes the minimal required code to demonstrate the feature or API and run the code.
- [ ] Sample favors 1P dependencies over 3P.
- [ ] Code has been sufficiently explained with doc comments.
- [ ] The sample app separates its bespoke code from general Flutter or Dart code.
- [ ] Favor readability over best practices unless readability requires an anti pattern. Use your best judgment.
- [ ] Avoid adding an onerous amount of blobs (typically images or other assets) to the repo.
- [ ] All files in the project must start with the appropriate [file headers].
* Tests, style and maintenance
- [ ] Sample has Dart tests that cover the business logic
- [ ] Sample is wired into the list of projects in the CI scripts for [stable](tool/flutter_ci_script_stable.sh),
[beta](tool/flutter_ci_script_beta.sh), and [master](tool/flutter_ci_script_master.sh),
which runs the analyzer, the formatter, and `flutter test`.
- [ ] Sample has a codeowner (it's likely you, the author)
- [ ] Sample is formatted with `dart format`.
- [ ] Sample has no analyzer errors or warnings.
- [ ] Add the new project directory to the [dependabot](.github/dependabot.yaml) configuration
to keep dependencies updated in an on-going basis.
- [ ] Follows the [Flutter style guide]
- [ ] Include the top level [`analysis_options.yaml`](analysis_options.yaml)
file used throughout the repo. This file include a base set of analyzer
conventions and lints.
* README
- [ ] Describes the apps design and purpose.
- [ ] Describes common errors and debugging steps.
- [ ] Describes any steps necessary to build and run.
- [ ] Has Open in IDX button, and a nix file (if compatible with IDX).
## File headers
## Code reviews
All files in the project must start with the following header.
```
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
```
<br />
# Code reviews
All submissions, including submissions by project members, require review.
@ -107,7 +236,7 @@ primary maintainers, though, are:
* [@domesticmouse](https://github.com/domesticmouse)
* [@ericwindmill](https://github.com/ericwindmill)
You are free to add one of these folks (particularly @RedBrogdon) as a reviewer
You are free to add one of these folks (particularly @ericwindmill) as a reviewer
to any PR sent to this repo. We're happy to comment, answer (or ask) questions,
and provide feedback.
@ -121,18 +250,24 @@ before, or changing something that's a meta-concern like the CI setup, web
hosting, project setup, etc., please include one of the primary maintainers as a
reviewer.
## File headers
All files in the project must start with the following header.
```
// Copyright 2024 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
```
<br />
## The small print
# The small print
Contributions made by corporations are covered by a different agreement than the
one above, the
[Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate).
one above, the [Software Grant and Corporate Contributor License Agreement].
[Flutter's code of conduct]: https://github.com/flutter/flutter/blob/master/CODE_OF_CONDUCT.md
[Before you contribute]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md#before-you-contribute
[Contribute enhancements and fixes]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md#contribute-enhancements-and-fixes
[Write a new sample]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md#write-a-new-sample
[Sample checklist]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md#samples-checklist
[file headers]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md#file-headers
[Software Grant and Corporate Contributor License Agreement]: https://developers.google.com/open-source/cla/corporate
[issue]: https://github.com/flutter/samples/issues
[file an issue]: https://github.com/flutter/samples/issues/new
[repository of that package]: https://dart.dev/tools/pub/package-layout#examples
[stable]: https://github.com/flutter/flutter/blob/master/docs/releases/Flutter-build-release-channels.md
[Flutter style guide]: (https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md)
[Google Individual Contributor License Agreement]: https://cla.developers.google.com/about/google-individual
[primary maintainers]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md#code-reviews

@ -3,26 +3,112 @@
[![Build Status](https://github.com/flutter/samples/workflows/Main%20Branch%20CI/badge.svg)](https://github.com/flutter/samples/actions?workflow=Main%20Branch%20CI)
A collection of open source samples that illustrate best practices for
[Flutter](https://flutter.dev).
## Visual samples index
The easiest way to browse through the samples in this repo (as well as a few others!)
is the [visual samples index](https://flutter.github.io/samples).
## Tip: minimize download size
As this repository is quite big, you can use svn to download a single example.
[Flutter].
## Index
### Quickstarts
* [`asset_transformation`] - Demonstrates how to transform images' color scales and formats.
* [`background_isolate_channels`] - Demonstrates how to use long-lived isolates.
* [`code_sharing`] - Demonstrates how to share business logic between Flutter client and Dart server using [`package:shelf`] )
* [`context_menus`] - This sample shows how to create and customize cross-platform context menus, such as the text selection toolbar on mobile or the right click menu on desktop.
* [`desktop_photo_search`] - Demonstrates desktop features in both Material and FluentUI design systems.
* [`dynamic_theme`] - A developer sample demonstrating how to call on-device Flutter APIs based on output from the Gemini API.
* [`form_app`] - A sample demonstrating different types of forms and best practices.
* [`game_template`] - (**note: deprecated!**) A starter game in Flutter with all the bells and whistles of a mobile (iOS & Android) game.
* [`gemini_tasks`] - A developer sample written in Flutter demonstrating how to interact with a to-do list in natural language using the Gemini API.
* [`google_maps`] - Demonstrates the Google Maps for Flutter plugin.
* [`infinite_list`] - A Flutter sample app that shows an implementation of the "infinite list" UX pattern.
* [`isolate_example`] - A sample application that demonstrate best practices when using [isolates].
* [`navigation_and_routing`] - A sample that shows how to use [go_router] API to handle common navigation scenarios.
* [`place_tracker`] - A sample place tracking app that uses the [google_maps_flutter plugin].
* [`platform_design`] - This sample project shows a Flutter app that maximizes application code reuse while adhering to different design patterns on Android and iOS.
* [`provider_counter`] - The starter Flutter application, but using [package:provider] to manage state.
* [`provider_shopper`] - A Flutter sample app that shows a state management approach using [package:provider].
* [`simple_shader`] - A simple [Flutter fragment shaders] sample project.
* [`simplistic_calculator`] - A calculator to demonstrate a simple start for a desktop Flutter app.
* [`simplistic_editor`] - This sample text editor showcases the use of [TextEditingDeltas] and a DeltaTextInputClient to expand and contract styled ranges of text.
* [`testing_app`] - A sample app that shows different types of testing in Flutter.
* [`web_embedding`] - This directory contains examples of how to embed Flutter in web apps (without iframes).
* [`element_embedding_demo`] - Modifies the index.html of a flutter app so it is launched in a custom hostElement. This is the most basic embedding example.
* [`ng-flutter`] - A simple Angular app (and component) that replicates the above example, but in an Angular style.
### Native platform samples
* [`add-to-app`] - Collection of samples that demonstrate embedding Flutter a view into a native app.
* [`fullscreen`] — Embeds a full screen instance of
Flutter into an existing iOS or Android app.
* [`prebuilt_module`] — Embeds a full screen
instance of Flutter as a prebuilt library that can be loaded into an existing
iOS or Android app.
* [`plugin`] — Embeds a full screen Flutter instance that
is using plugins into an existing iOS or Android app.
* [`books`] — Mimics a real world use-case of embedding Flutter into an
existing Android app and demonstrates using [Pigeon] to communicate between Flutter and
the host application.
* [`multiple_flutters`] — Shows the usage of the Flutter
Engine Group APIs to embed multiple instances of Flutter into an existing app
with low memory cost.
* [`android_view`] — Shows how to integrate a Flutter
add-to-app module at a view level for Android.
* [`android_splash_screen`]
* [`ios_app_clip`]
* [`platform_channels`] - A sample app which demonstrates how to use MethodChannel, EventChannel, BasicMessageChannel and MessageCodec in Flutter.
* [`platform_view_swift`] - A Flutter sample app that combines a native iOS UIViewController with a full-screen Flutter view.
### Demo galleries
* [`animations`] - Showcases Flutter's animation features
* [`material_3_demo`] - showcases [Material 3] features in the Flutter Material library.
### Demo apps
* [`compass_app`] - A sample application that implements MVVM architecture.
* [`deeplink_store_example`] - A demo app that implements deep-linking with go_router.
* [`veggie_seasons`] - A demo application.
## Flutter sample code
Samples are **correct and concise code** that developers
can **quickly understand** and **easily reuse** with minimal side effects.
Samples teach developers how to be successful using Flutter and Dart.
They are maintained on an ongoing basis
to reflect changing APIs and best practices.
### Types of samples
There are two types of sample code in this repository:
* **Quickstarts** provide a starting point to extend. They answer the question,
"What is the minimal amount of code needed to implement this feature?"
* **Demo apps** are meant to be built and ran. They demo the _product_,
not how to write code.
A majority of samples in this repository are quickstarts.
## Usage
Every sample in this repo is fully runnable. To run an example,
use `flutter run` inside that example's directory.
See the [getting started guide] to install the `flutter` tool.
> [!IMPORTANT]
> If you want to run an add-to-app sample, there are additional requirements.
> We suggest reading the [add-to-app documentation].
### Tip: minimize download size
As this repository is quite big, you can use
[svn] to download a single example.
For example:
```
svn co https://github.com/flutter/samples/trunk/provider_shopper
```
You can also use a
[partial clone](https://github.blog/2020-12-21-get-up-to-speed-with-partial-clone-and-shallow-clone/)
to skip blob objects that aren't currently checked out,
while including the full commit history:
You can also use a [partial clone] to skip blob objects
that aren't currently checked out, while including the full commit history:
```
git clone --filter=blob:none https://github.com/flutter/samples.git
@ -30,16 +116,76 @@ git clone --filter=blob:none https://github.com/flutter/samples.git
## Interested in contributing?
See the [contributor's guide](CONTRIBUTING.md)!
See the [contributor's guide]!
## Questions or issues?
If you have a general question about one of these samples or how to adapt its
techniques for one of your own apps, try one of these resources:
* [The FlutterDev Discord](https://discord.gg/rflutterdev)
* [StackOverflow](https://stackoverflow.com/questions/tagged/flutter)
* [The FlutterDev Discord]
* [The Flutter Community forum]
If you run into a bug in one of the samples, please file an issue in the
[`flutter/samples` issue tracker](https://github.com/flutter/samples/issues).
[`flutter/samples` issue tracker].
[`asset_transformation`]: ./asset_transformation
[`background_isolate_channels`]: ./background_isolate_channels
[`code_sharing`]: ./code_sharing
[`context_menus`]: ./context_menus
[`desktop_photo_search`]: ./desktop_photo_search
[`dynamic_theme`]: ./dynamic_theme
[`form_app`]: ./form_app
[`game_template`]: ./game_template
[`gemini_tasks`]: ./gemini_tasks
[`google_maps`]: ./google_maps
[`infinite_list`]: ./infinite_list
[`isolate_example`]: ./isolate_example
[`navigation_and_routing`]: ./navigation_and_routing
[`place_tracker`]: ./place_tracker
[`platform_design`]: ./platform_design
[`provider_counter`]: ./provider_counter
[`provider_shopper`]: ./provider_shopper
[`simple_shader`]: ./simple_shader
[`simplistic_calculator`]: ./simplistic_calculator
[`simplistic_editor`]: ./simplistic_editor
[`testing_app`]: ./testing_app
[`web_embedding`]: ./web_embedding
[`element_embedding_demo`]: ./web_embedding/element_embedding_demo
[`ng-flutter`]: ./web_embedding/ng-flutter
[`add-to-app`]: ./add_to_app
[`fullscreen`]: ./add_to_app/fullscreen
[`prebuilt_module`]: ./add_to_app/prebuilt_module
[`plugin`]: ./add_to_app/plugin
[`books`]: ./add_to_app/books
[`multiple_flutters`]: ./add_to_app/multiple_flutters
[`android_view`]: ./add_to_app/android_view
[`android_splash_screen`]: ./android_splash_screen
[`ios_app_clip`]: ./ios_app_clip
[`platform_channels`]: ./platform_channels
[`platform_view_swift`]: ./platform_view_swift
[`animations`]: ./animations
[`material_3_demo`]: ./material_3_demo
[`compass_app`]: ./compass_app
[`deeplink_store_example`]: ./deeplink_store_example
[`veggie_seasons`]: ./veggieseasons
[Flutter]: https://flutter.dev
[getting started guide]: https://docs.flutter.dev/get-started/install
[add-to-app documentation]: https://docs.flutter.dev/add-to-app
[isolates]: https://api.dart.dev/dart-isolate/Isolate-class.html
[Material 3]: https://m3.material.io
[go_router]: https://pub.dev/packages/go_router
[google_maps_flutter plugin]: https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter
[package:provider]: https://pub.dev/packages/provider
[Flutter fragment shaders]: https://docs.flutter.dev/development/ui/advanced/shaders
[TextEditingDeltas]: https://api.flutter.dev/flutter/services/TextEditingDelta-class.html
[Pigeon]: https://pub.dev/packages/pigeon
[`package:shelf`]: https://pub.dev/packages/shelf
[svn]: https://subversion.apache.org/
[partial clone]: https://github.blog/2020-12-21-get-up-to-speed-with-partial-clone-and-shallow-clone/
[contributing guide]: CONTRIBUTING.md
[The FlutterDev Discord]: https://discord.gg/rflutterdev
[The Flutter Community forum]: https://forum.itsallwidgets.com/latest
[`flutter/samples` issue tracker]: https://github.com/flutter/samples/issues

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

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

@ -1,17 +1,16 @@
This is a demo app created for the Google I/O talk "Gemini API and Flutter: Practical, AI-driven apps with Google AI tools"
# Sample retired
Check out the [the prompt we used on Google AI Studio](https://aistudio.google.com/app/prompts/1RsTAt6_N8BPhXbrrd8gSJcdeuYV834kf).
The `ai_recipe_generation` sample has been retired.
## Running the app
The sample was written for an event (Google IO 2024). Per our policies,
we don't maintain samples that are tied to events.
Before running the app:
* Get an API key from [ai.google.dev](ai.google.dev).
* Create a Firebase project, and install the Flutter Firebase CLI, and configure the platforms that you want to run this app on. You can find instructions by following [steps 2-4 of the Get to know Firebase for Flutter](https://firebase.google.com/codelabs/firebase-get-to-know-flutter?hl=en#2) codelab.
This sample has been copied to [another repository](https://github.com/ericwindmill/gemini_recipe_generation). It will not be updated to reflect changes to Gemini.
Then, pass the API key in with dart define when running the app:
```bash
flutter run --dart-define=API_KEY=your_api_key
```
## Other Gemini resources
The following resources can assist you in integrating Gemini with Flutter:
* [gemini_tasks sample](https://github.com/flutter/samples/tree/main/gemini_tasks) is a Gemini sample in this repository.
* [google_generative_ai package](https://pub.dev/packages/google_generative_ai) is the package that Flutter apps use to integrate with gemini, and it has additional samples.
* The [google-gemini github](https://github.com/google-gemini) has additional Flutter samples.

@ -1 +0,0 @@
include: package:analysis_defaults/flutter.yaml

@ -1,13 +0,0 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
**/*.keystore
**/*.jks

@ -1,67 +0,0 @@
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
android {
namespace "com.example.gemini_io_talk"
compileSdkVersion flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.gemini_io_talk"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {}

@ -1,29 +0,0 @@
{
"project_info": {
"project_number": "44885228795",
"project_id": "gemini-cat-chef",
"storage_bucket": "gemini-cat-chef.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:44885228795:android:d1ed69a5c617a8b98f845e",
"android_client_info": {
"package_name": "com.example.gemini_io_talk"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyANxBBzc4s-Yuol0xqs-mEtXe-pNcut3OU"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}

@ -1,7 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

@ -1,33 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="gemini_io_talk"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>

@ -1,6 +0,0 @@
package com.example.gemini_io_talk
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

@ -1,7 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

@ -1,30 +0,0 @@
buildscript {
ext.kotlin_version = '1.7.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
tasks.register("clean", Delete) {
delete rootProject.buildDir
}

@ -1,3 +0,0 @@
org.gradle.jvmargs=-Xmx4G
android.useAndroidX=true
android.enableJetifier=true

@ -1,5 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip

@ -1,29 +0,0 @@
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}
settings.ext.flutterSdkPath = flutterSdkPath()
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
plugins {
id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
}
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "7.3.0" apply false
}
include ":app"

@ -1,43 +0,0 @@
<svg width="1148" height="1148" viewBox="0 0 1148 1148" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_102_68" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="1148" height="1148">
<circle cx="574" cy="574" r="574" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_102_68)">
<circle cx="574" cy="574" r="566.5" fill="#FFE5ED" stroke="black" stroke-width="15"/>
<path d="M981.119 769.349C981.119 853.471 936.262 930.167 862.677 986.078C789.087 1041.99 687.091 1076.8 574.12 1076.8C461.148 1076.8 359.152 1041.99 285.562 986.078C211.977 930.167 167.12 853.471 167.12 769.349C167.12 685.227 211.977 608.531 285.562 552.619C359.152 496.704 461.148 461.902 574.12 461.902C687.091 461.902 789.087 496.704 862.677 552.619C936.262 608.531 981.119 685.227 981.119 769.349Z" fill="white" stroke="white" stroke-width="15"/>
<path d="M572.309 1084.3C682.721 1084.3 788.611 1051.11 866.685 992.05C944.758 932.986 988.619 852.878 988.619 769.349C988.619 685.82 944.758 605.712 866.685 546.648C788.611 487.584 682.721 454.402 572.309 454.402V769.349L572.309 1084.3Z" fill="#E8947F"/>
<rect x="268.38" y="695.137" width="70.6828" height="26.9205" rx="13.4602" transform="rotate(20 268.38 695.137)" fill="#42241F"/>
<rect x="251.932" y="759.722" width="70.6828" height="26.9205" rx="13.4602" transform="rotate(-0.999999 251.932 759.722)" fill="#42241F"/>
<rect width="70.6828" height="26.9205" rx="13.4602" transform="matrix(-0.939693 0.34202 0.34202 0.939693 870.415 696.947)" fill="#42241F"/>
<rect x="258.767" y="828.934" width="70.6828" height="26.9205" rx="13.4602" transform="rotate(-18.5 258.767 828.934)" fill="#42241F"/>
<rect width="70.6828" height="26.9205" rx="13.4602" transform="matrix(-0.948324 -0.317305 -0.317305 0.948324 879.567 827.978)" fill="#42241F"/>
<rect width="70.6828" height="26.9205" rx="13.4602" transform="matrix(-0.999848 -0.0174524 -0.0174524 0.999848 885.997 761.532)" fill="#42241F"/>
<circle cx="430.221" cy="697.852" r="20.8155" fill="#42241F"/>
<circle cx="705.347" cy="697.852" r="20.8155" fill="#42241F"/>
<path d="M544.055 734.488C541.641 730.453 544.596 725.337 549.296 725.409L601.34 726.212C605.918 726.283 608.733 731.247 606.444 735.212L581.118 779.078C578.829 783.043 573.122 783.087 570.772 779.158L544.055 734.488Z" fill="#42241F"/>
<path d="M452.847 848.078C487.841 882.986 561.449 922.627 575.93 801.93" stroke="#42241F" stroke-width="12"/>
<circle cx="451.037" cy="847.181" r="10.8602" fill="#42241F"/>
<circle cx="575.93" cy="800.12" r="10.8602" fill="black"/>
<path d="M699.012 848.078C664.018 882.986 590.41 922.627 575.929 801.93" stroke="#42241F" stroke-width="12"/>
<circle cx="10.8602" cy="10.8602" r="10.8602" transform="matrix(-1 0 0 1 711.683 836.32)" fill="#42241F"/>
<circle cx="10.8602" cy="10.8602" r="10.8602" transform="matrix(-1 0 0 1 586.79 789.259)" fill="#42241F"/>
<path d="M506.972 467.616C506.972 515.6 491.979 554.498 473.486 554.498C454.992 554.498 440 515.6 440 467.616C473.486 449.516 454.992 458.566 473.486 458.566C491.979 458.566 483.441 449.516 506.972 467.616Z" fill="#B36652"/>
<path d="M604.714 467.616C604.714 536.592 588.101 592.508 567.608 592.508C547.115 592.508 530.502 536.592 530.502 467.616C555.843 422.365 567.608 476.666 567.608 454.946C588.101 454.946 582.993 442.276 604.714 467.616Z" fill="#B36652"/>
<path d="M695.216 467.616C695.216 515.6 680.224 554.498 661.73 554.498C643.236 554.498 628.244 515.6 628.244 467.616C661.73 467.616 643.236 467.617 661.73 467.617C680.224 467.617 671.685 467.616 695.216 467.616Z" fill="#B36652"/>
<path d="M981.119 769.349C981.119 853.471 936.262 930.167 862.677 986.078C789.087 1041.99 687.091 1076.8 574.12 1076.8C461.148 1076.8 359.152 1041.99 285.562 986.078C211.977 930.167 167.12 853.471 167.12 769.349C167.12 685.227 211.977 608.531 285.562 552.619C359.152 496.704 461.148 461.902 574.12 461.902C687.091 461.902 789.087 496.704 862.677 552.619C936.262 608.531 981.119 685.227 981.119 769.349Z" stroke="#42241F" stroke-width="15"/>
<path d="M739.532 467.783C836.158 368.524 870.135 460.513 874.814 530.618C875.447 540.108 866.273 546.899 857.756 542.666C853.048 540.326 848.797 537.784 844.862 535.197C832.618 527.149 729.311 478.282 739.532 467.783Z" fill="white" stroke="#B36652" stroke-width="10"/>
<path d="M868.541 576.469C888.69 482.475 867.777 321.948 712 481.307" stroke="#42241F" stroke-width="12"/>
<mask id="path-26-outside-1_102_68" maskUnits="userSpaceOnUse" x="206.046" y="-15.9623" width="604.267" height="500.807" fill="black">
<rect fill="white" x="206.046" y="-15.9623" width="604.267" height="500.807"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M600.447 66.8672C576.847 34.8128 537.281 15.0525 493.846 17.915C450.41 20.7774 413.78 45.5591 394.591 80.4332C379.542 75.3158 363.184 73.011 346.234 74.128C279.306 78.5386 228.511 134.634 232.781 199.421C236.696 258.832 285.556 304.921 345.25 308.76L355.312 461.443L689.387 439.427L679.325 286.744C737.998 275.105 780.388 223.002 776.473 163.592C772.203 98.8046 714.486 49.8599 647.558 54.2706C630.609 55.3875 614.694 59.8191 600.447 66.8672Z"/>
</mask>
<path fill-rule="evenodd" clip-rule="evenodd" d="M600.447 66.8672C576.847 34.8128 537.281 15.0525 493.846 17.915C450.41 20.7774 413.78 45.5591 394.591 80.4332C379.542 75.3158 363.184 73.011 346.234 74.128C279.306 78.5386 228.511 134.634 232.781 199.421C236.696 258.832 285.556 304.921 345.25 308.76L355.312 461.443L689.387 439.427L679.325 286.744C737.998 275.105 780.388 223.002 776.473 163.592C772.203 98.8046 714.486 49.8599 647.558 54.2706C630.609 55.3875 614.694 59.8191 600.447 66.8672Z" fill="white"/>
<path d="M600.447 66.8672L588.367 75.7604L595.824 85.8888L607.098 80.312L600.447 66.8672ZM394.591 80.4332L389.762 94.6346L401.67 98.6837L407.733 87.6643L394.591 80.4332ZM345.25 308.76L360.217 307.773L359.351 294.636L346.212 293.791L345.25 308.76ZM355.312 461.443L340.344 462.43L341.33 477.397L356.298 476.411L355.312 461.443ZM689.387 439.427L690.374 454.395L705.341 453.409L704.355 438.441L689.387 439.427ZM679.325 286.744L676.406 272.031L663.492 274.592L664.358 287.73L679.325 286.744ZM612.526 57.9739C585.95 21.8772 541.498 -0.25787 492.859 2.94743L494.832 32.8825C533.065 30.3629 567.744 47.7484 588.367 75.7604L612.526 57.9739ZM492.859 2.94743C444.221 6.15273 403.058 33.93 381.449 73.202L407.733 87.6643C424.502 57.1881 456.6 35.4021 494.832 32.8825L492.859 2.94743ZM399.42 66.2318C382.527 60.4873 364.193 57.9119 345.248 59.1604L347.221 89.0955C362.174 88.1101 376.557 90.1444 389.762 94.6346L399.42 66.2318ZM345.248 59.1604C270.516 64.0854 212.969 126.899 217.813 200.408L247.748 198.435C244.054 142.369 288.096 92.9919 347.221 89.0955L345.248 59.1604ZM217.813 200.408C222.255 267.803 277.555 319.437 344.287 323.729L346.212 293.791C293.558 290.404 251.137 249.861 247.748 198.435L217.813 200.408ZM330.282 309.746L340.344 462.43L370.279 460.457L360.217 307.773L330.282 309.746ZM356.298 476.411L690.374 454.395L688.401 424.46L354.325 446.476L356.298 476.411ZM704.355 438.441L694.293 285.757L664.358 287.73L674.42 440.414L704.355 438.441ZM761.505 164.578C764.894 216.004 728.161 261.764 676.406 272.031L682.244 301.457C747.835 288.445 795.882 230.001 791.44 162.605L761.505 164.578ZM648.544 69.2381C707.668 65.3417 757.81 108.512 761.505 164.578L791.44 162.605C786.596 89.0969 721.303 34.3781 646.571 39.303L648.544 69.2381ZM607.098 80.312C619.599 74.1278 633.591 70.2235 648.544 69.2381L646.571 39.303C627.626 40.5515 609.789 45.5105 593.796 53.4223L607.098 80.312Z" fill="black" mask="url(#path-26-outside-1_102_68)"/>
<path d="M353.661 436.403L346.178 436.897L346.671 444.38L348.734 475.685C349.388 485.615 358.094 492.821 367.822 492.179L681.018 471.54C690.747 470.898 698.431 462.613 697.777 452.682L695.714 421.378L695.221 413.894L687.737 414.388L353.661 436.403Z" fill="#A2E3F6" stroke="black" stroke-width="15"/>
<path d="M426.064 146.542C413.396 147.376 403.695 158.263 404.533 170.976L409.231 242.257C410.069 254.97 421.114 264.489 433.782 263.654C446.45 262.819 456.151 251.933 455.313 239.22L450.616 167.939C449.778 155.226 438.732 145.707 426.064 146.542Z" fill="white" stroke="black" stroke-width="15"/>
<path d="M502.188 144.496C489.435 145.337 478.977 155.916 479.838 168.985L484.535 240.266C485.396 253.334 497.153 262.449 509.905 261.609C522.658 260.769 533.117 250.189 532.255 237.121L527.558 165.84C526.697 152.771 514.94 143.656 502.188 144.496Z" fill="white" stroke="black" stroke-width="15"/>
<path d="M573.064 142.542C560.396 143.376 550.695 154.263 551.533 166.976L556.231 238.257C557.069 250.97 568.114 260.489 580.782 259.654C593.45 258.819 603.151 247.933 602.313 235.22L597.616 163.939C596.778 151.226 585.732 141.707 573.064 142.542Z" fill="white" stroke="black" stroke-width="15"/>
<path d="M404.273 485.38C313.484 380.755 274.3 470.649 265.618 540.372C264.442 549.81 273.213 557.115 281.958 553.376C286.792 551.309 291.182 549.015 295.259 546.657C307.942 539.323 413.876 496.446 404.273 485.38Z" fill="#E4BFB5" stroke="#B36652" stroke-width="12"/>
<path d="M262.295 582.856C247.557 487.863 277.621 328.795 424.025 496.806" stroke="#42241F" stroke-width="12"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

@ -1,11 +0,0 @@
<svg width="1148" height="1148" viewBox="0 0 1148 1148" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_106_3" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="1148" height="1148">
<circle cx="574" cy="574" r="574" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_106_3)">
<circle cx="574" cy="574" r="566.5" fill="#E4BFB5" stroke="black" stroke-width="15"/>
<path d="M996 986C996 1277.61 807.064 1514 574 1514C340.936 1514 152 1277.61 152 986C152 694.394 340.936 574 574 574C807.064 574 996 694.394 996 986Z" fill="#DE7A60"/>
<ellipse cx="574" cy="367" rx="264" ry="276" fill="#DE7A60"/>
<circle cx="574" cy="574" r="566.5" stroke="black" stroke-width="15"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 705 B

@ -1,34 +0,0 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
</dict>
</plist>

@ -1,2 +0,0 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"

@ -1,2 +0,0 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

@ -1,44 +0,0 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '12.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end

@ -1,728 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
6CEAC1BB3402D2FB3D949175 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3AEDF9CC1F2478538C36F4EC /* GoogleService-Info.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
D176046199A01D7761A9B663 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 55AAE8E90222A83A2A3C5725 /* Pods_RunnerTests.framework */; };
EC8FDDE32B650F9294E84E49 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 560968E9DE345774409B17C1 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
26885269168D3B8FD1AC5E99 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3AEDF9CC1F2478538C36F4EC /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
55AAE8E90222A83A2A3C5725 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
560968E9DE345774409B17C1 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
6E452F04B76B6311A2D269D2 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
8AC0061116C886C86A9B5490 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
8C41D0F2AC811E3A0FF6FC41 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
CE028EB35B4B427E70F8476C /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
D397ABCFD4CECC2D1C87B0F5 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
3C97DB56F9108CED1F143C54 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D176046199A01D7761A9B663 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
EC8FDDE32B650F9294E84E49 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
4DC0F3FBD4704312B8DDA602 /* Pods */ = {
isa = PBXGroup;
children = (
D397ABCFD4CECC2D1C87B0F5 /* Pods-Runner.debug.xcconfig */,
8AC0061116C886C86A9B5490 /* Pods-Runner.release.xcconfig */,
8C41D0F2AC811E3A0FF6FC41 /* Pods-Runner.profile.xcconfig */,
26885269168D3B8FD1AC5E99 /* Pods-RunnerTests.debug.xcconfig */,
6E452F04B76B6311A2D269D2 /* Pods-RunnerTests.release.xcconfig */,
CE028EB35B4B427E70F8476C /* Pods-RunnerTests.profile.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
774D05E8B814CE61D1EBF4C8 /* Frameworks */ = {
isa = PBXGroup;
children = (
560968E9DE345774409B17C1 /* Pods_Runner.framework */,
55AAE8E90222A83A2A3C5725 /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
4DC0F3FBD4704312B8DDA602 /* Pods */,
774D05E8B814CE61D1EBF4C8 /* Frameworks */,
3AEDF9CC1F2478538C36F4EC /* GoogleService-Info.plist */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
825D4F9DB9CE5CD128B67AF7 /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
3C97DB56F9108CED1F143C54 /* Frameworks */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9B274ACE53869D8B50572937 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
04F3356025EB4C21B4C3E754 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
6CEAC1BB3402D2FB3D949175 /* GoogleService-Info.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
04F3356025EB4C21B4C3E754 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
825D4F9DB9CE5CD128B67AF7 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
9B274ACE53869D8B50572937 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 2UUT9AMTS2;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.geminiIoTalk;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 26885269168D3B8FD1AC5E99 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.geminiIoTalk.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 6E452F04B76B6311A2D269D2 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.geminiIoTalk.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = CE028EB35B4B427E70F8476C /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.geminiIoTalk.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 2UUT9AMTS2;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.geminiIoTalk;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 2UUT9AMTS2;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.geminiIoTalk;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

@ -1,98 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

@ -1,13 +0,0 @@
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

@ -1,122 +0,0 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

@ -1,23 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

@ -1,5 +0,0 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>API_KEY</key>
<string>AIzaSyBNEtAHdjm3oFV6JZ6nbx5t6Pfyj4w4hbo</string>
<key>GCM_SENDER_ID</key>
<string>44885228795</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>com.example.geminiIoTalk</string>
<key>PROJECT_ID</key>
<string>gemini-cat-chef</string>
<key>STORAGE_BUCKET</key>
<string>gemini-cat-chef.appspot.com</string>
<key>IS_ADS_ENABLED</key>
<false></false>
<key>IS_ANALYTICS_ENABLED</key>
<false></false>
<key>IS_APPINVITE_ENABLED</key>
<true></true>
<key>IS_GCM_ENABLED</key>
<true></true>
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>1:44885228795:ios:8ef52eb95f012a148f845e</string>
</dict>
</plist>

@ -1,59 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Gemini Io Talk</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>gemini_io_talk</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>Used to demonstrate image picker plugin</string>
<key>NSMicrophoneUsageDescription</key>
<string>Used to capture audio for image picker plugin</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Used to demonstrate image picker plugin</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>your usage description here</string>
<key>NSMicrophoneUsageDescription</key>
<string>your usage description here</string>
</dict>
</plist>

@ -1 +0,0 @@
#import "GeneratedPluginRegistrant.h"

@ -1,12 +0,0 @@
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}

@ -1,7 +0,0 @@
{
"file_generated_by": "FlutterFire CLI",
"purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory",
"GOOGLE_APP_ID": "1:44885228795:ios:8ef52eb95f012a148f845e",
"FIREBASE_PROJECT_ID": "gemini-cat-chef",
"GCM_SENDER_ID": "44885228795"
}

@ -1,160 +0,0 @@
import 'package:ai_recipe_generation/util/extensions.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'features/prompt/widgets/app_info_dialog_widget.dart';
import 'theme.dart';
import 'widgets/appbar_shape_border.dart';
class AnimatedAppBar extends StatelessWidget {
const AnimatedAppBar({
super.key,
required this.scrollController,
required this.textStyle,
required this.tabController,
});
final ScrollController scrollController;
final double collapsedHeight = 100;
final double expandedHeight = 300;
final double avatarSize = 50;
final TextStyle textStyle;
final TabController tabController;
String get headerText {
return switch (tabController.index) {
0 => 'Create a recipe',
1 => 'Saved recipes',
2 => 'Settings',
_ => 'Uh oh!',
};
}
String get helperText {
return switch (tabController.index) {
0 =>
"Tell me what ingredients you have and what you're feelin', and I'll create a recipe for you!",
1 => "These are all my saved recipes created by Chef Noodle.",
2 => 'Settings',
_ => 'Uh oh!',
};
}
@override
Widget build(BuildContext context) {
return SliverLayoutBuilder(
builder: (context, constraints) {
return SliverAppBar(
automaticallyImplyLeading: false,
pinned: true,
forceElevated: true,
elevation: 2,
shadowColor: Colors.black,
expandedHeight: expandedHeight,
collapsedHeight: collapsedHeight,
backgroundColor: Theme.of(context).primaryColor,
shape: const AppBarShapeBorder(50),
title: Column(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: avatarSize,
height: avatarSize,
child: SvgPicture.asset(
'assets/chef_cat.svg',
semanticsLabel: 'Chef cat icon',
),
),
const SizedBox(
width: MarketplaceTheme.spacing1,
),
if (scrollController.positions.isNotEmpty &&
scrollController.offset < 200)
Text(
"Meowdy! Let's get cooking!",
style: MarketplaceTheme.heading3,
),
if (scrollController.positions.isNotEmpty &&
scrollController.offset > 200)
Text(
headerText,
style: MarketplaceTheme.heading3,
),
const Spacer(),
if (scrollController.positions.isNotEmpty &&
scrollController.offset > 200)
IconButton(
onPressed: () => showDialog<Null>(
context: context,
builder: (context) => const AppInfoDialog(),
),
icon: const Icon(
Symbols.info,
color: Colors.black12,
),
),
],
),
],
),
flexibleSpace: FlexibleSpaceBar(
background: Padding(
padding: const EdgeInsets.all(MarketplaceTheme.spacing4),
child: SizedBox(
width: MediaQuery.of(context).size.width,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Expanded(
child: Text(
helperText,
style: constraints.isMobile
? MarketplaceTheme.subheading2
: MarketplaceTheme.subheading1,
),
),
IconButton(
onPressed: () {
showDialog<Null>(
context: context,
builder: (context) => const AppInfoDialog(),
);
},
icon: const Icon(
Symbols.info,
color: Colors.black12,
),
),
],
),
),
),
),
bottom: PreferredSize(
preferredSize: const Size(double.infinity, 0),
child: Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: EdgeInsets.only(
left: constraints.isMobile
? MarketplaceTheme.spacing2
: MarketplaceTheme.spacing1,
),
child: AnimatedDefaultTextStyle(
duration: const Duration(milliseconds: 0),
style: textStyle,
child: Text(
headerText,
),
),
),
),
),
);
},
);
}
}

@ -1,66 +0,0 @@
import 'package:image_picker/image_picker.dart';
import '../../util/filter_chip_enums.dart';
class PromptData {
PromptData({
required this.images,
required this.textInput,
Set<BasicIngredientsFilter>? basicIngredients,
Set<CuisineFilter>? cuisines,
Set<DietaryRestrictionsFilter>? dietaryRestrictions,
List<String>? additionalTextInputs,
}) : additionalTextInputs = additionalTextInputs ?? [],
selectedBasicIngredients = basicIngredients ?? {},
selectedCuisines = cuisines ?? {},
selectedDietaryRestrictions = dietaryRestrictions ?? {};
PromptData.empty()
: images = [],
additionalTextInputs = [],
selectedBasicIngredients = {},
selectedCuisines = {},
selectedDietaryRestrictions = {},
textInput = '';
String get cuisines {
return selectedCuisines.map((catFilter) => catFilter.name).join(",");
}
String get ingredients {
return selectedBasicIngredients
.map((ingredient) => ingredient.name)
.join(", ");
}
String get dietaryRestrictions {
return selectedDietaryRestrictions
.map((restriction) => restriction.name)
.join(", ");
}
List<XFile> images;
String textInput;
List<String> additionalTextInputs;
Set<BasicIngredientsFilter> selectedBasicIngredients;
Set<CuisineFilter> selectedCuisines;
Set<DietaryRestrictionsFilter> selectedDietaryRestrictions;
PromptData copyWith({
List<XFile>? images,
String? textInput,
List<String>? additionalTextInputs,
Set<BasicIngredientsFilter>? basicIngredients,
Set<CuisineFilter>? cuisineSelections,
Set<DietaryRestrictionsFilter>? dietaryRestrictions,
}) {
return PromptData(
images: images ?? this.images,
textInput: textInput ?? this.textInput,
additionalTextInputs: additionalTextInputs ?? this.additionalTextInputs,
basicIngredients: basicIngredients ?? selectedBasicIngredients,
cuisines: cuisineSelections ?? selectedCuisines,
dietaryRestrictions: dietaryRestrictions ?? selectedDietaryRestrictions,
);
}
}

@ -1,391 +0,0 @@
import 'package:ai_recipe_generation/features/prompt/prompt_view_model.dart';
import 'package:ai_recipe_generation/util/extensions.dart';
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import 'package:provider/provider.dart';
import '../../theme.dart';
import '../../util/filter_chip_enums.dart';
import '../../widgets/filter_chip_selection_input.dart';
import '../../widgets/highlight_border_on_hover_widget.dart';
import '../../widgets/marketplace_button_widget.dart';
import '../recipes/widgets/recipe_fullscreen_dialog.dart';
import 'widgets/full_prompt_dialog_widget.dart';
import 'widgets/image_input_widget.dart';
const double kAvatarSize = 50;
const double collapsedHeight = 100;
const double expandedHeight = 300;
const double elementPadding = MarketplaceTheme.spacing7;
class PromptScreen extends StatelessWidget {
const PromptScreen({super.key, required this.canScroll});
final bool canScroll;
@override
Widget build(BuildContext context) {
final viewModel = context.watch<PromptViewModel>();
return LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
physics: canScroll
? const BouncingScrollPhysics()
: const NeverScrollableScrollPhysics(),
child: Container(
padding: constraints.isMobile
? const EdgeInsets.only(
left: MarketplaceTheme.spacing7,
right: MarketplaceTheme.spacing7,
bottom: MarketplaceTheme.spacing7,
top: MarketplaceTheme.spacing7,
)
: const EdgeInsets.only(
left: MarketplaceTheme.spacing7,
right: MarketplaceTheme.spacing7,
bottom: MarketplaceTheme.spacing1,
top: MarketplaceTheme.spacing7,
),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: MarketplaceTheme.borderColor),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(4),
topRight: Radius.circular(50),
bottomRight:
Radius.circular(MarketplaceTheme.defaultBorderRadius),
bottomLeft:
Radius.circular(MarketplaceTheme.defaultBorderRadius),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(elementPadding + 10),
child: Text(
'Create a recipe:',
style: MarketplaceTheme.dossierParagraph.copyWith(
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
),
Padding(
padding: const EdgeInsets.all(
elementPadding,
),
child: SizedBox(
height: constraints.isMobile ? 130 : 230,
child: AddImageToPromptWidget(
height: constraints.isMobile ? 100 : 200,
width: constraints.isMobile ? 100 : 200,
),
),
),
if (constraints.isMobile)
Padding(
padding: const EdgeInsets.all(elementPadding),
child: _FilterChipSection(
label: "I also have these staple ingredients: ",
child: FilterChipSelectionInput<BasicIngredientsFilter>(
onChipSelected: (selected) {
viewModel.addBasicIngredients(
selected as Set<BasicIngredientsFilter>);
},
allValues: BasicIngredientsFilter.values,
selectedValues:
viewModel.userPrompt.selectedBasicIngredients,
),
),
),
if (constraints.isMobile)
Padding(
padding: const EdgeInsets.all(elementPadding),
child: _FilterChipSection(
label: "I'm in the mood for: ",
child: FilterChipSelectionInput<CuisineFilter>(
onChipSelected: (selected) {
viewModel.addCategoryFilters(
selected as Set<CuisineFilter>);
},
allValues: CuisineFilter.values,
selectedValues: viewModel.userPrompt.selectedCuisines,
),
),
),
if (constraints.isMobile)
Padding(
padding: const EdgeInsets.all(elementPadding),
child: _FilterChipSection(
label: "I have the following dietary restrictions:",
child:
FilterChipSelectionInput<DietaryRestrictionsFilter>(
onChipSelected: (selected) {
viewModel.addDietaryRestrictionFilter(
selected as Set<DietaryRestrictionsFilter>);
},
allValues: DietaryRestrictionsFilter.values,
selectedValues:
viewModel.userPrompt.selectedDietaryRestrictions,
),
),
),
if (!constraints.isMobile)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(elementPadding),
child: _FilterChipSection(
label: "I'm in the mood for: ",
child: FilterChipSelectionInput<CuisineFilter>(
onChipSelected: (selected) {
viewModel.addCategoryFilters(
selected as Set<CuisineFilter>);
},
allValues: CuisineFilter.values,
selectedValues:
viewModel.userPrompt.selectedCuisines,
),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(elementPadding),
child: _FilterChipSection(
label: "I also have these staple ingredients: ",
child: FilterChipSelectionInput<
BasicIngredientsFilter>(
onChipSelected: (selected) {
viewModel.addBasicIngredients(
selected as Set<BasicIngredientsFilter>);
},
allValues: BasicIngredientsFilter.values,
selectedValues: viewModel
.userPrompt.selectedBasicIngredients,
),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(elementPadding),
child: _FilterChipSection(
label:
"I have the following dietary restrictions:",
child: FilterChipSelectionInput<
DietaryRestrictionsFilter>(
onChipSelected: (selected) {
viewModel.addDietaryRestrictionFilter(selected
as Set<DietaryRestrictionsFilter>);
},
allValues: DietaryRestrictionsFilter.values,
selectedValues: viewModel
.userPrompt.selectedDietaryRestrictions,
),
),
),
),
],
),
Padding(
padding: const EdgeInsets.all(elementPadding),
child: _TextField(
controller: viewModel.promptTextController,
onChanged: (value) {
viewModel.notify();
},
),
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: MarketplaceTheme.spacing4,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (!constraints.isMobile) const Spacer(flex: 1),
if (!constraints.isMobile)
Expanded(
flex: 3,
child: MarketplaceButton(
onPressed: viewModel.resetPrompt,
buttonText: 'Reset prompt',
icon: Symbols.restart_alt,
iconColor: Colors.black45,
buttonBackgroundColor: Colors.transparent,
hoverColor:
MarketplaceTheme.secondary.withAlpha(25),
),
),
const Spacer(flex: 1),
Expanded(
flex: constraints.isMobile ? 10 : 3,
child: MarketplaceButton(
onPressed: () {
final promptData = viewModel.buildPrompt();
showDialog<Null>(
context: context,
builder: (context) {
return FullPromptDialog(
promptData: promptData,
);
},
);
},
buttonText: 'Full prompt',
icon: Symbols.info_rounded,
),
),
const Spacer(flex: 1),
Expanded(
flex: constraints.isMobile ? 10 : 3,
child: MarketplaceButton(
onPressed: () async {
await viewModel.submitPrompt().then((_) async {
if (!context.mounted) return;
if (viewModel.recipe != null) {
bool? shouldSave = await showDialog<bool>(
context: context,
barrierDismissible: false,
builder: (context) => RecipeDialogScreen(
recipe: viewModel.recipe!,
actions: [
MarketplaceButton(
onPressed: () {
Navigator.of(context).pop(true);
},
buttonText: "Save Recipe",
icon: Symbols.save,
),
],
),
);
if (shouldSave != null && shouldSave) {
viewModel.saveRecipe();
}
}
});
},
buttonText: 'Submit prompt',
icon: Symbols.send,
),
),
const Spacer(flex: 1),
],
),
),
if (constraints.isMobile)
Align(
alignment: Alignment.center,
child: MarketplaceButton(
onPressed: viewModel.resetPrompt,
buttonText: 'Reset prompt',
icon: Symbols.restart_alt,
iconColor: Colors.black45,
buttonBackgroundColor: Colors.transparent,
hoverColor: MarketplaceTheme.secondary.withAlpha(25),
),
),
const SizedBox(height: 200.0),
],
),
),
),
);
},
);
}
}
class _FilterChipSection extends StatelessWidget {
const _FilterChipSection({
required this.child,
required this.label,
});
final Widget child;
final String label;
@override
Widget build(BuildContext context) {
return HighlightBorderOnHoverWidget(
borderRadius: BorderRadius.zero,
child: Container(
height: 230,
decoration: BoxDecoration(
color: Theme.of(context).splashColor.withAlpha(25),
border: Border.all(
color: MarketplaceTheme.borderColor,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.max,
children: [
Padding(
padding: const EdgeInsets.all(MarketplaceTheme.spacing7),
child: Text(
label,
style: MarketplaceTheme.dossierParagraph,
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: child,
),
),
],
),
),
);
}
}
class _TextField extends StatelessWidget {
const _TextField({
required this.controller,
this.onChanged,
});
final TextEditingController controller;
final Null Function(String)? onChanged;
@override
Widget build(BuildContext context) {
return TextField(
scrollPadding: const EdgeInsets.only(bottom: 150),
maxLines: null,
onChanged: onChanged,
minLines: 3,
controller: controller,
style: WidgetStateTextStyle.resolveWith(
(states) => MarketplaceTheme.dossierParagraph),
decoration: InputDecoration(
fillColor: Theme.of(context).splashColor,
hintText: "Add additional context...",
hintStyle: WidgetStateTextStyle.resolveWith(
(states) => MarketplaceTheme.dossierParagraph,
),
enabledBorder: const OutlineInputBorder(
borderRadius: BorderRadius.zero,
borderSide: BorderSide(width: 1, color: Colors.black12),
),
focusedBorder: const OutlineInputBorder(
borderRadius: BorderRadius.zero,
borderSide: BorderSide(width: 1, color: Colors.black45),
),
filled: true,
),
);
}
}

@ -1,168 +0,0 @@
import 'package:ai_recipe_generation/services/gemini.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:google_generative_ai/google_generative_ai.dart';
import 'package:image_picker/image_picker.dart';
import '../../services/firestore.dart';
import '../../util/filter_chip_enums.dart';
import '../recipes/recipe_model.dart';
import 'prompt_model.dart';
class PromptViewModel extends ChangeNotifier {
PromptViewModel({
required this.multiModalModel,
required this.textModel,
});
final GenerativeModel multiModalModel;
final GenerativeModel textModel;
bool loadingNewRecipe = false;
PromptData userPrompt = PromptData.empty();
TextEditingController promptTextController = TextEditingController();
String badImageFailure =
"The recipe request either does not contain images, or does not contain images of food items. I cannot recommend a recipe.";
Recipe? recipe;
String? _geminiFailureResponse;
String? get geminiFailureResponse => _geminiFailureResponse;
set geminiFailureResponse(String? value) {
_geminiFailureResponse = value;
notifyListeners();
}
void notify() => notifyListeners();
void addImage(XFile image) {
userPrompt.images.insert(0, image);
notifyListeners();
}
void addAdditionalPromptContext(String text) {
final existingInputs = userPrompt.additionalTextInputs;
userPrompt.copyWith(additionalTextInputs: [...existingInputs, text]);
}
void removeImage(XFile image) {
userPrompt.images.removeWhere((el) => el.path == image.path);
notifyListeners();
}
void resetPrompt() {
userPrompt = PromptData.empty();
notifyListeners();
}
// Creates an ephemeral prompt with additional text that the user shouldn't be
// concerned with to send to Gemini, such as formatting.
PromptData buildPrompt() {
return PromptData(
images: userPrompt.images,
textInput: mainPrompt,
basicIngredients: userPrompt.selectedBasicIngredients,
cuisines: userPrompt.selectedCuisines,
dietaryRestrictions: userPrompt.selectedDietaryRestrictions,
additionalTextInputs: [format],
);
}
Future<void> submitPrompt() async {
loadingNewRecipe = true;
notifyListeners();
// Create an ephemeral PromptData, preserving the user prompt data without
// adding the additional context to it.
var model = userPrompt.images.isEmpty ? textModel : multiModalModel;
final prompt = buildPrompt();
try {
final content = await GeminiService.generateContent(model, prompt);
// handle no image or image of not-food
if (content.text != null && content.text!.contains(badImageFailure)) {
geminiFailureResponse = badImageFailure;
} else {
recipe = Recipe.fromGeneratedContent(content);
}
} catch (error) {
geminiFailureResponse = 'Failed to reach Gemini. \n\n$error';
if (kDebugMode) {
print(error);
}
loadingNewRecipe = false;
}
loadingNewRecipe = false;
resetPrompt();
notifyListeners();
}
void saveRecipe() {
FirestoreService.saveRecipe(recipe!);
}
void addBasicIngredients(Set<BasicIngredientsFilter> ingredients) {
userPrompt.selectedBasicIngredients.addAll(ingredients);
notifyListeners();
}
void addCategoryFilters(Set<CuisineFilter> categories) {
userPrompt.selectedCuisines.addAll(categories);
notifyListeners();
}
void addDietaryRestrictionFilter(
Set<DietaryRestrictionsFilter> restrictions) {
userPrompt.selectedDietaryRestrictions.addAll(restrictions);
notifyListeners();
}
String get mainPrompt {
return '''
You are a Cat who's a chef that travels around the world a lot, and your travels inspire recipes.
Recommend a recipe for me based on the provided image.
The recipe should only contain real, edible ingredients.
If there are no images attached, or if the image does not contain food items, respond exactly with: $badImageFailure
Adhere to food safety and handling best practices like ensuring that poultry is fully cooked.
I'm in the mood for the following types of cuisine: ${userPrompt.cuisines},
I have the following dietary restrictions: ${userPrompt.dietaryRestrictions}
Optionally also include the following ingredients: ${userPrompt.ingredients}
Do not repeat any ingredients.
After providing the recipe, add an descriptions that creatively explains why the recipe is good based on only the ingredients used in the recipe. Tell a short story of a travel experience that inspired the recipe.
List out any ingredients that are potential allergens.
Provide a summary of how many people the recipe will serve and the the nutritional information per serving.
${promptTextController.text.isNotEmpty ? promptTextController.text : ''}
''';
}
final String format = '''
Return the recipe as valid JSON using the following structure:
{
"id": \$uniqueId,
"title": \$recipeTitle,
"ingredients": \$ingredients,
"description": \$description,
"instructions": \$instructions,
"cuisine": \$cuisineType,
"allergens": \$allergens,
"servings": \$servings,
"nutritionInformation": {
"calories": "\$calories",
"fat": "\$fat",
"carbohydrates": "\$carbohydrates",
"protein": "\$protein",
},
}
uniqueId should be unique and of type String.
title, description, cuisine, allergens, and servings should be of String type.
ingredients and instructions should be of type List<String>.
nutritionInformation should be of type Map<String, String>.
''';
}

@ -1,118 +0,0 @@
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';
import '../../../theme.dart';
class AppInfoDialog extends StatelessWidget {
const AppInfoDialog({super.key});
Widget bulletRow(String text, {IconData? icon}) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(icon ?? Symbols.label_important_outline),
const SizedBox(
width: 10,
),
Expanded(
child: Text(
text,
),
),
],
);
}
@override
Widget build(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
MarketplaceTheme.defaultBorderRadius,
),
),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: MarketplaceTheme.borderColor),
),
padding: const EdgeInsets.all(MarketplaceTheme.spacing4),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Use the form on this screen to ask Cat Chef to make a recipe for you.",
style: MarketplaceTheme.heading3,
),
const SizedBox(
height: MarketplaceTheme.spacing4,
),
bulletRow(
"Add images of ingredients you have, like a picture of the inside of your fridge or pantry.",
icon: Symbols.looks_one,
),
const SizedBox(
height: MarketplaceTheme.spacing7,
),
bulletRow(
"Choose what kind of food you're in the mood for, and what staple ingredients you have that might not be pictured.",
icon: Symbols.looks_two,
),
const SizedBox(
height: MarketplaceTheme.spacing7,
),
bulletRow(
"In the text box at the bottom, add any additional context that you'd like. \nFor example, you could say \"I'm in a hurry! Make sure the recipe doesn't take longer than 30 minutes to make.\"",
icon: Symbols.looks_3,
),
const SizedBox(
height: MarketplaceTheme.spacing7,
),
bulletRow(
"Submit the prompt, and Chef Noodle will give you a recipe!",
icon: Symbols.looks_4,
),
const SizedBox(
height: MarketplaceTheme.spacing4,
),
Text(
"Steps 1, 2 and 3 are optional. More information will provide better results.",
style: MarketplaceTheme.label,
),
const SizedBox(height: MarketplaceTheme.spacing4),
TextButton.icon(
icon: const Icon(
Symbols.close,
color: Colors.black87,
),
label: Text(
'Close',
style: MarketplaceTheme.dossierParagraph,
),
onPressed: () {
Navigator.pop(context);
},
style: ButtonStyle(
shape: WidgetStateProperty.resolveWith(
(states) {
return const RoundedRectangleBorder(
side: BorderSide(color: Colors.black26),
borderRadius: BorderRadius.all(
Radius.circular(MarketplaceTheme.defaultBorderRadius),
),
);
},
),
textStyle: WidgetStateTextStyle.resolveWith(
(states) {
return MarketplaceTheme.dossierParagraph
.copyWith(color: Colors.black45);
},
),
),
),
],
),
),
);
}
}

@ -1,112 +0,0 @@
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';
import '../../../theme.dart';
import '../../../widgets/prompt_image_widget.dart';
import '../prompt_model.dart';
class FullPromptDialog extends StatelessWidget {
const FullPromptDialog({super.key, required this.promptData});
final PromptData promptData;
Widget bulletRow(String text, {IconData? icon}) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(icon ?? Symbols.label_important_outline),
const SizedBox(
width: 10,
),
Expanded(
child: Text(
text,
),
),
],
);
}
@override
Widget build(BuildContext context) {
return Dialog.fullscreen(
child: SingleChildScrollView(
child: Container(
decoration: BoxDecoration(
border: Border.all(color: MarketplaceTheme.borderColor),
),
padding: const EdgeInsets.all(MarketplaceTheme.spacing4),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"This is the full prompt that will be sent to Google's Gemini model.",
style: MarketplaceTheme.heading3,
),
const SizedBox(height: MarketplaceTheme.spacing4),
if (promptData.images.isNotEmpty)
Container(
height: 100,
decoration: const BoxDecoration(
border: Border.symmetric(
horizontal: BorderSide(
color: MarketplaceTheme.borderColor,
),
),
),
child: ListView(
scrollDirection: Axis.horizontal,
children: [
for (var image in promptData.images)
Padding(
padding: const EdgeInsets.all(8.0),
child: PromptImage(
file: image,
),
),
],
),
),
const SizedBox(height: MarketplaceTheme.spacing4),
bulletRow(promptData.textInput),
if (promptData.additionalTextInputs.isNotEmpty)
...promptData.additionalTextInputs.map((i) => bulletRow(i)),
const SizedBox(height: MarketplaceTheme.spacing4),
TextButton.icon(
icon: const Icon(
Symbols.close,
color: Colors.black87,
),
label: Text(
'Close',
style: MarketplaceTheme.dossierParagraph,
),
onPressed: () {
Navigator.pop(context);
},
style: ButtonStyle(
shape: WidgetStateProperty.resolveWith(
(states) {
return const RoundedRectangleBorder(
side: BorderSide(color: Colors.black26),
borderRadius: BorderRadius.all(
Radius.circular(MarketplaceTheme.defaultBorderRadius),
),
);
},
),
textStyle: WidgetStateTextStyle.resolveWith(
(states) {
return MarketplaceTheme.dossierParagraph
.copyWith(color: Colors.black45);
},
),
),
),
],
),
),
),
);
}
}

@ -1,278 +0,0 @@
import 'package:ai_recipe_generation/widgets/highlight_border_on_hover_widget.dart';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart';
import '../../../main.dart';
import '../../../theme.dart';
import '../../../util/device_info.dart';
import '../../../widgets/add_image_widget.dart';
import '../../../widgets/prompt_image_widget.dart';
import '../prompt_view_model.dart';
class AddImageToPromptWidget extends StatefulWidget {
const AddImageToPromptWidget({
super.key,
this.width = 100,
this.height = 100,
});
final double width;
final double height;
@override
State<AddImageToPromptWidget> createState() => _AddImageToPromptWidgetState();
}
class _AddImageToPromptWidgetState extends State<AddImageToPromptWidget> {
final ImagePicker picker = ImagePicker();
late CameraController _controller;
late Future<void> _initializeControllerFuture;
bool flashOn = false;
@override
void initState() {
super.initState();
if (DeviceInfo.isPhysicalDeviceWithCamera(deviceInfo)) {
_controller = CameraController(
camera,
ResolutionPreset.medium,
);
_initializeControllerFuture = _controller.initialize();
}
}
Future<XFile> _showCamera() async {
final image = await showGeneralDialog<XFile?>(
context: context,
transitionBuilder: (context, animation, secondaryAnimation, child) {
return AnimatedOpacity(
opacity: animation.value,
duration: const Duration(milliseconds: 100),
child: child,
);
},
pageBuilder: (context, animation, secondaryAnimation) {
return Dialog.fullscreen(
insetAnimationDuration: const Duration(seconds: 1),
child: FutureBuilder(
future: _initializeControllerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// If the Future is complete, display the preview.
return CameraView(
controller: _controller,
initializeControllerFuture: _initializeControllerFuture,
);
} else {
// Otherwise, display a loading indicator.
return const Center(child: CircularProgressIndicator());
}
},
),
);
},
);
if (image != null) {
return image;
} else {
throw "failed to take image";
}
}
Future<XFile> _pickImage() async {
final image = await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
return image;
} else {
throw "failed to take image";
}
}
Future<XFile> _addImage() async {
if (DeviceInfo.isPhysicalDeviceWithCamera(deviceInfo)) {
return await _showCamera();
} else {
return await _pickImage();
}
}
@override
Widget build(BuildContext context) {
final viewModel = context.watch<PromptViewModel>();
return HighlightBorderOnHoverWidget(
borderRadius: BorderRadius.zero,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(
left: MarketplaceTheme.spacing7,
top: MarketplaceTheme.spacing7,
),
child: Text(
'I have these ingredients:',
style: MarketplaceTheme.dossierParagraph,
),
),
SizedBox(
height: widget.height,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
Padding(
padding: const EdgeInsets.all(MarketplaceTheme.spacing7),
child: AddImage(
width: widget.width,
height: widget.height,
onTap: () async {
final image = await _addImage();
viewModel.addImage(image);
}),
),
for (var image in viewModel.userPrompt.images)
Padding(
padding: const EdgeInsets.all(MarketplaceTheme.spacing7),
child: PromptImage(
width: widget.width,
file: image,
onTapIcon: () => viewModel.removeImage(image),
),
),
],
),
),
],
),
);
}
}
class CameraView extends StatefulWidget {
final CameraController controller;
final Future initializeControllerFuture;
const CameraView(
{super.key,
required this.controller,
required this.initializeControllerFuture});
@override
State<CameraView> createState() => _CameraViewState();
}
class _CameraViewState extends State<CameraView> {
bool flashOn = false;
@override
Widget build(BuildContext context) {
CameraController controller = widget.controller;
return Stack(
children: [
Center(
child: AspectRatio(
aspectRatio: 9 / 14,
child: ClipRect(
child: FittedBox(
fit: BoxFit.cover,
child: SizedBox(
height: controller.value.previewSize!.width,
width: controller.value.previewSize!.height,
child: Center(
child: CameraPreview(
controller,
// child: ElevatedButton(
// child: Text('Button'),
// onPressed: () {},
// ),
),
),
),
),
),
),
),
Positioned(
top: 0,
left: 0,
right: 0,
height: 89.5,
child: Container(
color: Colors.black.withAlpha(179),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding:
const EdgeInsets.only(left: MarketplaceTheme.spacing4),
child: IconButton(
icon: Icon(
flashOn ? Symbols.flash_on : Symbols.flash_off,
size: 40,
color: flashOn ? Colors.yellowAccent : Colors.white,
),
onPressed: () {
controller.setFlashMode(
flashOn ? FlashMode.off : FlashMode.always);
setState(() {
flashOn = !flashOn;
});
},
),
),
Padding(
padding:
const EdgeInsets.only(right: MarketplaceTheme.spacing4),
child: IconButton(
icon: const Icon(
Symbols.cancel,
color: Colors.white,
size: 40,
),
onPressed: () async {
Navigator.of(context).pop();
},
),
),
],
),
),
),
Positioned(
bottom: 0,
left: 0,
right: 0,
height: 150,
child: Container(
color: Colors.black.withAlpha(179),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: const Icon(
Symbols.camera,
color: Colors.white,
size: 70,
),
onPressed: () async {
try {
await widget.initializeControllerFuture;
final image = await controller.takePicture();
if (!context.mounted) return;
Navigator.of(context).pop(image);
} catch (e) {
rethrow;
}
},
),
],
),
),
)
],
);
}
}

@ -1,110 +0,0 @@
import 'dart:convert';
import 'package:ai_recipe_generation/util/json_parsing.dart';
import 'package:google_generative_ai/google_generative_ai.dart';
class Recipe {
Recipe({
required this.title,
required this.id,
required this.description,
required this.ingredients,
required this.instructions,
required this.cuisine,
required this.allergens,
required this.servings,
required this.nutritionInformation,
this.rating = -1,
});
final String id;
final String title;
final String description;
final List<String> ingredients;
final List<String> instructions;
final String cuisine;
final List<String> allergens;
final String servings;
final Map<String, dynamic> nutritionInformation;
int rating;
factory Recipe.fromGeneratedContent(GenerateContentResponse content) {
/// failures should be handled when the response is received
assert(content.text != null);
final validJson = cleanJson(content.text!);
final json = jsonDecode(validJson);
if (json
case {
"ingredients": List<dynamic> ingredients,
"instructions": List<dynamic> instructions,
"title": String title,
"id": String id,
"cuisine": String cuisine,
"description": String description,
"servings": String servings,
"nutritionInformation": Map<String, dynamic> nutritionInformation,
"allergens": List<dynamic> allergens,
}) {
return Recipe(
id: id,
title: title,
ingredients: ingredients.map((i) => i.toString()).toList(),
instructions: instructions.map((i) => i.toString()).toList(),
nutritionInformation: nutritionInformation,
allergens: allergens.map((i) => i.toString()).toList(),
cuisine: cuisine,
servings: servings,
description: description);
}
throw JsonUnsupportedObjectError(json);
}
Map<String, Object?> toFirestore() {
return {
'id': id,
'title': title,
'instructions': instructions,
'ingredients': ingredients,
'cuisine': cuisine,
'rating': rating,
'allergens': allergens,
'nutritionInformation': nutritionInformation,
'servings': servings,
'description': description,
};
}
factory Recipe.fromFirestore(Map<String, Object?> data) {
if (data
case {
"ingredients": List<dynamic> ingredients,
"instructions": List<dynamic> instructions,
"title": String title,
"id": String id,
"cuisine": String cuisine,
"description": String description,
"servings": String servings,
"nutritionInformation": Map<String, dynamic> nutritionInformation,
"allergens": List<dynamic> allergens,
"rating": int rating
}) {
return Recipe(
id: id,
title: title,
ingredients: ingredients.map((i) => i.toString()).toList(),
instructions: instructions.map((i) => i.toString()).toList(),
nutritionInformation: nutritionInformation,
allergens: allergens.map((i) => i.toString()).toList(),
cuisine: cuisine,
servings: servings,
description: description,
rating: rating,
);
}
throw "Malformed Firestore data";
}
}

@ -1,31 +0,0 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/cupertino.dart';
import '../../services/firestore.dart';
import 'recipe_model.dart';
class SavedRecipesViewModel extends ChangeNotifier {
List<Recipe> recipes = [];
final recipePath = '/recipes';
final firestore = FirebaseFirestore.instance;
SavedRecipesViewModel() {
firestore.collection(recipePath).snapshots().listen((querySnapshot) {
recipes = querySnapshot.docs.map((doc) {
final data = doc.data();
return Recipe.fromFirestore(data);
}).toList();
notifyListeners();
});
}
void deleteRecipe(Recipe recipe) {
FirestoreService.deleteRecipe(recipe);
}
void updateRecipe(Recipe recipe) {
FirestoreService.updateRecipe(recipe);
notifyListeners();
}
}

@ -1,263 +0,0 @@
import 'package:ai_recipe_generation/features/recipes/recipes_view_model.dart';
import 'package:ai_recipe_generation/features/recipes/widgets/recipe_fullscreen_dialog.dart';
import 'package:ai_recipe_generation/theme.dart';
import 'package:ai_recipe_generation/util/extensions.dart';
import 'package:ai_recipe_generation/widgets/highlight_border_on_hover_widget.dart';
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart';
import '../../widgets/marketplace_button_widget.dart';
import '../../widgets/star_rating.dart';
import 'recipe_model.dart';
class SavedRecipesScreen extends StatefulWidget {
const SavedRecipesScreen({super.key, required this.canScroll});
final bool canScroll;
@override
State<SavedRecipesScreen> createState() => _SavedRecipesScreenState();
}
class _SavedRecipesScreenState extends State<SavedRecipesScreen>
with TickerProviderStateMixin {
@override
Widget build(BuildContext context) {
final viewModel = context.watch<SavedRecipesViewModel>();
return LayoutBuilder(
builder: (context, constraints) {
return Padding(
padding: constraints.isMobile
? const EdgeInsets.only(
left: MarketplaceTheme.spacing7,
right: MarketplaceTheme.spacing7,
bottom: MarketplaceTheme.spacing7,
top: MarketplaceTheme.spacing7,
)
: const EdgeInsets.only(
left: MarketplaceTheme.spacing7,
right: MarketplaceTheme.spacing7,
bottom: MarketplaceTheme.spacing1,
top: MarketplaceTheme.spacing7,
),
child: ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(MarketplaceTheme.defaultBorderRadius),
topRight: Radius.circular(50),
bottomRight:
Radius.circular(MarketplaceTheme.defaultBorderRadius),
bottomLeft: Radius.circular(MarketplaceTheme.defaultBorderRadius),
),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: MarketplaceTheme.borderColor),
borderRadius: const BorderRadius.only(
topLeft:
Radius.circular(MarketplaceTheme.defaultBorderRadius),
topRight: Radius.circular(50),
bottomRight:
Radius.circular(MarketplaceTheme.defaultBorderRadius),
bottomLeft:
Radius.circular(MarketplaceTheme.defaultBorderRadius),
),
color: Colors.white,
),
child: constraints.isMobile
? ListView.builder(
physics: widget.canScroll
? const PageScrollPhysics()
: const NeverScrollableScrollPhysics(),
itemCount: viewModel.recipes.length,
itemBuilder: (context, idx) {
final recipe = viewModel.recipes[idx];
return Container(
margin: EdgeInsets.only(top: idx == 0 ? 70 : 0),
child: Align(
heightFactor: .5,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: MarketplaceTheme.spacing7,
vertical: MarketplaceTheme.spacing7,
),
child: SizedBox(
width: MediaQuery.of(context).size.width * .99,
height: 200,
child: _ListTile(
constraints: constraints,
key: Key('$idx-${recipe.hashCode}'),
recipe: recipe,
idx: idx,
),
),
),
),
);
},
)
: GridView.count(
physics: widget.canScroll
? const PageScrollPhysics()
: const NeverScrollableScrollPhysics(),
crossAxisCount: 3,
childAspectRatio: 1.5,
children: [
...List.generate(viewModel.recipes.length, (idx) {
final recipe = viewModel.recipes[idx];
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: MarketplaceTheme.spacing7,
vertical: MarketplaceTheme.spacing7,
),
child: _ListTile(
key: Key('$idx-${recipe.hashCode}'),
recipe: recipe,
idx: idx,
constraints: constraints,
),
);
}),
],
),
),
),
);
},
);
}
}
class _ListTile extends StatefulWidget {
const _ListTile({
super.key,
required this.recipe,
this.idx = 0,
required this.constraints,
});
final Recipe recipe;
final int idx;
final BoxConstraints constraints;
@override
State<_ListTile> createState() => _ListTileState();
}
class _ListTileState extends State<_ListTile> {
final List<Color> colors = [
MarketplaceTheme.primary,
MarketplaceTheme.secondary,
MarketplaceTheme.tertiary,
MarketplaceTheme.scrim,
];
@override
Widget build(BuildContext context) {
final viewModel = context.watch<SavedRecipesViewModel>();
final color = colors[widget.idx % colors.length];
return GestureDetector(
child: HighlightBorderOnHoverWidget(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(MarketplaceTheme.defaultBorderRadius),
topRight: Radius.circular(50),
bottomRight: Radius.circular(MarketplaceTheme.defaultBorderRadius),
bottomLeft: Radius.circular(MarketplaceTheme.defaultBorderRadius),
),
color: color,
child: Container(
decoration: const BoxDecoration(
boxShadow: [
BoxShadow(
offset: Offset(0, -2),
color: Colors.black38,
blurRadius: 5,
),
],
borderRadius: BorderRadius.only(
topLeft: Radius.circular(MarketplaceTheme.defaultBorderRadius),
topRight: Radius.circular(50),
bottomRight:
Radius.circular(MarketplaceTheme.defaultBorderRadius),
bottomLeft: Radius.circular(MarketplaceTheme.defaultBorderRadius),
),
color: Colors.white,
),
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(MarketplaceTheme.defaultBorderRadius),
topRight: Radius.circular(50),
bottomRight:
Radius.circular(MarketplaceTheme.defaultBorderRadius),
bottomLeft:
Radius.circular(MarketplaceTheme.defaultBorderRadius),
),
color: color.withAlpha(77),
),
padding: const EdgeInsets.all(MarketplaceTheme.spacing7),
child: Stack(
children: [
Text(
widget.recipe.title,
style: MarketplaceTheme.heading3,
),
Positioned(
top: widget.constraints.isMobile ? 40 : 60,
left: 0,
child: Text(
widget.recipe.cuisine,
style: MarketplaceTheme.subheading1,
),
),
Positioned(
right: 15,
top: widget.constraints.isMobile ? 40 : 60,
child: StartRating(
initialRating: widget.recipe.rating,
starColor: color,
onTap: null,
),
)
],
),
),
),
),
onTap: () async {
await showDialog<Null>(
context: context,
builder: (context) {
return RecipeDialogScreen(
recipe: widget.recipe,
subheading: Row(
children: [
const Text('My rating:'),
const SizedBox(width: 10),
StartRating(
initialRating: widget.recipe.rating,
starColor: MarketplaceTheme.tertiary,
onTap: (index) {
widget.recipe.rating = index + 1;
viewModel.updateRecipe(widget.recipe);
},
),
],
),
actions: [
MarketplaceButton(
onPressed: () {
viewModel.deleteRecipe(widget.recipe);
Navigator.of(context).pop();
},
buttonText: "Delete Recipe",
icon: Symbols.delete,
),
],
);
},
);
},
);
}
}

@ -1,277 +0,0 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:material_symbols_icons/symbols.dart';
import '../../../theme.dart';
import '../recipe_model.dart';
class RecipeDisplayWidget extends StatelessWidget {
const RecipeDisplayWidget({
super.key,
required this.recipe,
this.subheading,
});
final Recipe recipe;
final Widget? subheading;
List<Widget> _buildIngredients(List<String> ingredients) {
final widgets = <Widget>[];
for (var ingredient in ingredients) {
widgets.add(
Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Symbols.stat_0_rounded,
size: 12,
),
const SizedBox(
width: 5,
),
Expanded(
child: Text(
ingredient,
softWrap: true,
),
),
],
),
);
}
return widgets;
}
List<Widget> _buildInstructions(List<String> instructions) {
final widgets = <Widget>[];
// check for existing numbers in instructions.
if (instructions.first.startsWith(RegExp('[0-9]'))) {
for (var instruction in instructions) {
widgets.add(Text(instruction));
widgets.add(const SizedBox(height: MarketplaceTheme.spacing6));
}
} else {
for (var i = 0; i < instructions.length; i++) {
widgets.add(Text(
'${i + 1}. ${instructions[i]}',
softWrap: true,
));
widgets.add(const SizedBox(height: MarketplaceTheme.spacing6));
}
}
return widgets;
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
physics: const ClampingScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(MarketplaceTheme.defaultBorderRadius),
color: MarketplaceTheme.primary.withAlpha(128),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
recipe.title,
softWrap: true,
style: MarketplaceTheme.heading2,
),
if (subheading != null)
Padding(
padding: const EdgeInsets.symmetric(
vertical: MarketplaceTheme.spacing7,
),
child: subheading,
),
],
),
),
TextButton(
style: ButtonStyle(
backgroundColor: WidgetStateColor.resolveWith((states) {
if (states.contains(WidgetState.hovered)) {
return MarketplaceTheme.scrim.withAlpha(153);
}
return Colors.white;
}),
shape: WidgetStateProperty.resolveWith(
(states) {
return RoundedRectangleBorder(
side: const BorderSide(
color: MarketplaceTheme.primary),
borderRadius: BorderRadius.circular(
MarketplaceTheme.defaultBorderRadius,
),
);
},
),
textStyle: WidgetStateTextStyle.resolveWith(
(states) {
return MarketplaceTheme.dossierParagraph.copyWith(
color: Colors.black45,
);
},
),
),
onPressed: () async {
await showDialog<dynamic>(
context: context,
builder: (context) {
return AlertDialog(
content: Padding(
padding: const EdgeInsets.all(
MarketplaceTheme.spacing7),
child: Text(recipe.description),
),
);
},
);
},
child: Transform.translate(
offset: const Offset(0, 5),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: MarketplaceTheme.spacing6),
child: Row(
children: [
SizedBox(
width: 35,
height: 35,
child: SvgPicture.asset(
'assets/chef_cat.svg',
semanticsLabel: 'Chef cat icon',
),
),
Transform.translate(
offset: const Offset(1, -6),
child: Transform.rotate(
angle: -pi / 20.0,
child: Text(
'Chef Noodle \n says...',
style: MarketplaceTheme.label,
),
),
)
],
),
),
),
)
],
),
const Divider(
height: 40,
color: Colors.black26,
),
Table(
columnWidths: const {
0: FlexColumnWidth(2),
1: FlexColumnWidth(3),
},
children: [
TableRow(
children: [
Text(
'Allergens:',
style: MarketplaceTheme.paragraph.copyWith(
fontWeight: FontWeight.bold,
),
),
Text(recipe.allergens.join(', '))
],
),
TableRow(children: [
Text(
'Servings:',
style: MarketplaceTheme.paragraph.copyWith(
fontWeight: FontWeight.bold,
),
),
Text(recipe.servings)
]),
TableRow(children: [
Text(
'Nutrition per serving:',
style: MarketplaceTheme.paragraph.copyWith(
fontWeight: FontWeight.bold,
),
),
const Text(''),
]),
...recipe.nutritionInformation.entries.map((entry) {
return TableRow(children: [
Row(
children: [
const Icon(
Symbols.stat_0_rounded,
size: 12,
),
const SizedBox(
width: 5,
),
Expanded(
child: Text(
entry.key,
style: MarketplaceTheme.label,
softWrap: true,
),
),
],
),
Text(entry.value as String,
style: MarketplaceTheme.label)
]);
}),
],
),
],
),
),
/// Body section
Padding(
padding: const EdgeInsets.all(MarketplaceTheme.spacing4),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(
vertical: MarketplaceTheme.spacing7,
),
child:
Text('Ingredients:', style: MarketplaceTheme.subheading1),
),
..._buildIngredients(recipe.ingredients),
const SizedBox(height: MarketplaceTheme.spacing4),
Padding(
padding: const EdgeInsets.symmetric(
vertical: MarketplaceTheme.spacing7),
child: Text('Instructions:',
style: MarketplaceTheme.subheading1),
),
..._buildInstructions(recipe.instructions),
],
),
)
],
),
);
}
}

@ -1,56 +0,0 @@
import 'package:ai_recipe_generation/features/recipes/widgets/recipe_display_widget.dart';
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';
import '../../../theme.dart';
import '../../../widgets/marketplace_button_widget.dart';
import '../recipe_model.dart';
class RecipeDialogScreen extends StatelessWidget {
const RecipeDialogScreen({
super.key,
required this.recipe,
required this.actions,
this.subheading,
});
final Recipe recipe;
final List<Widget> actions;
final Widget? subheading;
@override
Widget build(BuildContext context) {
return Dialog.fullscreen(
backgroundColor: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: RecipeDisplayWidget(
recipe: recipe,
subheading: subheading,
),
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: MarketplaceTheme.spacing5,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
MarketplaceButton(
onPressed: () {
Navigator.of(context).pop(true);
},
buttonText: 'Close',
icon: Symbols.close,
),
...actions,
],
),
),
],
),
);
}
}

@ -1,81 +0,0 @@
// File generated by FlutterFire CLI.
// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
import 'package:flutter/foundation.dart'
show defaultTargetPlatform, kIsWeb, TargetPlatform;
/// Default [FirebaseOptions] for use with your Firebase apps.
///
/// Example:
/// ```dart
/// import 'firebase_options.dart';
/// // ...
/// await Firebase.initializeApp(
/// options: DefaultFirebaseOptions.currentPlatform,
/// );
/// ```
class DefaultFirebaseOptions {
static FirebaseOptions get currentPlatform {
if (kIsWeb) {
return web;
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return android;
case TargetPlatform.iOS:
return ios;
case TargetPlatform.macOS:
return macos;
case TargetPlatform.windows:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for windows - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
case TargetPlatform.linux:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for linux - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
default:
throw UnsupportedError(
'DefaultFirebaseOptions are not supported for this platform.',
);
}
}
static const FirebaseOptions web = FirebaseOptions(
apiKey: 'FIREBASE API KEY',
appId: 'FIREBASE APP ID',
messagingSenderId: 'FIREBASE MESSAGING ID',
projectId: 'PROJECT ID',
authDomain: 'AUTH DOMAIN',
storageBucket: 'STORAGE BUCKET ID',
);
static const FirebaseOptions android = FirebaseOptions(
apiKey: 'FIREBASE API KEY',
appId: 'FIREBASE APP ID',
messagingSenderId: 'FIREBASE MESSAGING ID',
projectId: 'PROJECT ID',
authDomain: 'AUTH DOMAIN',
storageBucket: 'STORAGE BUCKET ID',
);
static const FirebaseOptions ios = FirebaseOptions(
apiKey: 'FIREBASE API KEY',
appId: 'FIREBASE APP ID',
messagingSenderId: 'FIREBASE MESSAGING ID',
projectId: 'PROJECT ID',
authDomain: 'AUTH DOMAIN',
storageBucket: 'STORAGE BUCKET ID',
);
static const FirebaseOptions macos = FirebaseOptions(
apiKey: 'FIREBASE API KEY',
appId: 'FIREBASE APP ID',
messagingSenderId: 'FIREBASE MESSAGING ID',
projectId: 'PROJECT ID',
authDomain: 'AUTH DOMAIN',
storageBucket: 'STORAGE BUCKET ID',
);
}

@ -1,122 +0,0 @@
import 'package:ai_recipe_generation/util/device_info.dart';
import 'package:ai_recipe_generation/util/tap_recorder.dart';
import 'package:camera/camera.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:google_generative_ai/google_generative_ai.dart';
import 'package:provider/provider.dart';
import 'features/prompt/prompt_view_model.dart';
import 'features/recipes/recipes_view_model.dart';
import 'firebase_options.dart';
import 'router.dart';
import 'theme.dart';
late CameraDescription camera;
late BaseDeviceInfo deviceInfo;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
deviceInfo = await DeviceInfo.initialize(DeviceInfoPlugin());
if (DeviceInfo.isPhysicalDeviceWithCamera(deviceInfo)) {
final cameras = await availableCameras();
camera = cameras.first;
}
runApp(const MainApp());
}
class MainApp extends StatefulWidget {
const MainApp({super.key});
@override
State<MainApp> createState() => _MainAppState();
}
class _MainAppState extends State<MainApp> {
late GenerativeModel geminiVisionProModel;
late GenerativeModel geminiProModel;
@override
void initState() {
const apiKey =
String.fromEnvironment('API_KEY', defaultValue: 'key not found');
if (apiKey == 'key not found') {
throw InvalidApiKey(
'Key not found in environment. Please add an API key.',
);
}
geminiVisionProModel = GenerativeModel(
model: 'gemini-pro-vision',
apiKey: apiKey,
generationConfig: GenerationConfig(
temperature: 0.4,
topK: 32,
topP: 1,
maxOutputTokens: 4096,
),
safetySettings: [
SafetySetting(HarmCategory.harassment, HarmBlockThreshold.high),
SafetySetting(HarmCategory.hateSpeech, HarmBlockThreshold.high),
],
);
geminiProModel = GenerativeModel(
model: 'gemini-pro',
apiKey: const String.fromEnvironment('API_KEY'),
generationConfig: GenerationConfig(
temperature: 0.4,
topK: 32,
topP: 1,
maxOutputTokens: 4096,
),
safetySettings: [
SafetySetting(HarmCategory.harassment, HarmBlockThreshold.high),
SafetySetting(HarmCategory.hateSpeech, HarmBlockThreshold.high),
],
);
super.initState();
}
@override
Widget build(BuildContext context) {
final recipesViewModel = SavedRecipesViewModel();
return TapRecorder(
child: MultiProvider(
providers: [
ChangeNotifierProvider(
create: (_) => PromptViewModel(
multiModalModel: geminiVisionProModel,
textModel: geminiProModel,
),
),
ChangeNotifierProvider(
create: (_) => recipesViewModel,
),
],
child: SafeArea(
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: MarketplaceTheme.theme,
scrollBehavior: const ScrollBehavior().copyWith(
dragDevices: {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
PointerDeviceKind.stylus,
PointerDeviceKind.unknown,
},
),
home: const AdaptiveRouter(),
),
),
),
);
}
}

@ -1,244 +0,0 @@
import 'package:ai_recipe_generation/app_bar.dart';
import 'package:ai_recipe_generation/features/prompt/prompt_screen.dart';
import 'package:ai_recipe_generation/features/prompt/prompt_view_model.dart';
import 'package:ai_recipe_generation/features/recipes/saved_recipes_screen.dart';
import 'package:ai_recipe_generation/widgets/bottom_bar_shape_border.dart';
import 'package:ai_recipe_generation/widgets/marketplace_button_widget.dart';
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart';
import 'theme.dart';
import 'widgets/icon_loading_indicator.dart';
const double avatarSize = 50;
const double collapsedHeight = 100;
const double expandedHeight = 300;
const double bottomTabBarHeight = 50;
class AdaptiveRouter extends StatefulWidget {
const AdaptiveRouter({super.key});
@override
State<AdaptiveRouter> createState() => _AdaptiveRouterState();
}
class _AdaptiveRouterState extends State<AdaptiveRouter>
with TickerProviderStateMixin {
late TextStyle _textStyle;
late ScrollController scrollController;
late TabController tabController;
bool innerScrollAllowed = false;
@override
void initState() {
super.initState();
tabController = TabController(length: 2, vsync: this);
_textStyle = MarketplaceTheme.heading1.copyWith(
color: Colors.black87.withAlpha(255),
);
scrollController = ScrollController();
scrollController.addListener(_scrollListener);
}
double prevOffset = 0;
void _scrollListener() {
setState(() {
innerScrollAllowed = scrollController.offset >= 230;
if (scrollController.offset >= 230) {
scrollController.animateTo(230,
duration: const Duration(milliseconds: 100),
curve: Curves.decelerate);
}
// Don't change the text opacity if scrolling down from original position (overscroll)
if (scrollController.offset < 0) return;
// By offset 200, ensure the text is transparent
if (scrollController.offset > 200) {
_textStyle = _textStyle.copyWith(
color: Colors.black87.withAlpha(0),
);
return;
}
var value = double.parse(
(1 - (scrollController.offset - 50) / 100).toStringAsFixed(2),
);
if (scrollController.offset > 200 && value > 0) value = 0;
if (value > 1) value = 1;
if (value < 0) value = 0;
_textStyle = _textStyle.copyWith(
color: Colors.black87.withAlpha((255 * value).ceil()),
);
});
}
@override
void dispose() {
scrollController.dispose();
tabController.dispose();
super.dispose();
}
List<NavigationRailDestination> destinations = [
const NavigationRailDestination(
icon: Icon(Symbols.home),
label: Text('Create a recipe'),
),
const NavigationRailDestination(
icon: Icon(Symbols.bookmarks),
label: Text('Saved Recipes'),
)
];
@override
Widget build(BuildContext context) {
final viewModel = context.watch<PromptViewModel>();
return LayoutBuilder(
builder: (context, constraints) {
return Scaffold(
body: Stack(
children: [
CustomScrollView(
controller: scrollController,
keyboardDismissBehavior:
ScrollViewKeyboardDismissBehavior.onDrag,
slivers: [
AnimatedAppBar(
scrollController: scrollController,
textStyle: _textStyle,
tabController: tabController,
),
SliverToBoxAdapter(
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: constraints.minHeight,
),
child: TabBarView(
controller: tabController,
children: [
PromptScreen(
canScroll: innerScrollAllowed,
),
SavedRecipesScreen(
canScroll: innerScrollAllowed,
),
],
),
),
)
],
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
height: bottomTabBarHeight,
decoration: ShapeDecoration(
shadows: const [
BoxShadow(
offset: Offset(1, -1),
color: Colors.black45,
blurRadius: 5,
)
],
shape: const BottomBarShapeBorder(50),
color: Theme.of(context).primaryColor,
),
child: TabBar(
labelColor: Colors.black,
unselectedLabelColor: Colors.black26,
controller: tabController,
onTap: (idx) {
setState(() {});
},
dividerColor: Colors.transparent,
tabs: [
for (var destination in destinations) destination.icon,
],
),
),
),
if (viewModel.loadingNewRecipe)
Positioned(
top: (MediaQuery.of(context).size.height / 2) - 80,
left: (MediaQuery.of(context).size.width / 2) - 80,
height: 160,
width: 160,
child: IconLoadingAnimator(
icons: const [
Symbols.icecream,
Symbols.local_pizza,
Symbols.restaurant_menu,
Symbols.egg,
Symbols.bakery_dining,
Symbols.skillet,
Symbols.nutrition,
Symbols.grocery,
Symbols.set_meal,
Icons.egg_alt,
Symbols.oven,
Icons.dinner_dining,
Icons.outdoor_grill,
Icons.cookie,
Icons.blender,
Symbols.stockpot,
],
),
),
if (viewModel.geminiFailureResponse != null)
Positioned(
top: (MediaQuery.of(context).size.height / 4),
left: (MediaQuery.of(context).size.width / 2) - 160,
height: MediaQuery.of(context).size.height / 4,
width: 320,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
MarketplaceTheme.defaultBorderRadius),
boxShadow: const [
BoxShadow(
offset: Offset(-1, 1),
color: Colors.black45,
blurRadius: 5,
)
],
color: Colors.white,
border: Border.all(
color: MarketplaceTheme.focusedBorderColor,
width: 1,
),
),
child: Padding(
padding: const EdgeInsets.all(MarketplaceTheme.spacing6),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(viewModel.geminiFailureResponse!),
Align(
alignment: Alignment.bottomRight,
child: MarketplaceButton(
onPressed: () {
viewModel.geminiFailureResponse = null;
},
buttonText: "Dismiss",
icon: Symbols.close,
),
)
],
),
),
),
),
],
),
);
},
);
}
}

@ -1,25 +0,0 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import '../features/recipes/recipe_model.dart';
const recipePath = '/recipes';
final firestore = FirebaseFirestore.instance;
class FirestoreService {
static Future<Null> saveRecipe(Recipe recipe) async {
await firestore
.collection(recipePath)
.doc(recipe.id)
.set(recipe.toFirestore());
}
static Future<Null> deleteRecipe(Recipe recipe) async {
await firestore.doc("$recipePath/${recipe.id}").delete();
}
static Future<Null> updateRecipe(Recipe recipe) async {
await firestore
.doc("$recipePath/${recipe.id}")
.update(recipe.toFirestore());
}
}

@ -1,58 +0,0 @@
import 'package:google_generative_ai/google_generative_ai.dart';
import '../features/prompt/prompt_model.dart';
class GeminiService {
static Future<GenerateContentResponse> generateContent(
GenerativeModel model, PromptData prompt) async {
if (prompt.images.isEmpty) {
return await GeminiService.generateContentFromText(model, prompt);
} else {
return await GeminiService.generateContentFromMultiModal(model, prompt);
}
}
static Future<GenerateContentResponse> generateContentFromMultiModal(
GenerativeModel model, PromptData prompt) async {
final mainText = TextPart(prompt.textInput);
final additionalTextParts =
prompt.additionalTextInputs.map((t) => TextPart(t));
final imagesParts = <DataPart>[];
for (var f in prompt.images) {
final bytes = await (f.readAsBytes());
imagesParts.add(DataPart('image/jpeg', bytes));
}
final input = [
Content.multi([...imagesParts, mainText, ...additionalTextParts])
];
return await model.generateContent(
input,
generationConfig: GenerationConfig(
temperature: 0.4,
topK: 32,
topP: 1,
maxOutputTokens: 4096,
),
safetySettings: [
SafetySetting(HarmCategory.harassment, HarmBlockThreshold.high),
SafetySetting(HarmCategory.hateSpeech, HarmBlockThreshold.high),
],
);
}
static Future<GenerateContentResponse> generateContentFromText(
GenerativeModel model, PromptData prompt) async {
final mainText = TextPart(prompt.textInput);
final additionalTextParts =
prompt.additionalTextInputs.map((t) => TextPart(t)).join("\n");
return await model.generateContent([
Content.text(
'${mainText.text} \n $additionalTextParts',
)
]);
}
}

@ -1,134 +0,0 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
abstract class MarketplaceTheme {
static ThemeData theme = ThemeData(
fontFamily: GoogleFonts.lexend().fontFamily,
textTheme: GoogleFonts.lexendTextTheme().copyWith().apply(
bodyColor: const Color(0xff000000),
displayColor: const Color(0xff000000)),
colorScheme: const ColorScheme.light(
primary: Color(0xffA2E3F6),
secondary: Color(0xff4FAD85),
tertiary: Color(0xffDE7A60),
scrim: Color(0xffFFABC7),
surface: Color(0xffFDF7F0),
onSecondary: Color(0xff000000),
shadow: Color(0xffAEAEAE),
onPrimary: Color(0xffFFFFFF),
),
useMaterial3: true,
canvasColor: Colors.transparent,
navigationBarTheme: NavigationBarThemeData(
indicatorColor: const Color(0xffA2E3F6),
indicatorShape: CircleBorder(
side: BorderSide.lerp(
const BorderSide(
color: Color(0xff000000),
width: 2,
),
const BorderSide(
color: Color(0xff000000),
width: 2,
),
1),
),
),
);
static const Color primary = Color(0xffA2E3F6);
static const Color scrim = Color(0xffFFABC7);
static const Color tertiary = Color(0xffDE7A60);
static const Color secondary = Color(0xff4FAD85);
static const Color borderColor = Colors.black12;
static const Color focusedBorderColor = Colors.black45;
static const double defaultBorderRadius = 16;
static const double defaultTextSize = 16;
static const Color defaultTextColor = Colors.black87;
static TextStyle get heading1 => theme.textTheme.headlineLarge!.copyWith(
fontWeight: FontWeight.bold,
fontSize: 28,
//height: 36,
color: theme.colorScheme.onSecondary,
);
static TextStyle get heading2 => theme.textTheme.headlineMedium!.copyWith(
fontWeight: FontWeight.bold,
fontSize: 24,
//height: 32,
color: theme.colorScheme.onSecondary,
);
static TextStyle get heading3 => theme.textTheme.headlineSmall!.copyWith(
fontWeight: FontWeight.bold,
fontSize: 18,
//height: 24,
color: theme.colorScheme.onSecondary,
);
static TextStyle get subheading1 => theme.textTheme.bodyLarge!.copyWith(
fontWeight: FontWeight.normal,
fontSize: 18,
//height: 20,
color: theme.colorScheme.onSecondary,
);
static TextStyle get subheading2 => theme.textTheme.bodyMedium!.copyWith(
fontWeight: FontWeight.normal,
fontSize: 14,
//height: 18,
color: theme.colorScheme.onSecondary,
);
static TextStyle get paragraph => theme.textTheme.bodySmall!.copyWith(
fontWeight: FontWeight.normal,
fontSize: 14,
//height: 18,
color: theme.colorScheme.onSecondary,
);
static TextStyle get label => theme.textTheme.labelSmall!.copyWith(
fontWeight: FontWeight.w600,
fontSize: 11,
//height: 16,
color: theme.colorScheme.onSecondary,
);
static TextStyle get dossierParagraph => GoogleFonts.anonymousPro().copyWith(
fontWeight: FontWeight.normal,
fontSize: 14,
//height: 18,
color: theme.colorScheme.onSecondary,
);
static TextStyle get dossierSubheading => GoogleFonts.anonymousPro().copyWith(
fontWeight: FontWeight.normal,
fontSize: 18,
//height: 18,
color: theme.colorScheme.onSecondary,
);
static TextStyle get dossierHeading => GoogleFonts.anonymousPro().copyWith(
fontWeight: FontWeight.bold,
fontSize: 28,
//height: 18,
color: theme.colorScheme.onSecondary,
);
static const double _spacingUnit = 8;
static const double spacing8 = _spacingUnit / 2;
static const double spacing7 = _spacingUnit;
static const double spacing6 = _spacingUnit * 1.5;
static const double spacing5 = _spacingUnit * 2;
static const double spacing4 = _spacingUnit * 2.5;
static const double spacing3 = _spacingUnit * 3;
static const double spacing2 = _spacingUnit * 3.5;
static const double spacing1 = _spacingUnit * 4;
static double lineWidth = 1;
static const Widget verticalSpacer = SizedBox(height: spacing5);
}

@ -1,39 +0,0 @@
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
class DeviceInfo {
static Future<BaseDeviceInfo> initialize(DeviceInfoPlugin plugin) async {
if (kIsWeb) {
return await plugin.webBrowserInfo;
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return await plugin.androidInfo;
case TargetPlatform.iOS:
return await plugin.iosInfo;
case TargetPlatform.macOS:
return plugin.macOsInfo;
case TargetPlatform.windows:
return await plugin.windowsInfo;
case TargetPlatform.linux:
return await plugin.linuxInfo;
default:
throw UnsupportedError(
'Device info not supported for this platform',
);
}
}
static bool isPhysicalDeviceWithCamera(BaseDeviceInfo deviceInfo) {
if (deviceInfo is! IosDeviceInfo && deviceInfo is! AndroidDeviceInfo) {
return false;
}
if (deviceInfo is IosDeviceInfo && deviceInfo.isPhysicalDevice) {
return true;
}
if (deviceInfo is AndroidDeviceInfo && deviceInfo.isPhysicalDevice) {
return true;
}
return false;
}
}

@ -1,13 +0,0 @@
import 'package:flutter/rendering.dart';
extension SliverBreakpointUtils on SliverConstraints {
bool get isTablet => crossAxisExtent > 730 && crossAxisExtent < 1000;
bool get isDesktop => crossAxisExtent > 1000;
bool get isMobile => crossAxisExtent < 730;
}
extension BoxBreakpointUtils on BoxConstraints {
bool get isTablet => maxWidth > 730 && maxWidth < 1000;
bool get isDesktop => maxWidth > 1000;
bool get isMobile => maxWidth < 730;
}

@ -1,66 +0,0 @@
enum CuisineFilter {
italian,
mexican,
american,
french,
japanese,
chinese,
indian,
greek,
moroccan,
ethiopian,
southAfrican,
}
enum BasicIngredientsFilter {
oil,
butter,
flour,
salt,
pepper,
sugar,
milk,
vinegar,
}
enum DietaryRestrictionsFilter {
vegan,
vegetarian,
lactoseIntolerant,
kosher,
// keto,
wheatAllergies,
nutAllergies,
fishAllergies,
soyAllergies,
}
String dietaryRestrictionReadable(DietaryRestrictionsFilter filter) {
return switch (filter) {
DietaryRestrictionsFilter.vegan => 'vegan',
DietaryRestrictionsFilter.vegetarian => 'vegetarian',
DietaryRestrictionsFilter.lactoseIntolerant => 'dairy free',
DietaryRestrictionsFilter.kosher => 'kosher',
// DietaryRestrictionsFilter.keto => 'low carb',
DietaryRestrictionsFilter.wheatAllergies => 'wheat allergy',
DietaryRestrictionsFilter.nutAllergies => 'nut allergy',
DietaryRestrictionsFilter.fishAllergies => 'fish allergy',
DietaryRestrictionsFilter.soyAllergies => 'soy allergy',
};
}
String cuisineReadable(CuisineFilter filter) {
return switch (filter) {
CuisineFilter.italian => 'Italian',
CuisineFilter.mexican => 'Mexican',
CuisineFilter.american => 'American',
CuisineFilter.french => 'French',
CuisineFilter.japanese => 'Japanese',
CuisineFilter.chinese => 'Chinese',
CuisineFilter.indian => 'Indian',
CuisineFilter.ethiopian => 'Ethiopian',
CuisineFilter.moroccan => 'Moroccan',
CuisineFilter.greek => 'Greek',
CuisineFilter.southAfrican => 'South African',
};
}

@ -1,8 +0,0 @@
String cleanJson(String maybeInvalidJson) {
if (maybeInvalidJson.contains('```')) {
final withoutLeading = maybeInvalidJson.split('```json').last;
final withoutTrailing = withoutLeading.split('```').first;
return withoutTrailing;
}
return maybeInvalidJson;
}

@ -1,121 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
/// From: https://gist.github.com/creativecreatorormaybenot/cd42b60cb33c9962b19f629ec638d4de
/// This is code that I (https://twitter.com/creativemaybeno) wrote for a
/// StackOverflow answer.
/// You can find it here: https://stackoverflow.com/a/65067655/6509751.
/// List of the taps recorded by [TapRecorder].
///
/// This is only a make-shift solution of course. This will only be viable
/// when using a single [TapRecorder] because it is saved as a top-level
/// variable.
@visibleForTesting
final recordedTaps = <Offset>[];
/// These are the parameters for the visualization of the recorded taps.
const _tapRadius = 15.0,
_tapDuration = Duration(milliseconds: 420),
_tapColor = Colors.white,
_shadowColor = Colors.black,
_shadowElevation = 2.0;
/// Widget that records any taps that hit its child.
///
/// It does not matter to this widget whether the child accepts the hit events.
/// Everything hitting the rect of the child will be recorded.
///
/// It will both visualize them and add them to [recordedTaps].
class TapRecorder extends SingleChildRenderObjectWidget {
const TapRecorder({super.key, required Widget child}) : super(child: child);
@override
RenderObject createRenderObject(BuildContext context) {
return _RenderTapRecorder();
}
}
class _RenderTapRecorder extends RenderProxyBox with _SilentTickerProvider {
final _recordedTaps = <_RecordedTap>[];
@override
void detach() {
for (final recordedTap in _recordedTaps) {
(recordedTap.animation as AnimationController).dispose();
}
_recordedTaps.clear();
super.detach();
}
@override
bool hitTest(BoxHitTestResult result, {required Offset position}) {
if (!size.contains(position)) return false;
// We always want to add a hit test entry for ourselves as we want to react
// to each and every hit event.
result.add(BoxHitTestEntry(this, position));
return hitTestChildren(result, position: position);
}
@override
void handleEvent(PointerEvent event, covariant HitTestEntry entry) {
// We do not want to interfere in the gesture arena, which is why we are not
// using regular tap recognizers. Instead, we handle it ourselves and always
// react to the hit events (ignoring the gesture arena).
if (event is PointerDownEvent) {
// Records the global position.
recordedTaps.add(event.position);
final controller = AnimationController(
vsync: this,
duration: _tapDuration,
),
recordedTap = _RecordedTap(event.localPosition, controller);
_recordedTaps.add(recordedTap);
controller
..addListener(markNeedsPaint)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.dispose();
_recordedTaps.remove(recordedTap);
}
})
..forward();
}
}
@override
void paint(PaintingContext context, Offset offset) {
context.paintChild(child!, offset);
final canvas = context.canvas;
for (final tap in _recordedTaps) {
final path = Path()
..addOval(
Rect.fromCircle(center: tap.localPosition, radius: _tapRadius));
final opacity = 1 - tap.animation.value;
canvas.drawShadow(path, _shadowColor.withAlpha((2565 * opacity).ceil()),
_shadowElevation, true);
canvas.drawPath(
path, Paint()..color = _tapColor.withAlpha((2565 * opacity).ceil()));
}
}
}
class _RecordedTap {
_RecordedTap(this.localPosition, this.animation);
final Offset localPosition;
final Animation<double> animation;
}
/// Ticker provider that does not perform any diagnostics.
///
/// We trust that the [_RenderTapRecorder] instance will dispose all tickers
/// by disposing the animation controllers.
mixin _SilentTickerProvider implements TickerProvider {
@override
Ticker createTicker(TickerCallback onTick) => Ticker(onTick);
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save