├── lib
├── utils
│ ├── constants.dart
│ ├── time.dart
│ └── theme.dart
├── generated
│ └── assets.dart
├── main.dart
├── models
│ └── chat_message.dart
├── api
│ ├── ai_config.dart
│ └── ai_service.dart
├── firebase_options.dart
└── ui
│ ├── widgets
│ └── home_page_widgets.dart
│ └── screens
│ └── home.dart
├── linux
├── .gitignore
├── runner
│ ├── main.cc
│ ├── my_application.h
│ ├── CMakeLists.txt
│ └── my_application.cc
├── flutter
│ ├── generated_plugin_registrant.h
│ ├── generated_plugin_registrant.cc
│ ├── generated_plugins.cmake
│ └── CMakeLists.txt
└── CMakeLists.txt
├── .fvmrc
├── ios
├── Flutter
│ ├── Debug.xcconfig
│ ├── Release.xcconfig
│ └── AppFrameworkInfo.plist
├── Runner
│ ├── Runner-Bridging-Header.h
│ ├── Assets.xcassets
│ │ ├── LaunchImage.imageset
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ ├── LaunchImage@3x.png
│ │ │ ├── README.md
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ ├── Icon-App-20x20@1x.png
│ │ │ ├── Icon-App-20x20@2x.png
│ │ │ ├── Icon-App-20x20@3x.png
│ │ │ ├── Icon-App-29x29@1x.png
│ │ │ ├── Icon-App-29x29@2x.png
│ │ │ ├── Icon-App-29x29@3x.png
│ │ │ ├── Icon-App-40x40@1x.png
│ │ │ ├── Icon-App-40x40@2x.png
│ │ │ ├── Icon-App-40x40@3x.png
│ │ │ ├── Icon-App-50x50@1x.png
│ │ │ ├── Icon-App-50x50@2x.png
│ │ │ ├── Icon-App-57x57@1x.png
│ │ │ ├── Icon-App-57x57@2x.png
│ │ │ ├── Icon-App-60x60@2x.png
│ │ │ ├── Icon-App-60x60@3x.png
│ │ │ ├── Icon-App-72x72@1x.png
│ │ │ ├── Icon-App-72x72@2x.png
│ │ │ ├── Icon-App-76x76@1x.png
│ │ │ ├── Icon-App-76x76@2x.png
│ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ ├── Icon-App-83.5x83.5@2x.png
│ │ │ └── Contents.json
│ │ └── LaunchBackground.imageset
│ │ │ ├── background.png
│ │ │ └── Contents.json
│ ├── AppDelegate.swift
│ ├── Base.lproj
│ │ ├── Main.storyboard
│ │ └── LaunchScreen.storyboard
│ └── Info.plist
├── Runner.xcodeproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── WorkspaceSettings.xcsettings
│ │ └── IDEWorkspaceChecks.plist
├── RunnerTests
│ └── RunnerTests.swift
└── .gitignore
├── web
├── favicon.png
├── icons
│ ├── Icon-192.png
│ ├── Icon-512.png
│ ├── Icon-maskable-192.png
│ └── Icon-maskable-512.png
├── manifest.json
└── index.html
├── macos
├── Flutter
│ ├── Flutter-Debug.xcconfig
│ ├── Flutter-Release.xcconfig
│ └── GeneratedPluginRegistrant.swift
├── Runner
│ ├── Configs
│ │ ├── Debug.xcconfig
│ │ ├── Release.xcconfig
│ │ ├── Warnings.xcconfig
│ │ └── AppInfo.xcconfig
│ ├── Assets.xcassets
│ │ └── AppIcon.appiconset
│ │ │ ├── app_icon_128.png
│ │ │ ├── app_icon_16.png
│ │ │ ├── app_icon_256.png
│ │ │ ├── app_icon_32.png
│ │ │ ├── app_icon_512.png
│ │ │ ├── app_icon_64.png
│ │ │ ├── app_icon_1024.png
│ │ │ └── Contents.json
│ ├── Release.entitlements
│ ├── AppDelegate.swift
│ ├── DebugProfile.entitlements
│ ├── MainFlutterWindow.swift
│ └── Info.plist
├── .gitignore
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── Runner.xcodeproj
│ ├── project.xcworkspace
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
└── RunnerTests
│ └── RunnerTests.swift
├── assets
├── images
│ ├── icon.png
│ └── splash.png
└── screenshots
│ ├── chat.png
│ ├── home.png
│ └── splash.png
├── windows
├── runner
│ ├── resources
│ │ └── app_icon.ico
│ ├── resource.h
│ ├── runner.exe.manifest
│ ├── utils.h
│ ├── flutter_window.h
│ ├── main.cpp
│ ├── CMakeLists.txt
│ ├── utils.cpp
│ ├── flutter_window.cpp
│ ├── Runner.rc
│ ├── win32_window.h
│ └── win32_window.cpp
├── .gitignore
├── flutter
│ ├── generated_plugin_registrant.h
│ ├── generated_plugins.cmake
│ ├── generated_plugin_registrant.cc
│ └── CMakeLists.txt
└── CMakeLists.txt
├── android
├── app
│ ├── src
│ │ ├── main
│ │ │ ├── res
│ │ │ │ ├── drawable
│ │ │ │ │ ├── background.png
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── drawable-v21
│ │ │ │ │ ├── background.png
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── launcher_icon.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── launcher_icon.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── launcher_icon.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── launcher_icon.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── launcher_icon.png
│ │ │ │ ├── drawable-hdpi
│ │ │ │ │ ├── android12splash.png
│ │ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ ├── drawable-mdpi
│ │ │ │ │ ├── android12splash.png
│ │ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ ├── drawable-xhdpi
│ │ │ │ │ ├── android12splash.png
│ │ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ ├── drawable-xxhdpi
│ │ │ │ │ ├── android12splash.png
│ │ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ ├── drawable-xxxhdpi
│ │ │ │ │ ├── android12splash.png
│ │ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ ├── drawable-night-hdpi
│ │ │ │ │ └── android12splash.png
│ │ │ │ ├── drawable-night-mdpi
│ │ │ │ │ └── android12splash.png
│ │ │ │ ├── values
│ │ │ │ │ ├── colors.xml
│ │ │ │ │ └── styles.xml
│ │ │ │ ├── drawable-night-xhdpi
│ │ │ │ │ └── android12splash.png
│ │ │ │ ├── drawable-night-xxhdpi
│ │ │ │ │ └── android12splash.png
│ │ │ │ ├── drawable-night-xxxhdpi
│ │ │ │ │ └── android12splash.png
│ │ │ │ ├── mipmap-anydpi-v26
│ │ │ │ │ └── launcher_icon.xml
│ │ │ │ ├── values-v31
│ │ │ │ │ └── styles.xml
│ │ │ │ ├── values-night-v31
│ │ │ │ │ └── styles.xml
│ │ │ │ └── values-night
│ │ │ │ │ └── styles.xml
│ │ │ ├── kotlin
│ │ │ │ └── com
│ │ │ │ │ └── example
│ │ │ │ │ └── promptu
│ │ │ │ │ └── MainActivity.kt
│ │ │ └── AndroidManifest.xml
│ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ └── profile
│ │ │ └── AndroidManifest.xml
│ ├── google-services.json
│ └── build.gradle.kts
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
├── .gitignore
├── build.gradle.kts
└── settings.gradle.kts
├── firebase.json
├── .gitignore
├── LICENSE
├── test
└── widget_test.dart
├── analysis_options.yaml
├── .metadata
├── pubspec.yaml
└── README.md
/lib/utils/constants.dart:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/linux/.gitignore:
--------------------------------------------------------------------------------
1 | flutter/ephemeral
2 |
--------------------------------------------------------------------------------
/.fvmrc:
--------------------------------------------------------------------------------
1 | {
2 | "flutter": "3.35.6"
3 | }
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/web/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/web/favicon.png
--------------------------------------------------------------------------------
/macos/Flutter/Flutter-Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "ephemeral/Flutter-Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/assets/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/assets/images/icon.png
--------------------------------------------------------------------------------
/macos/Flutter/Flutter-Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "ephemeral/Flutter-Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/web/icons/Icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/web/icons/Icon-192.png
--------------------------------------------------------------------------------
/web/icons/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/web/icons/Icon-512.png
--------------------------------------------------------------------------------
/assets/images/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/assets/images/splash.png
--------------------------------------------------------------------------------
/assets/screenshots/chat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/assets/screenshots/chat.png
--------------------------------------------------------------------------------
/assets/screenshots/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/assets/screenshots/home.png
--------------------------------------------------------------------------------
/assets/screenshots/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/assets/screenshots/splash.png
--------------------------------------------------------------------------------
/web/icons/Icon-maskable-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/web/icons/Icon-maskable-192.png
--------------------------------------------------------------------------------
/web/icons/Icon-maskable-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/web/icons/Icon-maskable-512.png
--------------------------------------------------------------------------------
/macos/Runner/Configs/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Debug.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/windows/runner/resources/app_icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/windows/runner/resources/app_icon.ico
--------------------------------------------------------------------------------
/macos/Runner/Configs/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Release.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/.gitignore:
--------------------------------------------------------------------------------
1 | # Flutter-related
2 | **/Flutter/ephemeral/
3 | **/Pods/
4 |
5 | # Xcode-related
6 | **/dgph
7 | **/xcuserdata/
8 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/android/app/src/main/res/drawable/background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/android/app/src/main/res/drawable-v21/background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/android/app/src/main/res/mipmap-hdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/android/app/src/main/res/mipmap-mdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/android12splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/android/app/src/main/res/drawable-hdpi/android12splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/android12splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/android/app/src/main/res/drawable-mdpi/android12splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/android12splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/android/app/src/main/res/drawable-xhdpi/android12splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/android12splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/android/app/src/main/res/drawable-xxhdpi/android12splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/android12splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/android/app/src/main/res/drawable-xxxhdpi/android12splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-hdpi/android12splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/android/app/src/main/res/drawable-night-hdpi/android12splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-mdpi/android12splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/android/app/src/main/res/drawable-night-mdpi/android12splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-xhdpi/android12splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/android/app/src/main/res/drawable-night-xhdpi/android12splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joel-Fah/gyde/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/example/promptu/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.promptu
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity : FlutterActivity()
6 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/linux/runner/main.cc:
--------------------------------------------------------------------------------
1 | #include "my_application.h"
2 |
3 | int main(int argc, char** argv) {
4 | g_autoptr(MyApplication) app = my_application_new();
5 | return g_application_run(G_APPLICATION(app), argc, argv);
6 | }
7 |
--------------------------------------------------------------------------------
/macos/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip
6 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/macos/Runner/Release.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 | .cxx/
9 |
10 | # Remember to never publicly share your keystore.
11 | # See https://flutter.dev/to/reference-keystore
12 | key.properties
13 | **/*.keystore
14 | **/*.jks
15 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/RunnerTests/RunnerTests.swift:
--------------------------------------------------------------------------------
1 | import Flutter
2 | import UIKit
3 | import XCTest
4 |
5 | class RunnerTests: XCTestCase {
6 |
7 | func testExample() {
8 | // If you add code to the Runner application, consider adding tests here.
9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/macos/RunnerTests/RunnerTests.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 | import XCTest
4 |
5 | class RunnerTests: XCTestCase {
6 |
7 | func testExample() {
8 | // If you add code to the Runner application, consider adding tests here.
9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/windows/.gitignore:
--------------------------------------------------------------------------------
1 | flutter/ephemeral/
2 |
3 | # Visual Studio user-specific files.
4 | *.suo
5 | *.user
6 | *.userosscache
7 | *.sln.docstates
8 |
9 | # Visual Studio build-related files.
10 | x64/
11 | x86/
12 |
13 | # Visual Studio cache files
14 | # files ending in .cache can be ignored
15 | *.[Cc]ache
16 | # but keep track of directories ending in .cache
17 | !*.[Cc]ache/
18 |
--------------------------------------------------------------------------------
/macos/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | @main
5 | class AppDelegate: FlutterAppDelegate {
6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
7 | return true
8 | }
9 |
10 | override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
11 | return true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/linux/flutter/generated_plugin_registrant.h:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | // clang-format off
6 |
7 | #ifndef GENERATED_PLUGIN_REGISTRANT_
8 | #define GENERATED_PLUGIN_REGISTRANT_
9 |
10 | #include
11 |
12 | // Registers Flutter plugins.
13 | void fl_register_plugins(FlPluginRegistry* registry);
14 |
15 | #endif // GENERATED_PLUGIN_REGISTRANT_
16 |
--------------------------------------------------------------------------------
/windows/flutter/generated_plugin_registrant.h:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | // clang-format off
6 |
7 | #ifndef GENERATED_PLUGIN_REGISTRANT_
8 | #define GENERATED_PLUGIN_REGISTRANT_
9 |
10 | #include
11 |
12 | // Registers Flutter plugins.
13 | void RegisterPlugins(flutter::PluginRegistry* registry);
14 |
15 | #endif // GENERATED_PLUGIN_REGISTRANT_
16 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/firebase.json:
--------------------------------------------------------------------------------
1 | {"flutter":{"platforms":{"android":{"default":{"projectId":"promptu-7b099","appId":"1:1028490463027:android:7e8ddc053b24e8fbff6046","fileOutput":"android/app/google-services.json"}},"dart":{"lib/firebase_options.dart":{"projectId":"promptu-7b099","configurations":{"android":"1:1028490463027:android:7e8ddc053b24e8fbff6046","ios":"1:1028490463027:ios:d6bfed9bad3bc943ff6046","web":"1:1028490463027:web:0075bcf4d0a083faff6046"}}}}}}
--------------------------------------------------------------------------------
/macos/Runner/DebugProfile.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.cs.allow-jit
8 |
9 | com.apple.security.network.server
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "background.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import Flutter
2 | import UIKit
3 |
4 | @main
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/macos/Runner/MainFlutterWindow.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | class MainFlutterWindow: NSWindow {
5 | override func awakeFromNib() {
6 | let flutterViewController = FlutterViewController()
7 | let windowFrame = self.frame
8 | self.contentViewController = flutterViewController
9 | self.setFrame(windowFrame, display: true)
10 |
11 | RegisterGeneratedPlugins(registry: flutterViewController)
12 |
13 | super.awakeFromNib()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/linux/runner/my_application.h:
--------------------------------------------------------------------------------
1 | #ifndef FLUTTER_MY_APPLICATION_H_
2 | #define FLUTTER_MY_APPLICATION_H_
3 |
4 | #include
5 |
6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
7 | GtkApplication)
8 |
9 | /**
10 | * my_application_new:
11 | *
12 | * Creates a new Flutter-based application.
13 | *
14 | * Returns: a new #MyApplication.
15 | */
16 | MyApplication* my_application_new();
17 |
18 | #endif // FLUTTER_MY_APPLICATION_H_
19 |
--------------------------------------------------------------------------------
/windows/runner/resource.h:
--------------------------------------------------------------------------------
1 | //{{NO_DEPENDENCIES}}
2 | // Microsoft Visual C++ generated include file.
3 | // Used by Runner.rc
4 | //
5 | #define IDI_APP_ICON 101
6 |
7 | // Next default values for new objects
8 | //
9 | #ifdef APSTUDIO_INVOKED
10 | #ifndef APSTUDIO_READONLY_SYMBOLS
11 | #define _APS_NEXT_RESOURCE_VALUE 102
12 | #define _APS_NEXT_COMMAND_VALUE 40001
13 | #define _APS_NEXT_CONTROL_VALUE 1001
14 | #define _APS_NEXT_SYMED_VALUE 101
15 | #endif
16 | #endif
17 |
--------------------------------------------------------------------------------
/lib/generated/assets.dart:
--------------------------------------------------------------------------------
1 | ///This file is automatically generated. DO NOT EDIT, all your changes would be lost.
2 | class Assets {
3 | Assets._();
4 |
5 | static const String imagesIcon = 'assets/images/icon.png';
6 | static const String imagesSplash = 'assets/images/splash.png';
7 | static const String screenshotsChat = 'assets/screenshots/chat.png';
8 | static const String screenshotsHome = 'assets/screenshots/home.png';
9 | static const String screenshotsSplash = 'assets/screenshots/splash.png';
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "LaunchImage.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "LaunchImage@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "LaunchImage@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/android/build.gradle.kts:
--------------------------------------------------------------------------------
1 | allprojects {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | }
6 | }
7 |
8 | val newBuildDir: Directory =
9 | rootProject.layout.buildDirectory
10 | .dir("../../build")
11 | .get()
12 | rootProject.layout.buildDirectory.value(newBuildDir)
13 |
14 | subprojects {
15 | val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
16 | project.layout.buildDirectory.value(newSubprojectBuildDir)
17 | }
18 | subprojects {
19 | project.evaluationDependsOn(":app")
20 | }
21 |
22 | tasks.register("clean") {
23 | delete(rootProject.layout.buildDirectory)
24 | }
25 |
--------------------------------------------------------------------------------
/macos/Runner/Configs/Warnings.xcconfig:
--------------------------------------------------------------------------------
1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
2 | GCC_WARN_UNDECLARED_SELECTOR = YES
3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
6 | CLANG_WARN_PRAGMA_PACK = YES
7 | CLANG_WARN_STRICT_PROTOTYPES = YES
8 | CLANG_WARN_COMMA = YES
9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES
10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
12 | GCC_WARN_SHADOW = YES
13 | CLANG_WARN_UNREACHABLE_CODE = YES
14 |
--------------------------------------------------------------------------------
/macos/Runner/Configs/AppInfo.xcconfig:
--------------------------------------------------------------------------------
1 | // Application-level settings for the Runner target.
2 | //
3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
4 | // future. If not, the values below would default to using the project name when this becomes a
5 | // 'flutter create' template.
6 |
7 | // The application's name. By default this is also the title of the Flutter window.
8 | PRODUCT_NAME = promptu
9 |
10 | // The application's bundle identifier
11 | PRODUCT_BUNDLE_IDENTIFIER = com.example.promptu
12 |
13 | // The copyright displayed in application information
14 | PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved.
15 |
--------------------------------------------------------------------------------
/windows/runner/runner.exe.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PerMonitorV2
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/windows/runner/utils.h:
--------------------------------------------------------------------------------
1 | #ifndef RUNNER_UTILS_H_
2 | #define RUNNER_UTILS_H_
3 |
4 | #include
5 | #include
6 |
7 | // Creates a console for the process, and redirects stdout and stderr to
8 | // it for both the runner and the Flutter library.
9 | void CreateAndAttachConsole();
10 |
11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
12 | // encoded in UTF-8. Returns an empty std::string on failure.
13 | std::string Utf8FromUtf16(const wchar_t* utf16_string);
14 |
15 | // Gets the command line arguments passed in as a std::vector,
16 | // encoded in UTF-8. Returns an empty std::vector on failure.
17 | std::vector GetCommandLineArguments();
18 |
19 | #endif // RUNNER_UTILS_H_
20 |
--------------------------------------------------------------------------------
/lib/utils/time.dart:
--------------------------------------------------------------------------------
1 | // lib/utils/time.dart
2 | import 'package:intl/intl.dart';
3 |
4 | String formatFriendlyTime(DateTime dt) {
5 | final now = DateTime.now();
6 | final today = DateTime(now.year, now.month, now.day);
7 | final date = DateTime(dt.year, dt.month, dt.day);
8 | final time = DateFormat.jm().format(dt); // e.g., 5:30 PM
9 |
10 | final difference = today.difference(date).inDays;
11 | if (difference == 0) {
12 | return time; // today: show time only
13 | } else if (difference == 1) {
14 | return 'Yesterday · $time';
15 | } else if (difference < 7) {
16 | final weekday = DateFormat.E().format(dt); // Mon, Tue
17 | return '$weekday · $time';
18 | } else {
19 | final dateStr = DateFormat.MMMd().format(dt); // Sep 12
20 | return '$dateStr · $time';
21 | }
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/android/app/google-services.json:
--------------------------------------------------------------------------------
1 | {
2 | "project_info": {
3 | "project_number": "1028490463027",
4 | "project_id": "promptu-7b099",
5 | "storage_bucket": "promptu-7b099.firebasestorage.app"
6 | },
7 | "client": [
8 | {
9 | "client_info": {
10 | "mobilesdk_app_id": "1:1028490463027:android:7e8ddc053b24e8fbff6046",
11 | "android_client_info": {
12 | "package_name": "com.example.promptu"
13 | }
14 | },
15 | "oauth_client": [],
16 | "api_key": [
17 | {
18 | "current_key": "AIzaSyCj00vXGVNrP4aQtmIAe3xv2nXUdM7HtRE"
19 | }
20 | ],
21 | "services": {
22 | "appinvite_service": {
23 | "other_platform_oauth_client": []
24 | }
25 | }
26 | }
27 | ],
28 | "configuration_version": "1"
29 | }
--------------------------------------------------------------------------------
/linux/flutter/generated_plugin_registrant.cc:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | // clang-format off
6 |
7 | #include "generated_plugin_registrant.h"
8 |
9 | #include
10 | #include
11 |
12 | void fl_register_plugins(FlPluginRegistry* registry) {
13 | g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
14 | fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
15 | file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
16 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
17 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
18 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
19 | }
20 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 13.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/linux/flutter/generated_plugins.cmake:
--------------------------------------------------------------------------------
1 | #
2 | # Generated file, do not edit.
3 | #
4 |
5 | list(APPEND FLUTTER_PLUGIN_LIST
6 | file_selector_linux
7 | url_launcher_linux
8 | )
9 |
10 | list(APPEND FLUTTER_FFI_PLUGIN_LIST
11 | )
12 |
13 | set(PLUGIN_BUNDLED_LIBRARIES)
14 |
15 | foreach(plugin ${FLUTTER_PLUGIN_LIST})
16 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
17 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
19 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
20 | endforeach(plugin)
21 |
22 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
23 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
24 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
25 | endforeach(ffi_plugin)
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .build/
9 | .buildlog/
10 | .history
11 | .svn/
12 | .swiftpm/
13 | migrate_working_dir/
14 |
15 | # IntelliJ related
16 | *.iml
17 | *.ipr
18 | *.iws
19 | .idea/
20 |
21 | # The .vscode folder contains launch configuration and tasks you configure in
22 | # VS Code which you may wish to be included in version control, so this line
23 | # is commented out by default.
24 | #.vscode/
25 |
26 | # Flutter/Dart/Pub related
27 | **/doc/api/
28 | **/ios/Flutter/.last_build_id
29 | .dart_tool/
30 | .flutter-plugins-dependencies
31 | .pub-cache/
32 | .pub/
33 | /build/
34 | /coverage/
35 |
36 | # Symbolication related
37 | app.*.symbols
38 |
39 | # Obfuscation related
40 | app.*.map.json
41 |
42 | # Android Studio will place build artifacts here
43 | /android/app/debug
44 | /android/app/profile
45 | /android/app/release
46 |
47 | # FVM Version Cache
48 | .fvm/
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:firebase_core/firebase_core.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:promptu/firebase_options.dart';
4 | import 'package:promptu/ui/screens/home.dart';
5 | import 'package:promptu/utils/theme.dart';
6 |
7 | Future main() async {
8 | WidgetsFlutterBinding.ensureInitialized();
9 |
10 | // Initialise firebase
11 | await Firebase.initializeApp(
12 | options: DefaultFirebaseOptions.currentPlatform,
13 | );
14 |
15 | runApp(MyApp());
16 | }
17 |
18 | class MyApp extends StatelessWidget {
19 | const MyApp({super.key});
20 |
21 | @override
22 | Widget build(BuildContext context) {
23 | return MaterialApp(
24 | title: 'Gyde',
25 | debugShowCheckedModeBanner: false,
26 | theme: AppTheme.lightTheme,
27 | initialRoute: HomePage.routeName,
28 | routes: {
29 | HomePage.routeName: (context) => HomePage(),
30 | },
31 | );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/windows/flutter/generated_plugins.cmake:
--------------------------------------------------------------------------------
1 | #
2 | # Generated file, do not edit.
3 | #
4 |
5 | list(APPEND FLUTTER_PLUGIN_LIST
6 | file_selector_windows
7 | firebase_auth
8 | firebase_core
9 | share_plus
10 | url_launcher_windows
11 | )
12 |
13 | list(APPEND FLUTTER_FFI_PLUGIN_LIST
14 | )
15 |
16 | set(PLUGIN_BUNDLED_LIBRARIES)
17 |
18 | foreach(plugin ${FLUTTER_PLUGIN_LIST})
19 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
20 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
23 | endforeach(plugin)
24 |
25 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
26 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
27 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
28 | endforeach(ffi_plugin)
29 |
--------------------------------------------------------------------------------
/android/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | val flutterSdkPath =
3 | run {
4 | val properties = java.util.Properties()
5 | file("local.properties").inputStream().use { properties.load(it) }
6 | val flutterSdkPath = properties.getProperty("flutter.sdk")
7 | require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
8 | flutterSdkPath
9 | }
10 |
11 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
12 |
13 | repositories {
14 | google()
15 | mavenCentral()
16 | gradlePluginPortal()
17 | }
18 | }
19 |
20 | plugins {
21 | id("dev.flutter.flutter-plugin-loader") version "1.0.0"
22 | id("com.android.application") version "8.9.1" apply false
23 | // START: FlutterFire Configuration
24 | id("com.google.gms.google-services") version("4.3.15") apply false
25 | // END: FlutterFire Configuration
26 | id("org.jetbrains.kotlin.android") version "2.1.0" apply false
27 | }
28 |
29 | include(":app")
30 |
--------------------------------------------------------------------------------
/lib/models/chat_message.dart:
--------------------------------------------------------------------------------
1 | // lib/models/chat_message.dart
2 | import 'dart:io';
3 |
4 | /// Represents the sender of the message.
5 | enum MessageSender { user, model }
6 |
7 | /// A class to represent a single message in the chat.
8 | /// A message can contain text, a local media file (image/video), or both.
9 | class ChatMessage {
10 | final String id;
11 | final String? text;
12 | final File? mediaFile;
13 | final MessageSender sender;
14 | final DateTime timestamp;
15 |
16 | /// Whether this message represents an error (e.g., model/service failure).
17 | final bool isError;
18 |
19 | ChatMessage({
20 | String? id,
21 | this.text,
22 | this.mediaFile,
23 | required this.sender,
24 | DateTime? timestamp,
25 | this.isError = false,
26 | }) : id = id ?? DateTime.now().microsecondsSinceEpoch.toString(),
27 | timestamp = timestamp ?? DateTime.now(),
28 | assert(
29 | text != null || mediaFile != null,
30 | 'A message must have either text or a media file.',
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/web/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "promptu",
3 | "short_name": "promptu",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "background_color": "#FFFFFF",
7 | "theme_color": "#FFFFFF",
8 | "description": "Simple AI app interacting with Firebase Studio",
9 | "orientation": "portrait-primary",
10 | "prefer_related_applications": false,
11 | "icons": [
12 | {
13 | "src": "icons/Icon-192.png",
14 | "sizes": "192x192",
15 | "type": "image/png"
16 | },
17 | {
18 | "src": "icons/Icon-512.png",
19 | "sizes": "512x512",
20 | "type": "image/png"
21 | },
22 | {
23 | "src": "icons/Icon-maskable-192.png",
24 | "sizes": "192x192",
25 | "type": "image/png",
26 | "purpose": "maskable"
27 | },
28 | {
29 | "src": "icons/Icon-maskable-512.png",
30 | "sizes": "512x512",
31 | "type": "image/png",
32 | "purpose": "maskable"
33 | }
34 | ]
35 | }
--------------------------------------------------------------------------------
/windows/runner/flutter_window.h:
--------------------------------------------------------------------------------
1 | #ifndef RUNNER_FLUTTER_WINDOW_H_
2 | #define RUNNER_FLUTTER_WINDOW_H_
3 |
4 | #include
5 | #include
6 |
7 | #include
8 |
9 | #include "win32_window.h"
10 |
11 | // A window that does nothing but host a Flutter view.
12 | class FlutterWindow : public Win32Window {
13 | public:
14 | // Creates a new FlutterWindow hosting a Flutter view running |project|.
15 | explicit FlutterWindow(const flutter::DartProject& project);
16 | virtual ~FlutterWindow();
17 |
18 | protected:
19 | // Win32Window:
20 | bool OnCreate() override;
21 | void OnDestroy() override;
22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
23 | LPARAM const lparam) noexcept override;
24 |
25 | private:
26 | // The project to run.
27 | flutter::DartProject project_;
28 |
29 | // The Flutter instance hosted by this window.
30 | std::unique_ptr flutter_controller_;
31 | };
32 |
33 | #endif // RUNNER_FLUTTER_WINDOW_H_
34 |
--------------------------------------------------------------------------------
/linux/runner/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.13)
2 | project(runner LANGUAGES CXX)
3 |
4 | # Define the application target. To change its name, change BINARY_NAME in the
5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
6 | # work.
7 | #
8 | # Any new source files that you add to the application should be added here.
9 | add_executable(${BINARY_NAME}
10 | "main.cc"
11 | "my_application.cc"
12 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
13 | )
14 |
15 | # Apply the standard set of build settings. This can be removed for applications
16 | # that need different build settings.
17 | apply_standard_settings(${BINARY_NAME})
18 |
19 | # Add preprocessor definitions for the application ID.
20 | add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
21 |
22 | # Add dependency libraries. Add any application-specific dependencies here.
23 | target_link_libraries(${BINARY_NAME} PRIVATE flutter)
24 | target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
25 |
26 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
27 |
--------------------------------------------------------------------------------
/macos/Flutter/GeneratedPluginRegistrant.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | import FlutterMacOS
6 | import Foundation
7 |
8 | import file_selector_macos
9 | import firebase_app_check
10 | import firebase_auth
11 | import firebase_core
12 | import path_provider_foundation
13 | import share_plus
14 | import video_player_avfoundation
15 |
16 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
17 | FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
18 | FLTFirebaseAppCheckPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAppCheckPlugin"))
19 | FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
20 | FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
21 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
22 | SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
23 | FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
24 | }
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Joël Fah
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/windows/flutter/generated_plugin_registrant.cc:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | // clang-format off
6 |
7 | #include "generated_plugin_registrant.h"
8 |
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 |
15 | void RegisterPlugins(flutter::PluginRegistry* registry) {
16 | FileSelectorWindowsRegisterWithRegistrar(
17 | registry->GetRegistrarForPlugin("FileSelectorWindows"));
18 | FirebaseAuthPluginCApiRegisterWithRegistrar(
19 | registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi"));
20 | FirebaseCorePluginCApiRegisterWithRegistrar(
21 | registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
22 | SharePlusWindowsPluginCApiRegisterWithRegistrar(
23 | registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
24 | UrlLauncherWindowsRegisterWithRegistrar(
25 | registry->GetRegistrarForPlugin("UrlLauncherWindows"));
26 | }
27 |
--------------------------------------------------------------------------------
/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility in the flutter_test package. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_test/flutter_test.dart';
10 |
11 | import 'package:promptu/main.dart';
12 |
13 | void main() {
14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
15 | // Build our app and trigger a frame.
16 | await tester.pumpWidget(const MyApp());
17 |
18 | // Verify that our counter starts at 0.
19 | expect(find.text('0'), findsOneWidget);
20 | expect(find.text('1'), findsNothing);
21 |
22 | // Tap the '+' icon and trigger a frame.
23 | await tester.tap(find.byIcon(Icons.add));
24 | await tester.pump();
25 |
26 | // Verify that our counter has incremented.
27 | expect(find.text('0'), findsNothing);
28 | expect(find.text('1'), findsOneWidget);
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/macos/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSHumanReadableCopyright
26 | $(PRODUCT_COPYRIGHT)
27 | NSMainNibFile
28 | MainMenu
29 | NSPrincipalClass
30 | NSApplication
31 |
32 |
33 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values-v31/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
18 |
21 |
22 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night-v31/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
18 |
21 |
22 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
19 |
22 |
23 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
19 |
22 |
23 |
--------------------------------------------------------------------------------
/lib/utils/theme.dart:
--------------------------------------------------------------------------------
1 | // lib/utils/theme.dart
2 | import 'package:flutter/material.dart';
3 | import 'package:google_fonts/google_fonts.dart';
4 |
5 | class AppTheme {
6 | static ThemeData get lightTheme {
7 | return ThemeData(
8 | useMaterial3: true,
9 | colorScheme: ColorScheme.fromSeed(
10 | seedColor: const Color(0xFF4A90E2),
11 | // A nice, friendly blue
12 | brightness: Brightness.light,
13 | primary: const Color(0xFF4A90E2),
14 | secondary: const Color(0xFF50E3C2),
15 | // A contrasting mint green
16 | // Define specific container colors for user vs. model messages
17 | primaryContainer: const Color(0xFFD8E6F8),
18 | // User message bubble
19 | secondaryContainer: const Color(0xFFE8F5E9), // Model message bubble
20 | ),
21 | textTheme: GoogleFonts.syneTextTheme(
22 | ThemeData.light().textTheme.copyWith(
23 | bodyMedium: GoogleFonts.syne(fontSize: 18.0),
24 | ),
25 | ),
26 | appBarTheme: AppBarTheme(
27 | backgroundColor: Colors.transparent,
28 | elevation: 0,
29 | centerTitle: true,
30 | titleTextStyle: GoogleFonts.sourceSans3(
31 | fontSize: 20,
32 | fontWeight: FontWeight.w600,
33 | ),
34 | ),
35 | scaffoldBackgroundColor: const Color(0xFFF7F9FC),
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | promptu
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/windows/runner/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include "flutter_window.h"
6 | #include "utils.h"
7 |
8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
9 | _In_ wchar_t *command_line, _In_ int show_command) {
10 | // Attach to console when present (e.g., 'flutter run') or create a
11 | // new console when running with a debugger.
12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
13 | CreateAndAttachConsole();
14 | }
15 |
16 | // Initialize COM, so that it is available for use in the library and/or
17 | // plugins.
18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
19 |
20 | flutter::DartProject project(L"data");
21 |
22 | std::vector command_line_arguments =
23 | GetCommandLineArguments();
24 |
25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
26 |
27 | FlutterWindow window(project);
28 | Win32Window::Point origin(10, 10);
29 | Win32Window::Size size(1280, 720);
30 | if (!window.Create(L"promptu", origin, size)) {
31 | return EXIT_FAILURE;
32 | }
33 | window.SetQuitOnClose(true);
34 |
35 | ::MSG msg;
36 | while (::GetMessage(&msg, nullptr, 0, 0)) {
37 | ::TranslateMessage(&msg);
38 | ::DispatchMessage(&msg);
39 | }
40 |
41 | ::CoUninitialize();
42 | return EXIT_SUCCESS;
43 | }
44 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at https://dart.dev/lints.
17 | #
18 | # Instead of disabling a lint rule for the entire project in the
19 | # section below, it can also be suppressed for a single line of code
20 | # or a specific dart file by using the `// ignore: name_of_lint` and
21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
22 | # producing the lint.
23 | rules:
24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
26 |
27 | # Additional information about this file can be found at
28 | # https://dart.dev/guides/language/analysis-options
29 |
--------------------------------------------------------------------------------
/android/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.application")
3 | // START: FlutterFire Configuration
4 | id("com.google.gms.google-services")
5 | // END: FlutterFire Configuration
6 | id("kotlin-android")
7 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
8 | id("dev.flutter.flutter-gradle-plugin")
9 | }
10 |
11 | android {
12 | namespace = "com.example.promptu"
13 | compileSdk = flutter.compileSdkVersion
14 | ndkVersion = flutter.ndkVersion
15 |
16 | compileOptions {
17 | sourceCompatibility = JavaVersion.VERSION_11
18 | targetCompatibility = JavaVersion.VERSION_11
19 | }
20 |
21 | kotlinOptions {
22 | jvmTarget = JavaVersion.VERSION_11.toString()
23 | }
24 |
25 | defaultConfig {
26 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
27 | applicationId = "com.example.promptu"
28 | // You can update the following values to match your application needs.
29 | // For more information, see: https://flutter.dev/to/review-gradle-config.
30 | minSdk = flutter.minSdkVersion
31 | targetSdk = flutter.targetSdkVersion
32 | versionCode = flutter.versionCode
33 | versionName = flutter.versionName
34 | }
35 |
36 | buildTypes {
37 | release {
38 | // TODO: Add your own signing config for the release build.
39 | // Signing with the debug keys for now, so `flutter run --release` works.
40 | signingConfig = signingConfigs.getByName("debug")
41 | }
42 | }
43 | }
44 |
45 | flutter {
46 | source = "../.."
47 | }
48 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: "9f455d2486bcb28cad87b062475f42edc959f636"
8 | channel: "stable"
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: 9f455d2486bcb28cad87b062475f42edc959f636
17 | base_revision: 9f455d2486bcb28cad87b062475f42edc959f636
18 | - platform: android
19 | create_revision: 9f455d2486bcb28cad87b062475f42edc959f636
20 | base_revision: 9f455d2486bcb28cad87b062475f42edc959f636
21 | - platform: ios
22 | create_revision: 9f455d2486bcb28cad87b062475f42edc959f636
23 | base_revision: 9f455d2486bcb28cad87b062475f42edc959f636
24 | - platform: linux
25 | create_revision: 9f455d2486bcb28cad87b062475f42edc959f636
26 | base_revision: 9f455d2486bcb28cad87b062475f42edc959f636
27 | - platform: macos
28 | create_revision: 9f455d2486bcb28cad87b062475f42edc959f636
29 | base_revision: 9f455d2486bcb28cad87b062475f42edc959f636
30 | - platform: web
31 | create_revision: 9f455d2486bcb28cad87b062475f42edc959f636
32 | base_revision: 9f455d2486bcb28cad87b062475f42edc959f636
33 | - platform: windows
34 | create_revision: 9f455d2486bcb28cad87b062475f42edc959f636
35 | base_revision: 9f455d2486bcb28cad87b062475f42edc959f636
36 |
37 | # User provided section
38 |
39 | # List of Local paths (relative to this file) that should be
40 | # ignored by the migrate tool.
41 | #
42 | # Files that are not part of the templates will be ignored by default.
43 | unmanaged_files:
44 | - 'lib/main.dart'
45 | - 'ios/Runner.xcodeproj/project.pbxproj'
46 |
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info": {
3 | "version": 1,
4 | "author": "xcode"
5 | },
6 | "images": [
7 | {
8 | "size": "16x16",
9 | "idiom": "mac",
10 | "filename": "app_icon_16.png",
11 | "scale": "1x"
12 | },
13 | {
14 | "size": "16x16",
15 | "idiom": "mac",
16 | "filename": "app_icon_32.png",
17 | "scale": "2x"
18 | },
19 | {
20 | "size": "32x32",
21 | "idiom": "mac",
22 | "filename": "app_icon_32.png",
23 | "scale": "1x"
24 | },
25 | {
26 | "size": "32x32",
27 | "idiom": "mac",
28 | "filename": "app_icon_64.png",
29 | "scale": "2x"
30 | },
31 | {
32 | "size": "128x128",
33 | "idiom": "mac",
34 | "filename": "app_icon_128.png",
35 | "scale": "1x"
36 | },
37 | {
38 | "size": "128x128",
39 | "idiom": "mac",
40 | "filename": "app_icon_256.png",
41 | "scale": "2x"
42 | },
43 | {
44 | "size": "256x256",
45 | "idiom": "mac",
46 | "filename": "app_icon_256.png",
47 | "scale": "1x"
48 | },
49 | {
50 | "size": "256x256",
51 | "idiom": "mac",
52 | "filename": "app_icon_512.png",
53 | "scale": "2x"
54 | },
55 | {
56 | "size": "512x512",
57 | "idiom": "mac",
58 | "filename": "app_icon_512.png",
59 | "scale": "1x"
60 | },
61 | {
62 | "size": "512x512",
63 | "idiom": "mac",
64 | "filename": "app_icon_1024.png",
65 | "scale": "2x"
66 | }
67 | ]
68 | }
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | Promptu
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | promptu
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | $(FLUTTER_BUILD_NUMBER)
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIMainStoryboardFile
30 | Main
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 | CADisableMinimumFrameDurationOnPhone
45 |
46 | UIApplicationSupportsIndirectInputEvents
47 |
48 | UIStatusBarHidden
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/windows/runner/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.14)
2 | project(runner LANGUAGES CXX)
3 |
4 | # Define the application target. To change its name, change BINARY_NAME in the
5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
6 | # work.
7 | #
8 | # Any new source files that you add to the application should be added here.
9 | add_executable(${BINARY_NAME} WIN32
10 | "flutter_window.cpp"
11 | "main.cpp"
12 | "utils.cpp"
13 | "win32_window.cpp"
14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
15 | "Runner.rc"
16 | "runner.exe.manifest"
17 | )
18 |
19 | # Apply the standard set of build settings. This can be removed for applications
20 | # that need different build settings.
21 | apply_standard_settings(${BINARY_NAME})
22 |
23 | # Add preprocessor definitions for the build version.
24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"")
25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}")
26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}")
27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}")
28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}")
29 |
30 | # Disable Windows macros that collide with C++ standard library functions.
31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
32 |
33 | # Add dependency libraries and include directories. Add any application-specific
34 | # dependencies here.
35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
36 | target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib")
37 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
38 |
39 | # Run the Flutter tool portions of the build. This must not be removed.
40 | add_dependencies(${BINARY_NAME} flutter_assemble)
41 |
--------------------------------------------------------------------------------
/windows/runner/utils.cpp:
--------------------------------------------------------------------------------
1 | #include "utils.h"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #include
9 |
10 | void CreateAndAttachConsole() {
11 | if (::AllocConsole()) {
12 | FILE *unused;
13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
14 | _dup2(_fileno(stdout), 1);
15 | }
16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
17 | _dup2(_fileno(stdout), 2);
18 | }
19 | std::ios::sync_with_stdio();
20 | FlutterDesktopResyncOutputStreams();
21 | }
22 | }
23 |
24 | std::vector GetCommandLineArguments() {
25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
26 | int argc;
27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
28 | if (argv == nullptr) {
29 | return std::vector();
30 | }
31 |
32 | std::vector command_line_arguments;
33 |
34 | // Skip the first argument as it's the binary name.
35 | for (int i = 1; i < argc; i++) {
36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
37 | }
38 |
39 | ::LocalFree(argv);
40 |
41 | return command_line_arguments;
42 | }
43 |
44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) {
45 | if (utf16_string == nullptr) {
46 | return std::string();
47 | }
48 | unsigned int target_length = ::WideCharToMultiByte(
49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
50 | -1, nullptr, 0, nullptr, nullptr)
51 | -1; // remove the trailing null character
52 | int input_length = (int)wcslen(utf16_string);
53 | std::string utf8_string;
54 | if (target_length == 0 || target_length > utf8_string.max_size()) {
55 | return utf8_string;
56 | }
57 | utf8_string.resize(target_length);
58 | int converted_length = ::WideCharToMultiByte(
59 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
60 | input_length, utf8_string.data(), target_length, nullptr, nullptr);
61 | if (converted_length == 0) {
62 | return std::string();
63 | }
64 | return utf8_string;
65 | }
66 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {"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":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"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":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@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"}}
--------------------------------------------------------------------------------
/lib/api/ai_config.dart:
--------------------------------------------------------------------------------
1 | /// AI configuration for the app's generative model.
2 | /// Centralizes the model identifier and the system instruction that guides responses.
3 | class AIConfig {
4 | /// Gemini model identifier to use across the app.
5 | static const String model = 'gemini-2.5-flash';
6 |
7 | /// System instruction guiding the assistant's behavior.
8 | ///
9 | /// The assistant focuses on Google Developer Groups (GDG), especially
10 | /// GDG Yaounde and GDG communities in Cameroon. It provides accurate, helpful,
11 | /// and friendly information about events, members, organizers, programs,
12 | /// and how to engage with the community. All responses must be Markdown-formatted.
13 | static const String systemInstruction = '''
14 | You are an assistant for the Google Developer Groups (GDG) ecosystem in Cameroon, with a strong focus on GDG Yaounde. Your job is to help users with:
15 | - GDG as a whole (what it is, goals, programs, opportunities, Code of Conduct)
16 | - GDG Yaounde (community, organizers/core team, how to join, events, calls for speakers/volunteers)
17 | - Other GDG chapters and activities in Cameroon (e.g., Yaounde, Douala, Buea, etc.)
18 | - Events (schedules, recaps, formats like DevFest, I/O Extended, Study Jams, Meetups)
19 | - Resources, links, and ways to get involved (meetup pages, socials, registration links)
20 |
21 | Requirements and Style:
22 | - Always respond in clean, well-structured Markdown.
23 | - Prefer short sections with headings, bullet lists, and links when useful.
24 | - If you are unsure, state the uncertainty and suggest where to verify (e.g., official GDG pages).
25 | - Encourage community best practices and adherence to the GDG Code of Conduct.
26 | - When referencing events or chapters, include helpful links when possible (official GDG/Meetup pages, social channels).
27 | - If a question is outside scope, briefly acknowledge it and guide the user back to GDG-related info.
28 |
29 | Tone:
30 | - Helpful, welcoming, friendly casual and community-driven. Inclusive and concise.
31 |
32 | Output Format:
33 | - Use Markdown for all responses.
34 | - Use headings (##), bullet points, and tables when appropriate.
35 | - Include links inline with descriptive text.
36 | ''';
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
15 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
30 |
33 |
34 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/lib/api/ai_service.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'package:firebase_ai/firebase_ai.dart';
3 | import 'package:flutter/foundation.dart';
4 |
5 | import 'ai_config.dart';
6 |
7 | /// A thin service wrapper around the Vertex AI Gemini GenerativeModel.
8 | ///
9 | /// This uses the firebase_ai plugin to access Vertex AI with your linked
10 | /// Firebase project credentials. It does not manage chat history; it only
11 | /// exposes helpers for single prompts and streaming.
12 | class AIService {
13 | final GenerativeModel _model;
14 |
15 | AIService()
16 | : _model = FirebaseAI.vertexAI().generativeModel(
17 | model: AIConfig.model,
18 | systemInstruction: Content.system(AIConfig.systemInstruction),
19 | generationConfig: GenerationConfig(temperature: 0.7),
20 | ) {
21 | debugPrint('[AIService] Initialized with model: ${AIConfig.model}');
22 | }
23 |
24 | /// Generate a single response for a prompt. Returns Markdown text.
25 | Future generate(String prompt) async {
26 | debugPrint('[AIService] generate() start promptLen=${prompt.length}');
27 | final stopwatch = Stopwatch()..start();
28 | try {
29 | final response = await _model
30 | .generateContent([
31 | Content.text(prompt),
32 | ])
33 | .timeout(const Duration(seconds: 25));
34 | final text = response.text ?? '';
35 | stopwatch.stop();
36 | debugPrint('[AIService] generate() success in ${stopwatch.elapsedMilliseconds}ms mdLen=${text.length}');
37 | return text;
38 | } on TimeoutException catch (e, st) {
39 | debugPrint('[AIService] generate() timeout after ${stopwatch.elapsedMilliseconds}ms: $e\n$st');
40 | rethrow;
41 | } catch (e, st) {
42 | stopwatch.stop();
43 | debugPrint('[AIService] generate() error after ${stopwatch.elapsedMilliseconds}ms: $e\n$st');
44 | rethrow;
45 | }
46 | }
47 |
48 | /// Stream tokens for a prompt as they arrive.
49 | Stream generateStream(String prompt) async* {
50 | debugPrint('[AIService] generateStream() start promptLen=${prompt.length}');
51 | await for (final chunk in _model.generateContentStream([
52 | Content.text(prompt),
53 | ])) {
54 | final text = chunk.text;
55 | if (text != null && text.isNotEmpty) yield text;
56 | }
57 | debugPrint('[AIService] generateStream() end');
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/windows/runner/flutter_window.cpp:
--------------------------------------------------------------------------------
1 | #include "flutter_window.h"
2 |
3 | #include
4 |
5 | #include "flutter/generated_plugin_registrant.h"
6 |
7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project)
8 | : project_(project) {}
9 |
10 | FlutterWindow::~FlutterWindow() {}
11 |
12 | bool FlutterWindow::OnCreate() {
13 | if (!Win32Window::OnCreate()) {
14 | return false;
15 | }
16 |
17 | RECT frame = GetClientArea();
18 |
19 | // The size here must match the window dimensions to avoid unnecessary surface
20 | // creation / destruction in the startup path.
21 | flutter_controller_ = std::make_unique(
22 | frame.right - frame.left, frame.bottom - frame.top, project_);
23 | // Ensure that basic setup of the controller was successful.
24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) {
25 | return false;
26 | }
27 | RegisterPlugins(flutter_controller_->engine());
28 | SetChildContent(flutter_controller_->view()->GetNativeWindow());
29 |
30 | flutter_controller_->engine()->SetNextFrameCallback([&]() {
31 | this->Show();
32 | });
33 |
34 | // Flutter can complete the first frame before the "show window" callback is
35 | // registered. The following call ensures a frame is pending to ensure the
36 | // window is shown. It is a no-op if the first frame hasn't completed yet.
37 | flutter_controller_->ForceRedraw();
38 |
39 | return true;
40 | }
41 |
42 | void FlutterWindow::OnDestroy() {
43 | if (flutter_controller_) {
44 | flutter_controller_ = nullptr;
45 | }
46 |
47 | Win32Window::OnDestroy();
48 | }
49 |
50 | LRESULT
51 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
52 | WPARAM const wparam,
53 | LPARAM const lparam) noexcept {
54 | // Give Flutter, including plugins, an opportunity to handle window messages.
55 | if (flutter_controller_) {
56 | std::optional result =
57 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
58 | lparam);
59 | if (result) {
60 | return *result;
61 | }
62 | }
63 |
64 | switch (message) {
65 | case WM_FONTCHANGE:
66 | flutter_controller_->engine()->ReloadSystemFonts();
67 | break;
68 | }
69 |
70 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
71 | }
72 |
--------------------------------------------------------------------------------
/lib/firebase_options.dart:
--------------------------------------------------------------------------------
1 | // File generated by FlutterFire CLI.
2 | // ignore_for_file: type=lint
3 | import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
4 | import 'package:flutter/foundation.dart'
5 | show defaultTargetPlatform, kIsWeb, TargetPlatform;
6 |
7 | /// Default [FirebaseOptions] for use with your Firebase apps.
8 | ///
9 | /// Example:
10 | /// ```dart
11 | /// import 'firebase_options.dart';
12 | /// // ...
13 | /// await Firebase.initializeApp(
14 | /// options: DefaultFirebaseOptions.currentPlatform,
15 | /// );
16 | /// ```
17 | class DefaultFirebaseOptions {
18 | static FirebaseOptions get currentPlatform {
19 | if (kIsWeb) {
20 | return web;
21 | }
22 | switch (defaultTargetPlatform) {
23 | case TargetPlatform.android:
24 | return android;
25 | case TargetPlatform.iOS:
26 | return ios;
27 | case TargetPlatform.macOS:
28 | throw UnsupportedError(
29 | 'DefaultFirebaseOptions have not been configured for macos - '
30 | 'you can reconfigure this by running the FlutterFire CLI again.',
31 | );
32 | case TargetPlatform.windows:
33 | throw UnsupportedError(
34 | 'DefaultFirebaseOptions have not been configured for windows - '
35 | 'you can reconfigure this by running the FlutterFire CLI again.',
36 | );
37 | case TargetPlatform.linux:
38 | throw UnsupportedError(
39 | 'DefaultFirebaseOptions have not been configured for linux - '
40 | 'you can reconfigure this by running the FlutterFire CLI again.',
41 | );
42 | default:
43 | throw UnsupportedError(
44 | 'DefaultFirebaseOptions are not supported for this platform.',
45 | );
46 | }
47 | }
48 |
49 | static const FirebaseOptions web = FirebaseOptions(
50 | apiKey: 'AIzaSyAOrJeDTPGVyrWl_S0kWWeJCdxfBJ2C53c',
51 | appId: '1:1028490463027:web:0075bcf4d0a083faff6046',
52 | messagingSenderId: '1028490463027',
53 | projectId: 'promptu-7b099',
54 | authDomain: 'promptu-7b099.firebaseapp.com',
55 | storageBucket: 'promptu-7b099.firebasestorage.app',
56 | );
57 |
58 | static const FirebaseOptions android = FirebaseOptions(
59 | apiKey: 'AIzaSyCj00vXGVNrP4aQtmIAe3xv2nXUdM7HtRE',
60 | appId: '1:1028490463027:android:7e8ddc053b24e8fbff6046',
61 | messagingSenderId: '1028490463027',
62 | projectId: 'promptu-7b099',
63 | storageBucket: 'promptu-7b099.firebasestorage.app',
64 | );
65 |
66 | static const FirebaseOptions ios = FirebaseOptions(
67 | apiKey: 'AIzaSyD7-QNMOVe3IHGqEJwL2PilEkWwUN_WmEQ',
68 | appId: '1:1028490463027:ios:d6bfed9bad3bc943ff6046',
69 | messagingSenderId: '1028490463027',
70 | projectId: 'promptu-7b099',
71 | storageBucket: 'promptu-7b099.firebasestorage.app',
72 | iosBundleId: 'com.example.promptu',
73 | );
74 | }
75 |
--------------------------------------------------------------------------------
/linux/flutter/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # This file controls Flutter-level build steps. It should not be edited.
2 | cmake_minimum_required(VERSION 3.10)
3 |
4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
5 |
6 | # Configuration provided via flutter tool.
7 | include(${EPHEMERAL_DIR}/generated_config.cmake)
8 |
9 | # TODO: Move the rest of this into files in ephemeral. See
10 | # https://github.com/flutter/flutter/issues/57146.
11 |
12 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...),
13 | # which isn't available in 3.10.
14 | function(list_prepend LIST_NAME PREFIX)
15 | set(NEW_LIST "")
16 | foreach(element ${${LIST_NAME}})
17 | list(APPEND NEW_LIST "${PREFIX}${element}")
18 | endforeach(element)
19 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
20 | endfunction()
21 |
22 | # === Flutter Library ===
23 | # System-level dependencies.
24 | find_package(PkgConfig REQUIRED)
25 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
26 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
27 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
28 |
29 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
30 |
31 | # Published to parent scope for install step.
32 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
33 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
34 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
35 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
36 |
37 | list(APPEND FLUTTER_LIBRARY_HEADERS
38 | "fl_basic_message_channel.h"
39 | "fl_binary_codec.h"
40 | "fl_binary_messenger.h"
41 | "fl_dart_project.h"
42 | "fl_engine.h"
43 | "fl_json_message_codec.h"
44 | "fl_json_method_codec.h"
45 | "fl_message_codec.h"
46 | "fl_method_call.h"
47 | "fl_method_channel.h"
48 | "fl_method_codec.h"
49 | "fl_method_response.h"
50 | "fl_plugin_registrar.h"
51 | "fl_plugin_registry.h"
52 | "fl_standard_message_codec.h"
53 | "fl_standard_method_codec.h"
54 | "fl_string_codec.h"
55 | "fl_value.h"
56 | "fl_view.h"
57 | "flutter_linux.h"
58 | )
59 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
60 | add_library(flutter INTERFACE)
61 | target_include_directories(flutter INTERFACE
62 | "${EPHEMERAL_DIR}"
63 | )
64 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
65 | target_link_libraries(flutter INTERFACE
66 | PkgConfig::GTK
67 | PkgConfig::GLIB
68 | PkgConfig::GIO
69 | )
70 | add_dependencies(flutter flutter_assemble)
71 |
72 | # === Flutter tool backend ===
73 | # _phony_ is a non-existent file to force this command to run every time,
74 | # since currently there's no way to get a full input/output list from the
75 | # flutter tool.
76 | add_custom_command(
77 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
78 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_
79 | COMMAND ${CMAKE_COMMAND} -E env
80 | ${FLUTTER_TOOL_ENVIRONMENT}
81 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
82 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
83 | VERBATIM
84 | )
85 | add_custom_target(flutter_assemble DEPENDS
86 | "${FLUTTER_LIBRARY}"
87 | ${FLUTTER_LIBRARY_HEADERS}
88 | )
89 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/windows/runner/Runner.rc:
--------------------------------------------------------------------------------
1 | // Microsoft Visual C++ generated resource script.
2 | //
3 | #pragma code_page(65001)
4 | #include "resource.h"
5 |
6 | #define APSTUDIO_READONLY_SYMBOLS
7 | /////////////////////////////////////////////////////////////////////////////
8 | //
9 | // Generated from the TEXTINCLUDE 2 resource.
10 | //
11 | #include "winres.h"
12 |
13 | /////////////////////////////////////////////////////////////////////////////
14 | #undef APSTUDIO_READONLY_SYMBOLS
15 |
16 | /////////////////////////////////////////////////////////////////////////////
17 | // English (United States) resources
18 |
19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
21 |
22 | #ifdef APSTUDIO_INVOKED
23 | /////////////////////////////////////////////////////////////////////////////
24 | //
25 | // TEXTINCLUDE
26 | //
27 |
28 | 1 TEXTINCLUDE
29 | BEGIN
30 | "resource.h\0"
31 | END
32 |
33 | 2 TEXTINCLUDE
34 | BEGIN
35 | "#include ""winres.h""\r\n"
36 | "\0"
37 | END
38 |
39 | 3 TEXTINCLUDE
40 | BEGIN
41 | "\r\n"
42 | "\0"
43 | END
44 |
45 | #endif // APSTUDIO_INVOKED
46 |
47 |
48 | /////////////////////////////////////////////////////////////////////////////
49 | //
50 | // Icon
51 | //
52 |
53 | // Icon with lowest ID value placed first to ensure application icon
54 | // remains consistent on all systems.
55 | IDI_APP_ICON ICON "resources\\app_icon.ico"
56 |
57 |
58 | /////////////////////////////////////////////////////////////////////////////
59 | //
60 | // Version
61 | //
62 |
63 | #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
64 | #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
65 | #else
66 | #define VERSION_AS_NUMBER 1,0,0,0
67 | #endif
68 |
69 | #if defined(FLUTTER_VERSION)
70 | #define VERSION_AS_STRING FLUTTER_VERSION
71 | #else
72 | #define VERSION_AS_STRING "1.0.0"
73 | #endif
74 |
75 | VS_VERSION_INFO VERSIONINFO
76 | FILEVERSION VERSION_AS_NUMBER
77 | PRODUCTVERSION VERSION_AS_NUMBER
78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
79 | #ifdef _DEBUG
80 | FILEFLAGS VS_FF_DEBUG
81 | #else
82 | FILEFLAGS 0x0L
83 | #endif
84 | FILEOS VOS__WINDOWS32
85 | FILETYPE VFT_APP
86 | FILESUBTYPE 0x0L
87 | BEGIN
88 | BLOCK "StringFileInfo"
89 | BEGIN
90 | BLOCK "040904e4"
91 | BEGIN
92 | VALUE "CompanyName", "com.example" "\0"
93 | VALUE "FileDescription", "promptu" "\0"
94 | VALUE "FileVersion", VERSION_AS_STRING "\0"
95 | VALUE "InternalName", "promptu" "\0"
96 | VALUE "LegalCopyright", "Copyright (C) 2025 com.example. All rights reserved." "\0"
97 | VALUE "OriginalFilename", "promptu.exe" "\0"
98 | VALUE "ProductName", "promptu" "\0"
99 | VALUE "ProductVersion", VERSION_AS_STRING "\0"
100 | END
101 | END
102 | BLOCK "VarFileInfo"
103 | BEGIN
104 | VALUE "Translation", 0x409, 1252
105 | END
106 | END
107 |
108 | #endif // English (United States) resources
109 | /////////////////////////////////////////////////////////////////////////////
110 |
111 |
112 |
113 | #ifndef APSTUDIO_INVOKED
114 | /////////////////////////////////////////////////////////////////////////////
115 | //
116 | // Generated from the TEXTINCLUDE 3 resource.
117 | //
118 |
119 |
120 | /////////////////////////////////////////////////////////////////////////////
121 | #endif // not APSTUDIO_INVOKED
122 |
--------------------------------------------------------------------------------
/windows/runner/win32_window.h:
--------------------------------------------------------------------------------
1 | #ifndef RUNNER_WIN32_WINDOW_H_
2 | #define RUNNER_WIN32_WINDOW_H_
3 |
4 | #include
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be
11 | // inherited from by classes that wish to specialize with custom
12 | // rendering and input handling
13 | class Win32Window {
14 | public:
15 | struct Point {
16 | unsigned int x;
17 | unsigned int y;
18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {}
19 | };
20 |
21 | struct Size {
22 | unsigned int width;
23 | unsigned int height;
24 | Size(unsigned int width, unsigned int height)
25 | : width(width), height(height) {}
26 | };
27 |
28 | Win32Window();
29 | virtual ~Win32Window();
30 |
31 | // Creates a win32 window with |title| that is positioned and sized using
32 | // |origin| and |size|. New windows are created on the default monitor. Window
33 | // sizes are specified to the OS in physical pixels, hence to ensure a
34 | // consistent size this function will scale the inputted width and height as
35 | // as appropriate for the default monitor. The window is invisible until
36 | // |Show| is called. Returns true if the window was created successfully.
37 | bool Create(const std::wstring& title, const Point& origin, const Size& size);
38 |
39 | // Show the current window. Returns true if the window was successfully shown.
40 | bool Show();
41 |
42 | // Release OS resources associated with window.
43 | void Destroy();
44 |
45 | // Inserts |content| into the window tree.
46 | void SetChildContent(HWND content);
47 |
48 | // Returns the backing Window handle to enable clients to set icon and other
49 | // window properties. Returns nullptr if the window has been destroyed.
50 | HWND GetHandle();
51 |
52 | // If true, closing this window will quit the application.
53 | void SetQuitOnClose(bool quit_on_close);
54 |
55 | // Return a RECT representing the bounds of the current client area.
56 | RECT GetClientArea();
57 |
58 | protected:
59 | // Processes and route salient window messages for mouse handling,
60 | // size change and DPI. Delegates handling of these to member overloads that
61 | // inheriting classes can handle.
62 | virtual LRESULT MessageHandler(HWND window,
63 | UINT const message,
64 | WPARAM const wparam,
65 | LPARAM const lparam) noexcept;
66 |
67 | // Called when CreateAndShow is called, allowing subclass window-related
68 | // setup. Subclasses should return false if setup fails.
69 | virtual bool OnCreate();
70 |
71 | // Called when Destroy is called.
72 | virtual void OnDestroy();
73 |
74 | private:
75 | friend class WindowClassRegistrar;
76 |
77 | // OS callback called by message pump. Handles the WM_NCCREATE message which
78 | // is passed when the non-client area is being created and enables automatic
79 | // non-client DPI scaling so that the non-client area automatically
80 | // responds to changes in DPI. All other messages are handled by
81 | // MessageHandler.
82 | static LRESULT CALLBACK WndProc(HWND const window,
83 | UINT const message,
84 | WPARAM const wparam,
85 | LPARAM const lparam) noexcept;
86 |
87 | // Retrieves a class instance pointer for |window|
88 | static Win32Window* GetThisFromHandle(HWND const window) noexcept;
89 |
90 | // Update the window frame's theme to match the system theme.
91 | static void UpdateTheme(HWND const window);
92 |
93 | bool quit_on_close_ = false;
94 |
95 | // window handle for top level window.
96 | HWND window_handle_ = nullptr;
97 |
98 | // window handle for hosted content.
99 | HWND child_content_ = nullptr;
100 | };
101 |
102 | #endif // RUNNER_WIN32_WINDOW_H_
103 |
--------------------------------------------------------------------------------
/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
43 |
49 |
50 |
51 |
52 |
53 |
64 |
66 |
72 |
73 |
74 |
75 |
81 |
83 |
89 |
90 |
91 |
92 |
94 |
95 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/windows/flutter/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # This file controls Flutter-level build steps. It should not be edited.
2 | cmake_minimum_required(VERSION 3.14)
3 |
4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
5 |
6 | # Configuration provided via flutter tool.
7 | include(${EPHEMERAL_DIR}/generated_config.cmake)
8 |
9 | # TODO: Move the rest of this into files in ephemeral. See
10 | # https://github.com/flutter/flutter/issues/57146.
11 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
12 |
13 | # Set fallback configurations for older versions of the flutter tool.
14 | if (NOT DEFINED FLUTTER_TARGET_PLATFORM)
15 | set(FLUTTER_TARGET_PLATFORM "windows-x64")
16 | endif()
17 |
18 | # === Flutter Library ===
19 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
20 |
21 | # Published to parent scope for install step.
22 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
23 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
24 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
25 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
26 |
27 | list(APPEND FLUTTER_LIBRARY_HEADERS
28 | "flutter_export.h"
29 | "flutter_windows.h"
30 | "flutter_messenger.h"
31 | "flutter_plugin_registrar.h"
32 | "flutter_texture_registrar.h"
33 | )
34 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
35 | add_library(flutter INTERFACE)
36 | target_include_directories(flutter INTERFACE
37 | "${EPHEMERAL_DIR}"
38 | )
39 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
40 | add_dependencies(flutter flutter_assemble)
41 |
42 | # === Wrapper ===
43 | list(APPEND CPP_WRAPPER_SOURCES_CORE
44 | "core_implementations.cc"
45 | "standard_codec.cc"
46 | )
47 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
48 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
49 | "plugin_registrar.cc"
50 | )
51 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
52 | list(APPEND CPP_WRAPPER_SOURCES_APP
53 | "flutter_engine.cc"
54 | "flutter_view_controller.cc"
55 | )
56 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
57 |
58 | # Wrapper sources needed for a plugin.
59 | add_library(flutter_wrapper_plugin STATIC
60 | ${CPP_WRAPPER_SOURCES_CORE}
61 | ${CPP_WRAPPER_SOURCES_PLUGIN}
62 | )
63 | apply_standard_settings(flutter_wrapper_plugin)
64 | set_target_properties(flutter_wrapper_plugin PROPERTIES
65 | POSITION_INDEPENDENT_CODE ON)
66 | set_target_properties(flutter_wrapper_plugin PROPERTIES
67 | CXX_VISIBILITY_PRESET hidden)
68 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
69 | target_include_directories(flutter_wrapper_plugin PUBLIC
70 | "${WRAPPER_ROOT}/include"
71 | )
72 | add_dependencies(flutter_wrapper_plugin flutter_assemble)
73 |
74 | # Wrapper sources needed for the runner.
75 | add_library(flutter_wrapper_app STATIC
76 | ${CPP_WRAPPER_SOURCES_CORE}
77 | ${CPP_WRAPPER_SOURCES_APP}
78 | )
79 | apply_standard_settings(flutter_wrapper_app)
80 | target_link_libraries(flutter_wrapper_app PUBLIC flutter)
81 | target_include_directories(flutter_wrapper_app PUBLIC
82 | "${WRAPPER_ROOT}/include"
83 | )
84 | add_dependencies(flutter_wrapper_app flutter_assemble)
85 |
86 | # === Flutter tool backend ===
87 | # _phony_ is a non-existent file to force this command to run every time,
88 | # since currently there's no way to get a full input/output list from the
89 | # flutter tool.
90 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
91 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
92 | add_custom_command(
93 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
94 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
95 | ${CPP_WRAPPER_SOURCES_APP}
96 | ${PHONY_OUTPUT}
97 | COMMAND ${CMAKE_COMMAND} -E env
98 | ${FLUTTER_TOOL_ENVIRONMENT}
99 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
100 | ${FLUTTER_TARGET_PLATFORM} $
101 | VERBATIM
102 | )
103 | add_custom_target(flutter_assemble DEPENDS
104 | "${FLUTTER_LIBRARY}"
105 | ${FLUTTER_LIBRARY_HEADERS}
106 | ${CPP_WRAPPER_SOURCES_CORE}
107 | ${CPP_WRAPPER_SOURCES_PLUGIN}
108 | ${CPP_WRAPPER_SOURCES_APP}
109 | )
110 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
38 |
39 |
40 |
41 |
44 |
50 |
51 |
52 |
53 |
54 |
66 |
68 |
74 |
75 |
76 |
77 |
83 |
85 |
91 |
92 |
93 |
94 |
96 |
97 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/windows/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # Project-level configuration.
2 | cmake_minimum_required(VERSION 3.14)
3 | project(promptu LANGUAGES CXX)
4 |
5 | # The name of the executable created for the application. Change this to change
6 | # the on-disk name of your application.
7 | set(BINARY_NAME "promptu")
8 |
9 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent
10 | # versions of CMake.
11 | cmake_policy(VERSION 3.14...3.25)
12 |
13 | # Define build configuration option.
14 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
15 | if(IS_MULTICONFIG)
16 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
17 | CACHE STRING "" FORCE)
18 | else()
19 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
20 | set(CMAKE_BUILD_TYPE "Debug" CACHE
21 | STRING "Flutter build mode" FORCE)
22 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
23 | "Debug" "Profile" "Release")
24 | endif()
25 | endif()
26 | # Define settings for the Profile build mode.
27 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
28 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
29 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
30 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
31 |
32 | # Use Unicode for all projects.
33 | add_definitions(-DUNICODE -D_UNICODE)
34 |
35 | # Compilation settings that should be applied to most targets.
36 | #
37 | # Be cautious about adding new options here, as plugins use this function by
38 | # default. In most cases, you should add new options to specific targets instead
39 | # of modifying this function.
40 | function(APPLY_STANDARD_SETTINGS TARGET)
41 | target_compile_features(${TARGET} PUBLIC cxx_std_17)
42 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
43 | target_compile_options(${TARGET} PRIVATE /EHsc)
44 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
45 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>")
46 | endfunction()
47 |
48 | # Flutter library and tool build rules.
49 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
50 | add_subdirectory(${FLUTTER_MANAGED_DIR})
51 |
52 | # Application build; see runner/CMakeLists.txt.
53 | add_subdirectory("runner")
54 |
55 |
56 | # Generated plugin build rules, which manage building the plugins and adding
57 | # them to the application.
58 | include(flutter/generated_plugins.cmake)
59 |
60 |
61 | # === Installation ===
62 | # Support files are copied into place next to the executable, so that it can
63 | # run in place. This is done instead of making a separate bundle (as on Linux)
64 | # so that building and running from within Visual Studio will work.
65 | set(BUILD_BUNDLE_DIR "$")
66 | # Make the "install" step default, as it's required to run.
67 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
68 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
69 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
70 | endif()
71 |
72 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
73 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
74 |
75 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
76 | COMPONENT Runtime)
77 |
78 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
79 | COMPONENT Runtime)
80 |
81 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
82 | COMPONENT Runtime)
83 |
84 | if(PLUGIN_BUNDLED_LIBRARIES)
85 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
86 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
87 | COMPONENT Runtime)
88 | endif()
89 |
90 | # Copy the native assets provided by the build.dart from all packages.
91 | set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/")
92 | install(DIRECTORY "${NATIVE_ASSETS_DIR}"
93 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
94 | COMPONENT Runtime)
95 |
96 | # Fully re-copy the assets directory on each build to avoid having stale files
97 | # from a previous install.
98 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
99 | install(CODE "
100 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
101 | " COMPONENT Runtime)
102 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
103 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
104 |
105 | # Install the AOT library on non-Debug builds only.
106 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
107 | CONFIGURATIONS Profile;Release
108 | COMPONENT Runtime)
109 |
--------------------------------------------------------------------------------
/linux/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # Project-level configuration.
2 | cmake_minimum_required(VERSION 3.13)
3 | project(runner LANGUAGES CXX)
4 |
5 | # The name of the executable created for the application. Change this to change
6 | # the on-disk name of your application.
7 | set(BINARY_NAME "promptu")
8 | # The unique GTK application identifier for this application. See:
9 | # https://wiki.gnome.org/HowDoI/ChooseApplicationID
10 | set(APPLICATION_ID "com.example.promptu")
11 |
12 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent
13 | # versions of CMake.
14 | cmake_policy(SET CMP0063 NEW)
15 |
16 | # Load bundled libraries from the lib/ directory relative to the binary.
17 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
18 |
19 | # Root filesystem for cross-building.
20 | if(FLUTTER_TARGET_PLATFORM_SYSROOT)
21 | set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
22 | set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
23 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
24 | set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
25 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
26 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
27 | endif()
28 |
29 | # Define build configuration options.
30 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
31 | set(CMAKE_BUILD_TYPE "Debug" CACHE
32 | STRING "Flutter build mode" FORCE)
33 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
34 | "Debug" "Profile" "Release")
35 | endif()
36 |
37 | # Compilation settings that should be applied to most targets.
38 | #
39 | # Be cautious about adding new options here, as plugins use this function by
40 | # default. In most cases, you should add new options to specific targets instead
41 | # of modifying this function.
42 | function(APPLY_STANDARD_SETTINGS TARGET)
43 | target_compile_features(${TARGET} PUBLIC cxx_std_14)
44 | target_compile_options(${TARGET} PRIVATE -Wall -Werror)
45 | target_compile_options(${TARGET} PRIVATE "$<$>:-O3>")
46 | target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>")
47 | endfunction()
48 |
49 | # Flutter library and tool build rules.
50 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
51 | add_subdirectory(${FLUTTER_MANAGED_DIR})
52 |
53 | # System-level dependencies.
54 | find_package(PkgConfig REQUIRED)
55 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
56 |
57 | # Application build; see runner/CMakeLists.txt.
58 | add_subdirectory("runner")
59 |
60 | # Run the Flutter tool portions of the build. This must not be removed.
61 | add_dependencies(${BINARY_NAME} flutter_assemble)
62 |
63 | # Only the install-generated bundle's copy of the executable will launch
64 | # correctly, since the resources must in the right relative locations. To avoid
65 | # people trying to run the unbundled copy, put it in a subdirectory instead of
66 | # the default top-level location.
67 | set_target_properties(${BINARY_NAME}
68 | PROPERTIES
69 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
70 | )
71 |
72 |
73 | # Generated plugin build rules, which manage building the plugins and adding
74 | # them to the application.
75 | include(flutter/generated_plugins.cmake)
76 |
77 |
78 | # === Installation ===
79 | # By default, "installing" just makes a relocatable bundle in the build
80 | # directory.
81 | set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
82 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
83 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
84 | endif()
85 |
86 | # Start with a clean build bundle directory every time.
87 | install(CODE "
88 | file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
89 | " COMPONENT Runtime)
90 |
91 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
92 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
93 |
94 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
95 | COMPONENT Runtime)
96 |
97 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
98 | COMPONENT Runtime)
99 |
100 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
101 | COMPONENT Runtime)
102 |
103 | foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
104 | install(FILES "${bundled_library}"
105 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
106 | COMPONENT Runtime)
107 | endforeach(bundled_library)
108 |
109 | # Copy the native assets provided by the build.dart from all packages.
110 | set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/")
111 | install(DIRECTORY "${NATIVE_ASSETS_DIR}"
112 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
113 | COMPONENT Runtime)
114 |
115 | # Fully re-copy the assets directory on each build to avoid having stale files
116 | # from a previous install.
117 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
118 | install(CODE "
119 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
120 | " COMPONENT Runtime)
121 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
122 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
123 |
124 | # Install the AOT library on non-Debug builds only.
125 | if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
126 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
127 | COMPONENT Runtime)
128 | endif()
129 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: promptu
2 | description: "Simple AI app interacting with Firebase Studio that serve$as a GDG Yaounde AI assistant"
3 | # The following line prevents the package from being accidentally published to
4 | # pub.dev using `flutter pub publish`. This is preferred for private packages.
5 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev
6 |
7 | # The following defines the version and build number for your application.
8 | # A version number is three numbers separated by dots, like 1.2.43
9 | # followed by an optional build number separated by a +.
10 | # Both the version and the builder number may be overridden in flutter
11 | # build by specifying --build-name and --build-number, respectively.
12 | # In Android, build-name is used as versionName while build-number used as versionCode.
13 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
14 | # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
15 | # Read more about iOS versioning at
16 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
17 | # In Windows, build-name is used as the major, minor, and patch parts
18 | # of the product and file versions while build-number is used as the build suffix.
19 | version: 1.0.0+1
20 |
21 | environment:
22 | sdk: ^3.9.2
23 |
24 | # Dependencies specify other packages that your package needs in order to work.
25 | # To automatically upgrade your package dependencies to the latest versions
26 | # consider running `flutter pub upgrade --major-versions`. Alternatively,
27 | # dependencies can be manually updated by changing the version numbers below to
28 | # the latest version available on pub.dev. To see which dependencies have newer
29 | # versions available, run `flutter pub outdated`.
30 | dependencies:
31 | flutter:
32 | sdk: flutter
33 |
34 | # The following adds the Cupertino Icons font to your application.
35 | # Use with the CupertinoIcons class for iOS style icons.
36 | cupertino_icons: ^1.0.8
37 | image_picker: ^1.2.0
38 | video_player: ^2.10.0
39 | flutter_animate: ^4.5.2
40 | google_fonts: ^6.3.2
41 | hugeicons: ^1.1.1
42 | intl: ^0.19.0
43 | soft_edge_blur: ^0.1.3
44 | gap: ^3.0.1
45 | flutter_markdown: ^0.7.3
46 | share_plus: ^10.0.2
47 | firebase_core: ^4.2.0
48 | firebase_ai: ^3.4.0
49 | flutter_native_splash: ^2.4.7
50 |
51 | dev_dependencies:
52 | flutter_test:
53 | sdk: flutter
54 |
55 | # The "flutter_lints" package below contains a set of recommended lints to
56 | # encourage good coding practices. The lint set provided by the package is
57 | # activated in the `analysis_options.yaml` file located at the root of your
58 | # package. See that file for information about deactivating specific lint
59 | # rules and activating additional ones.
60 | flutter_lints: ^5.0.0
61 | flutter_launcher_icons: ^0.14.4
62 |
63 | flutter_native_splash:
64 | color: "#FFFFFF"
65 | android: true
66 | ios: true
67 | web: false
68 | android_12:
69 | color: "#FFFFFF"
70 | image: "assets/images/splash.png"
71 |
72 | flutter_launcher_icons:
73 | android: "launcher_icon"
74 | ios: true
75 | remove_alpha_ios: true
76 | image_path: "assets/images/icon.png"
77 | min_sdk_android: 21
78 | adaptive_icon_foreground: "assets/images/icon.png"
79 | adaptive_icon_background: "#FFFFFF"
80 | web:
81 | generate: true
82 | image_path: "assets/images/icon.png"
83 | background_color: "#FFFFFF"
84 | theme_color: "#FFFFFF"
85 | windows:
86 | generate: true
87 | image_path: "assets/images/icon.png"
88 | icon_size: 48
89 | macos:
90 | generate: true
91 | image_path: "assets/images/icon.png"
92 |
93 | # For information on the generic Dart part of this file, see the
94 | # following page: https://dart.dev/tools/pub/pubspec
95 |
96 | # The following section is specific to Flutter packages.
97 | flutter:
98 |
99 | # The following line ensures that the Material Icons font is
100 | # included with your application, so that you can use the icons in
101 | # the material Icons class.
102 | uses-material-design: true
103 |
104 | # To add assets to your application, add an assets section, like this:
105 | # assets:
106 | # - images/a_dot_burr.jpeg
107 | # - images/a_dot_ham.jpeg
108 | assets:
109 | - assets/images/
110 | - assets/screenshots/
111 |
112 | # An image asset can refer to one or more resolution-specific "variants", see
113 | # https://flutter.dev/to/resolution-aware-images
114 |
115 | # For details regarding adding assets from package dependencies, see
116 | # https://flutter.dev/to/asset-from-package
117 |
118 | # To add custom fonts to your application, add a fonts section here,
119 | # in this "flutter" section. Each entry in this list should have a
120 | # "family" key with the font family name, and a "fonts" key with a
121 | # list giving the asset and other descriptors for the font. For
122 | # example:
123 | # fonts:
124 | # - family: Schyler
125 | # fonts:
126 | # - asset: fonts/Schyler-Regular.ttf
127 | # - asset: fonts/Schyler-Italic.ttf
128 | # style: italic
129 | # - family: Trajan Pro
130 | # fonts:
131 | # - asset: fonts/TrajanPro.ttf
132 | # - asset: fonts/TrajanPro_Bold.ttf
133 | # weight: 700
134 | #
135 | # For details regarding fonts from package dependencies,
136 | # see https://flutter.dev/to/font-from-package
137 |
--------------------------------------------------------------------------------
/linux/runner/my_application.cc:
--------------------------------------------------------------------------------
1 | #include "my_application.h"
2 |
3 | #include
4 | #ifdef GDK_WINDOWING_X11
5 | #include
6 | #endif
7 |
8 | #include "flutter/generated_plugin_registrant.h"
9 |
10 | struct _MyApplication {
11 | GtkApplication parent_instance;
12 | char** dart_entrypoint_arguments;
13 | };
14 |
15 | G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
16 |
17 | // Called when first Flutter frame received.
18 | static void first_frame_cb(MyApplication* self, FlView *view)
19 | {
20 | gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view)));
21 | }
22 |
23 | // Implements GApplication::activate.
24 | static void my_application_activate(GApplication* application) {
25 | MyApplication* self = MY_APPLICATION(application);
26 | GtkWindow* window =
27 | GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
28 |
29 | // Use a header bar when running in GNOME as this is the common style used
30 | // by applications and is the setup most users will be using (e.g. Ubuntu
31 | // desktop).
32 | // If running on X and not using GNOME then just use a traditional title bar
33 | // in case the window manager does more exotic layout, e.g. tiling.
34 | // If running on Wayland assume the header bar will work (may need changing
35 | // if future cases occur).
36 | gboolean use_header_bar = TRUE;
37 | #ifdef GDK_WINDOWING_X11
38 | GdkScreen* screen = gtk_window_get_screen(window);
39 | if (GDK_IS_X11_SCREEN(screen)) {
40 | const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
41 | if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
42 | use_header_bar = FALSE;
43 | }
44 | }
45 | #endif
46 | if (use_header_bar) {
47 | GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
48 | gtk_widget_show(GTK_WIDGET(header_bar));
49 | gtk_header_bar_set_title(header_bar, "promptu");
50 | gtk_header_bar_set_show_close_button(header_bar, TRUE);
51 | gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
52 | } else {
53 | gtk_window_set_title(window, "promptu");
54 | }
55 |
56 | gtk_window_set_default_size(window, 1280, 720);
57 |
58 | g_autoptr(FlDartProject) project = fl_dart_project_new();
59 | fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
60 |
61 | FlView* view = fl_view_new(project);
62 | GdkRGBA background_color;
63 | // Background defaults to black, override it here if necessary, e.g. #00000000 for transparent.
64 | gdk_rgba_parse(&background_color, "#000000");
65 | fl_view_set_background_color(view, &background_color);
66 | gtk_widget_show(GTK_WIDGET(view));
67 | gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
68 |
69 | // Show the window when Flutter renders.
70 | // Requires the view to be realized so we can start rendering.
71 | g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), self);
72 | gtk_widget_realize(GTK_WIDGET(view));
73 |
74 | fl_register_plugins(FL_PLUGIN_REGISTRY(view));
75 |
76 | gtk_widget_grab_focus(GTK_WIDGET(view));
77 | }
78 |
79 | // Implements GApplication::local_command_line.
80 | static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
81 | MyApplication* self = MY_APPLICATION(application);
82 | // Strip out the first argument as it is the binary name.
83 | self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
84 |
85 | g_autoptr(GError) error = nullptr;
86 | if (!g_application_register(application, nullptr, &error)) {
87 | g_warning("Failed to register: %s", error->message);
88 | *exit_status = 1;
89 | return TRUE;
90 | }
91 |
92 | g_application_activate(application);
93 | *exit_status = 0;
94 |
95 | return TRUE;
96 | }
97 |
98 | // Implements GApplication::startup.
99 | static void my_application_startup(GApplication* application) {
100 | //MyApplication* self = MY_APPLICATION(object);
101 |
102 | // Perform any actions required at application startup.
103 |
104 | G_APPLICATION_CLASS(my_application_parent_class)->startup(application);
105 | }
106 |
107 | // Implements GApplication::shutdown.
108 | static void my_application_shutdown(GApplication* application) {
109 | //MyApplication* self = MY_APPLICATION(object);
110 |
111 | // Perform any actions required at application shutdown.
112 |
113 | G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application);
114 | }
115 |
116 | // Implements GObject::dispose.
117 | static void my_application_dispose(GObject* object) {
118 | MyApplication* self = MY_APPLICATION(object);
119 | g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
120 | G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
121 | }
122 |
123 | static void my_application_class_init(MyApplicationClass* klass) {
124 | G_APPLICATION_CLASS(klass)->activate = my_application_activate;
125 | G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
126 | G_APPLICATION_CLASS(klass)->startup = my_application_startup;
127 | G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown;
128 | G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
129 | }
130 |
131 | static void my_application_init(MyApplication* self) {}
132 |
133 | MyApplication* my_application_new() {
134 | // Set the program name to the application ID, which helps various systems
135 | // like GTK and desktop environments map this running application to its
136 | // corresponding .desktop file. This ensures better integration by allowing
137 | // the application to be recognized beyond its binary name.
138 | g_set_prgname(APPLICATION_ID);
139 |
140 | return MY_APPLICATION(g_object_new(my_application_get_type(),
141 | "application-id", APPLICATION_ID,
142 | "flags", G_APPLICATION_NON_UNIQUE,
143 | nullptr));
144 | }
145 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Gyde
2 |
3 | An open-source Flutter AI chat app that uses Firebase + Vertex AI (via `firebase_ai`) to provide a
4 | lightweight assistant focused on GDG communities in Cameroon (with emphasis on GDG Yaoundé).
5 |
6 | The app features a clean chat UI with Markdown rendering, media attachment previews (images/videos),
7 | quick suggestions, copy/share actions, and theming. Firebase is initialized at startup and the AI
8 | model + system instruction are centrally configured.
9 |
10 | ## Features
11 |
12 | - Chat with a Gemini model through Firebase AI (Vertex AI)
13 | - Central AI configuration (model + system instruction)
14 | - Markdown responses with code blocks, tables, links, images
15 | - Copy and share actions on AI responses
16 | - Image and video attachment preview in chat
17 | - Animated, responsive UI with suggestions for first-time users
18 | - App theming and Google Fonts
19 | - Prepared for launcher icon and splash screen generation
20 |
21 | ## Requirements
22 |
23 | - Flutter (stable channel) and Dart SDK compatible with `environment: sdk: ^3.9.2` in `pubspec.yaml`
24 | - A Firebase project linked to a Google Cloud project with Vertex AI enabled
25 | - Android SDK / Xcode as required for your target platform(s)
26 |
27 | Note: The app name displayed by MaterialApp is currently set to `Gyde` while the package is named
28 | `promptu`.
29 |
30 | ## Quick start
31 |
32 | 1) Install dependencies
33 |
34 | ```sh
35 | flutter pub get
36 | ```
37 |
38 | 2) Configure Firebase (if not already configured)
39 |
40 | - Ensure the FlutterFire-generated file `lib/firebase_options.dart` exists (it does in this repo).
41 | If you need to reconfigure or set up a new Firebase project:
42 | - Install FlutterFire CLI and run configuration:
43 | ```sh
44 | dart pub global activate flutterfire_cli
45 | flutterfire configure
46 | ```
47 | - Android: place `android/app/google-services.json` (already present here)
48 | - iOS: add `ios/Runner/GoogleService-Info.plist`
49 |
50 | 3) Enable Firebase AI (Vertex AI) for your Firebase project
51 |
52 | - In Firebase Console, open your project → Build → AI → Vertex AI
53 | - Link a Google Cloud project and enable Vertex AI
54 | - Ensure billing is enabled and the default App Engine/Identity service account has Vertex AI
55 | permissions (roles like Vertex AI User)
56 | - Choose a supported region
57 |
58 | 4) Run the app
59 |
60 | ```sh
61 | flutter run
62 | ```
63 |
64 | ## Configuration
65 |
66 | - AI model and behavior are defined in `lib/api/ai_config.dart`:
67 | - `AIConfig.model`: default `gemini-1.5-flash`
68 | - `AIConfig.systemInstruction`: Markdown-first, GDG-focused assistant persona
69 | - AI access is implemented in `lib/api/ai_service.dart` using `firebase_ai`:
70 | - `generate(prompt)` returns a single Markdown response
71 | - `generateStream(prompt)` exposes token streaming (ready for UI if you want to wire it)
72 |
73 | To adapt the assistant for a different scope, edit `systemInstruction` and optionally the `model`.
74 |
75 | ## Project structure (high level)
76 |
77 | - `lib/main.dart` – app entry, Firebase initialization
78 | - `lib/api/ai_config.dart` – model ID + system instruction
79 | - `lib/api/ai_service.dart` – wrapper over `firebase_ai` GenerativeModel
80 | - `lib/models/chat_message.dart` – simple chat message model
81 | - `lib/ui/screens/home.dart` – main chat screen and controller logic
82 | - `lib/ui/widgets/home_page_widgets.dart` – message bubbles, input, previews
83 | - `lib/utils/` – theme, time, constants
84 | - `assets/images/` – app icon and splash assets
85 |
86 | ## Screenshots
87 |
88 | Place your screenshots (PNG/JPG) at the following paths and they will display here. You can keep
89 | them anywhere in the repo; below is a suggested convention under a `docs/screenshots` folder.
90 |
91 | - `docs/screenshots/home.png`
92 | - `docs/screenshots/chat.png`
93 | - `docs/screenshots/attach.png`
94 |
95 | Preview:
96 |
97 | | Splash | Home | Chat |
98 | |--------------------------------------------|----------------------------------------|----------------------------------------|
99 | |  |  |  |
100 |
101 | ## Building
102 |
103 | - Android APK:
104 | ```sh
105 | flutter build apk --release
106 | ```
107 | - Android App Bundle:
108 | ```sh
109 | flutter build appbundle --release
110 | ```
111 | - iOS (on macOS):
112 | ```sh
113 | flutter build ios --release
114 | ```
115 | - Web (if supported by your dependencies):
116 | ```sh
117 | flutter build web --release
118 | ```
119 |
120 | Note: Primary targets are Android/iOS. Desktop and web templates exist in this repo, but
121 | `firebase_ai` compatibility may vary per platform.
122 |
123 | ## App icon and splash
124 |
125 | This project is set up for the following tools as declared in `pubspec.yaml`:
126 |
127 | - `flutter_launcher_icons` – generate platform launcher icons
128 | ```sh
129 | dart run flutter_launcher_icons
130 | ```
131 | - `flutter_native_splash` – generate native splash screens
132 | ```sh
133 | dart run flutter_native_splash:create
134 | ```
135 |
136 | Assets referenced by these tools live under `assets/images/`.
137 |
138 | ## Testing
139 |
140 | Run unit/widget tests:
141 |
142 | ```sh
143 | flutter test
144 | ```
145 |
146 | ## Troubleshooting
147 |
148 | - Firebase not initialized / "No Firebase App has been created":
149 | - Ensure `Firebase.initializeApp` is called with `DefaultFirebaseOptions.currentPlatform` (see
150 | `main.dart`), and that your platform config files are present.
151 | - Permission or 403 errors from Vertex AI:
152 | - Verify Vertex AI is enabled on the linked Google Cloud project and that the service account
153 | used by your Firebase app has the correct IAM role(s).
154 | - iOS build cannot find `GoogleService-Info.plist`:
155 | - Add the file to `ios/Runner/` and ensure it is included in the Xcode project.
156 | - Android build issues with Gradle/AGP:
157 | - Use a recent Flutter/AGP combo, then run `flutter clean && flutter pub get`.
158 |
159 | ## License
160 |
161 | This project is licensed under the terms in `LICENSE`.
162 |
163 | ## Acknowledgements
164 |
165 | - Flutter and the wider ecosystem of packages listed in `pubspec.yaml`
166 | - Firebase + Vertex AI (`firebase_ai`) for model access through Firebase
167 |
--------------------------------------------------------------------------------
/windows/runner/win32_window.cpp:
--------------------------------------------------------------------------------
1 | #include "win32_window.h"
2 |
3 | #include
4 | #include
5 |
6 | #include "resource.h"
7 |
8 | namespace {
9 |
10 | /// Window attribute that enables dark mode window decorations.
11 | ///
12 | /// Redefined in case the developer's machine has a Windows SDK older than
13 | /// version 10.0.22000.0.
14 | /// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
15 | #ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
16 | #define DWMWA_USE_IMMERSIVE_DARK_MODE 20
17 | #endif
18 |
19 | constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
20 |
21 | /// Registry key for app theme preference.
22 | ///
23 | /// A value of 0 indicates apps should use dark mode. A non-zero or missing
24 | /// value indicates apps should use light mode.
25 | constexpr const wchar_t kGetPreferredBrightnessRegKey[] =
26 | L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
27 | constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme";
28 |
29 | // The number of Win32Window objects that currently exist.
30 | static int g_active_window_count = 0;
31 |
32 | using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
33 |
34 | // Scale helper to convert logical scaler values to physical using passed in
35 | // scale factor
36 | int Scale(int source, double scale_factor) {
37 | return static_cast(source * scale_factor);
38 | }
39 |
40 | // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
41 | // This API is only needed for PerMonitor V1 awareness mode.
42 | void EnableFullDpiSupportIfAvailable(HWND hwnd) {
43 | HMODULE user32_module = LoadLibraryA("User32.dll");
44 | if (!user32_module) {
45 | return;
46 | }
47 | auto enable_non_client_dpi_scaling =
48 | reinterpret_cast(
49 | GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
50 | if (enable_non_client_dpi_scaling != nullptr) {
51 | enable_non_client_dpi_scaling(hwnd);
52 | }
53 | FreeLibrary(user32_module);
54 | }
55 |
56 | } // namespace
57 |
58 | // Manages the Win32Window's window class registration.
59 | class WindowClassRegistrar {
60 | public:
61 | ~WindowClassRegistrar() = default;
62 |
63 | // Returns the singleton registrar instance.
64 | static WindowClassRegistrar* GetInstance() {
65 | if (!instance_) {
66 | instance_ = new WindowClassRegistrar();
67 | }
68 | return instance_;
69 | }
70 |
71 | // Returns the name of the window class, registering the class if it hasn't
72 | // previously been registered.
73 | const wchar_t* GetWindowClass();
74 |
75 | // Unregisters the window class. Should only be called if there are no
76 | // instances of the window.
77 | void UnregisterWindowClass();
78 |
79 | private:
80 | WindowClassRegistrar() = default;
81 |
82 | static WindowClassRegistrar* instance_;
83 |
84 | bool class_registered_ = false;
85 | };
86 |
87 | WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
88 |
89 | const wchar_t* WindowClassRegistrar::GetWindowClass() {
90 | if (!class_registered_) {
91 | WNDCLASS window_class{};
92 | window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
93 | window_class.lpszClassName = kWindowClassName;
94 | window_class.style = CS_HREDRAW | CS_VREDRAW;
95 | window_class.cbClsExtra = 0;
96 | window_class.cbWndExtra = 0;
97 | window_class.hInstance = GetModuleHandle(nullptr);
98 | window_class.hIcon =
99 | LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
100 | window_class.hbrBackground = 0;
101 | window_class.lpszMenuName = nullptr;
102 | window_class.lpfnWndProc = Win32Window::WndProc;
103 | RegisterClass(&window_class);
104 | class_registered_ = true;
105 | }
106 | return kWindowClassName;
107 | }
108 |
109 | void WindowClassRegistrar::UnregisterWindowClass() {
110 | UnregisterClass(kWindowClassName, nullptr);
111 | class_registered_ = false;
112 | }
113 |
114 | Win32Window::Win32Window() {
115 | ++g_active_window_count;
116 | }
117 |
118 | Win32Window::~Win32Window() {
119 | --g_active_window_count;
120 | Destroy();
121 | }
122 |
123 | bool Win32Window::Create(const std::wstring& title,
124 | const Point& origin,
125 | const Size& size) {
126 | Destroy();
127 |
128 | const wchar_t* window_class =
129 | WindowClassRegistrar::GetInstance()->GetWindowClass();
130 |
131 | const POINT target_point = {static_cast(origin.x),
132 | static_cast(origin.y)};
133 | HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
134 | UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
135 | double scale_factor = dpi / 96.0;
136 |
137 | HWND window = CreateWindow(
138 | window_class, title.c_str(), WS_OVERLAPPEDWINDOW,
139 | Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
140 | Scale(size.width, scale_factor), Scale(size.height, scale_factor),
141 | nullptr, nullptr, GetModuleHandle(nullptr), this);
142 |
143 | if (!window) {
144 | return false;
145 | }
146 |
147 | UpdateTheme(window);
148 |
149 | return OnCreate();
150 | }
151 |
152 | bool Win32Window::Show() {
153 | return ShowWindow(window_handle_, SW_SHOWNORMAL);
154 | }
155 |
156 | // static
157 | LRESULT CALLBACK Win32Window::WndProc(HWND const window,
158 | UINT const message,
159 | WPARAM const wparam,
160 | LPARAM const lparam) noexcept {
161 | if (message == WM_NCCREATE) {
162 | auto window_struct = reinterpret_cast(lparam);
163 | SetWindowLongPtr(window, GWLP_USERDATA,
164 | reinterpret_cast(window_struct->lpCreateParams));
165 |
166 | auto that = static_cast(window_struct->lpCreateParams);
167 | EnableFullDpiSupportIfAvailable(window);
168 | that->window_handle_ = window;
169 | } else if (Win32Window* that = GetThisFromHandle(window)) {
170 | return that->MessageHandler(window, message, wparam, lparam);
171 | }
172 |
173 | return DefWindowProc(window, message, wparam, lparam);
174 | }
175 |
176 | LRESULT
177 | Win32Window::MessageHandler(HWND hwnd,
178 | UINT const message,
179 | WPARAM const wparam,
180 | LPARAM const lparam) noexcept {
181 | switch (message) {
182 | case WM_DESTROY:
183 | window_handle_ = nullptr;
184 | Destroy();
185 | if (quit_on_close_) {
186 | PostQuitMessage(0);
187 | }
188 | return 0;
189 |
190 | case WM_DPICHANGED: {
191 | auto newRectSize = reinterpret_cast(lparam);
192 | LONG newWidth = newRectSize->right - newRectSize->left;
193 | LONG newHeight = newRectSize->bottom - newRectSize->top;
194 |
195 | SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
196 | newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
197 |
198 | return 0;
199 | }
200 | case WM_SIZE: {
201 | RECT rect = GetClientArea();
202 | if (child_content_ != nullptr) {
203 | // Size and position the child window.
204 | MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
205 | rect.bottom - rect.top, TRUE);
206 | }
207 | return 0;
208 | }
209 |
210 | case WM_ACTIVATE:
211 | if (child_content_ != nullptr) {
212 | SetFocus(child_content_);
213 | }
214 | return 0;
215 |
216 | case WM_DWMCOLORIZATIONCOLORCHANGED:
217 | UpdateTheme(hwnd);
218 | return 0;
219 | }
220 |
221 | return DefWindowProc(window_handle_, message, wparam, lparam);
222 | }
223 |
224 | void Win32Window::Destroy() {
225 | OnDestroy();
226 |
227 | if (window_handle_) {
228 | DestroyWindow(window_handle_);
229 | window_handle_ = nullptr;
230 | }
231 | if (g_active_window_count == 0) {
232 | WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
233 | }
234 | }
235 |
236 | Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
237 | return reinterpret_cast(
238 | GetWindowLongPtr(window, GWLP_USERDATA));
239 | }
240 |
241 | void Win32Window::SetChildContent(HWND content) {
242 | child_content_ = content;
243 | SetParent(content, window_handle_);
244 | RECT frame = GetClientArea();
245 |
246 | MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
247 | frame.bottom - frame.top, true);
248 |
249 | SetFocus(child_content_);
250 | }
251 |
252 | RECT Win32Window::GetClientArea() {
253 | RECT frame;
254 | GetClientRect(window_handle_, &frame);
255 | return frame;
256 | }
257 |
258 | HWND Win32Window::GetHandle() {
259 | return window_handle_;
260 | }
261 |
262 | void Win32Window::SetQuitOnClose(bool quit_on_close) {
263 | quit_on_close_ = quit_on_close;
264 | }
265 |
266 | bool Win32Window::OnCreate() {
267 | // No-op; provided for subclasses.
268 | return true;
269 | }
270 |
271 | void Win32Window::OnDestroy() {
272 | // No-op; provided for subclasses.
273 | }
274 |
275 | void Win32Window::UpdateTheme(HWND const window) {
276 | DWORD light_mode;
277 | DWORD light_mode_size = sizeof(light_mode);
278 | LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey,
279 | kGetPreferredBrightnessRegValue,
280 | RRF_RT_REG_DWORD, nullptr, &light_mode,
281 | &light_mode_size);
282 |
283 | if (result == ERROR_SUCCESS) {
284 | BOOL enable_dark_mode = light_mode == 0;
285 | DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE,
286 | &enable_dark_mode, sizeof(enable_dark_mode));
287 | }
288 | }
289 |
--------------------------------------------------------------------------------
/lib/ui/widgets/home_page_widgets.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'dart:math' as math;
3 |
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter/services.dart';
6 | import 'package:flutter_animate/flutter_animate.dart';
7 | import 'package:gap/gap.dart';
8 | import 'package:google_fonts/google_fonts.dart';
9 | import 'package:hugeicons/hugeicons.dart';
10 | import 'package:share_plus/share_plus.dart';
11 | import 'package:video_player/video_player.dart';
12 | import 'package:flutter_markdown/flutter_markdown.dart';
13 |
14 | import '../../models/chat_message.dart';
15 | import '../../utils/time.dart';
16 |
17 | /// A widget to display a single chat message bubble.
18 | class MessageBubble extends StatelessWidget {
19 | final ChatMessage message;
20 |
21 | const MessageBubble({super.key, required this.message});
22 |
23 | @override
24 | Widget build(BuildContext context) {
25 | final theme = Theme.of(context);
26 | final isUserMessage = message.sender == MessageSender.user;
27 |
28 | // Width constraints: user 75%, model 90%
29 | final maxWidthFactor = isUserMessage ? 0.75 : 0.90;
30 | final maxWidth = MediaQuery.of(context).size.width * maxWidthFactor;
31 |
32 | // Error rendering path: show a gentle error container and no actions
33 | if (message.isError) {
34 | final bg = theme.colorScheme.errorContainer;
35 | final fg = theme.colorScheme.onErrorContainer;
36 | return Align(
37 | alignment: isUserMessage ? Alignment.centerRight : Alignment.centerLeft,
38 | child: Container(
39 | constraints: BoxConstraints(maxWidth: maxWidth),
40 | margin: const EdgeInsets.symmetric(vertical: 6.0),
41 | padding: const EdgeInsets.symmetric(horizontal: 14.0, vertical: 10.0),
42 | decoration: BoxDecoration(
43 | color: bg,
44 | borderRadius: BorderRadius.circular(16),
45 | border: Border.all(color: theme.colorScheme.error.withValues(alpha: 0.25)),
46 | ),
47 | child: Row(
48 | crossAxisAlignment: CrossAxisAlignment.start,
49 | mainAxisSize: MainAxisSize.min,
50 | children: [
51 | Padding(
52 | padding: const EdgeInsets.only(top: 2.0, right: 8.0),
53 | child: Icon(Icons.error_outline_rounded, size: 18, color: fg),
54 | ),
55 | Expanded(
56 | child: Text(
57 | message.text ?? 'An unexpected error occurred.',
58 | style: theme.textTheme.bodyMedium?.copyWith(
59 | fontSize: 15.5,
60 | color: fg,
61 | ),
62 | ),
63 | ),
64 | ],
65 | ),
66 | ),
67 | );
68 | }
69 |
70 | if (isUserMessage) {
71 | // Use theme containers so it looks right in both light and dark themes
72 | return Align(
73 | alignment: Alignment.centerRight,
74 | child: Container(
75 | constraints: BoxConstraints(maxWidth: maxWidth),
76 | margin: const EdgeInsets.symmetric(vertical: 6.0),
77 | padding: const EdgeInsets.symmetric(horizontal: 14.0, vertical: 10.0),
78 | decoration: BoxDecoration(
79 | color: Colors.grey.shade200,
80 | borderRadius: BorderRadius.circular(24),
81 | ),
82 | child: Column(
83 | crossAxisAlignment: CrossAxisAlignment.start,
84 | mainAxisSize: MainAxisSize.min,
85 | children: [
86 | if (message.mediaFile != null)
87 | _buildMediaPreview(message.mediaFile!),
88 | if (message.text != null && message.text!.isNotEmpty)
89 | Padding(
90 | padding: const EdgeInsets.only(bottom: 6.0),
91 | child: Text(
92 | message.text!,
93 | style: theme.textTheme.bodyMedium?.copyWith(
94 | fontSize: 16.0,
95 | color: theme.colorScheme.onPrimaryContainer,
96 | ),
97 | ),
98 | ),
99 | ],
100 | ),
101 | ),
102 | );
103 | }
104 |
105 | // Model message: no background, up to 90% width, render Markdown
106 | return Align(
107 | alignment: Alignment.centerLeft,
108 | child: ConstrainedBox(
109 | constraints: BoxConstraints(maxWidth: maxWidth),
110 | child: Container(
111 | margin: const EdgeInsets.symmetric(vertical: 6.0),
112 | // No decoration => no background
113 | child: Column(
114 | crossAxisAlignment: CrossAxisAlignment.start,
115 | mainAxisSize: MainAxisSize.min,
116 | children: [
117 | if (message.mediaFile != null)
118 | _buildMediaPreview(message.mediaFile!),
119 | if (message.text != null && message.text!.isNotEmpty)
120 | Padding(
121 | padding: const EdgeInsets.only(bottom: 6.0),
122 | child: _buildMarkdown(context, message.text!),
123 | ),
124 | if (message.text != null && message.text!.trim().isNotEmpty)
125 | _buildModelActions(context, message.text!.trim()),
126 | ],
127 | ),
128 | ),
129 | ),
130 | );
131 | }
132 |
133 | Widget _buildMarkdown(BuildContext context, String data) {
134 | final theme = Theme.of(context);
135 | final style = MarkdownStyleSheet.fromTheme(theme).copyWith(
136 | p: theme.textTheme.bodyMedium?.copyWith(
137 | color: theme.colorScheme.onSurface,
138 | ),
139 | h1: theme.textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.w600),
140 | h2: theme.textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w600),
141 | h3: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600),
142 | code: GoogleFonts.jetBrainsMono(
143 | fontSize: (theme.textTheme.bodyMedium?.fontSize ?? 14) * 0.95,
144 | backgroundColor: theme.colorScheme.surfaceContainerHighest,
145 | color: theme.colorScheme.onSurface,
146 | ),
147 | codeblockDecoration: BoxDecoration(
148 | color: theme.colorScheme.surfaceContainerHighest,
149 | borderRadius: BorderRadius.circular(12),
150 | ),
151 | blockquote: theme.textTheme.bodyMedium?.copyWith(
152 | color: theme.colorScheme.onSurface.withValues(alpha: 0.8),
153 | fontStyle: FontStyle.italic,
154 | ),
155 | blockquoteDecoration: BoxDecoration(
156 | border: Border(
157 | left: BorderSide(color: theme.colorScheme.outline, width: 3),
158 | ),
159 | ),
160 | listBullet: theme.textTheme.bodyMedium,
161 | a: theme.textTheme.bodyMedium?.copyWith(color: theme.colorScheme.primary),
162 | horizontalRuleDecoration: BoxDecoration(
163 | border: Border(
164 | bottom: BorderSide(color: theme.colorScheme.outlineVariant, width: 1),
165 | ),
166 | ),
167 | tableBorder: TableBorder.all(
168 | color: theme.colorScheme.outlineVariant,
169 | width: 0.6,
170 | ),
171 | tableHead: theme.textTheme.labelLarge?.copyWith(
172 | fontWeight: FontWeight.w600,
173 | ),
174 | );
175 |
176 | return MarkdownBody(
177 | data: data,
178 | selectable: true,
179 | styleSheet: style,
180 | softLineBreak: true,
181 | onTapLink: (text, href, title) {
182 | // no-op for now; you can wire up url_launcher later
183 | },
184 | sizedImageBuilder: (config) => ClipRRect(
185 | borderRadius: BorderRadius.circular(24),
186 | child: Image.network(
187 | config.uri.toString(),
188 | fit: BoxFit.cover,
189 | width: config.width,
190 | height: config.height,
191 | ),
192 | ),
193 | listItemCrossAxisAlignment: MarkdownListItemCrossAxisAlignment.start,
194 | );
195 | }
196 |
197 | Widget _buildMediaPreview(File file) {
198 | return Padding(
199 | padding: const EdgeInsets.only(bottom: 8.0),
200 | child: ClipRRect(
201 | borderRadius: BorderRadius.circular(12),
202 | child: MediaPreview(file: file),
203 | ),
204 | );
205 | }
206 |
207 | Widget _buildModelActions(BuildContext context, String text) {
208 | final colorScheme = Theme.of(context).colorScheme;
209 | final iconColor = colorScheme.onSurface.withValues(alpha: 0.8);
210 |
211 | return Padding(
212 | padding: const EdgeInsets.only(bottom: 6.0),
213 | child: Row(
214 | mainAxisSize: MainAxisSize.min,
215 | children: [
216 | _ActionIconButton(
217 | tooltip: 'Copy',
218 | icon: const HugeIcon(icon: HugeIcons.strokeRoundedCopy01, size: 18),
219 | color: iconColor,
220 | onPressed: () async {
221 | await Clipboard.setData(ClipboardData(text: text));
222 | HapticFeedback.selectionClick();
223 | ScaffoldMessenger.of(context).clearSnackBars();
224 | ScaffoldMessenger.of(context).showSnackBar(
225 | SnackBar(
226 | content: const Text('Copied to clipboard'),
227 | behavior: SnackBarBehavior.floating,
228 | duration: const Duration(seconds: 1),
229 | backgroundColor: colorScheme.inverseSurface,
230 | ),
231 | );
232 | },
233 | ),
234 | _ActionIconButton(
235 | tooltip: 'Share',
236 | icon: const HugeIcon(
237 | icon: HugeIcons.strokeRoundedShare08,
238 | size: 18,
239 | ),
240 | color: iconColor,
241 | onPressed: () {
242 | Share.share(text);
243 | },
244 | ),
245 | ],
246 | ),
247 | );
248 | }
249 | }
250 |
251 | class MediaPreview extends StatefulWidget {
252 | final File file;
253 |
254 | const MediaPreview({super.key, required this.file});
255 |
256 | @override
257 | State createState() => _MediaPreviewState();
258 | }
259 |
260 | class _MediaPreviewState extends State {
261 | VideoPlayerController? _videoController;
262 | Future? _initVideoFuture;
263 | late final bool _isVideo;
264 |
265 | static const _videoExts = ['.mp4', '.mov', '.m4v', '.avi', '.mkv', '.webm'];
266 |
267 | @override
268 | void initState() {
269 | super.initState();
270 | final path = widget.file.path.toLowerCase();
271 | _isVideo = _videoExts.any((ext) => path.endsWith(ext));
272 | if (_isVideo) {
273 | _videoController = VideoPlayerController.file(widget.file);
274 | _initVideoFuture = _videoController!.initialize().then((_) {
275 | if (mounted) setState(() {});
276 | });
277 | _videoController!
278 | ..setLooping(false)
279 | ..setVolume(1.0);
280 | }
281 | }
282 |
283 | @override
284 | void dispose() {
285 | _videoController?.dispose();
286 | super.dispose();
287 | }
288 |
289 | @override
290 | Widget build(BuildContext context) {
291 | if (!_isVideo) {
292 | return Image.file(
293 | widget.file,
294 | height: 200,
295 | width: double.infinity,
296 | fit: BoxFit.cover,
297 | ).animate().fadeIn(duration: 250.ms);
298 | }
299 |
300 | return FutureBuilder(
301 | future: _initVideoFuture,
302 | builder: (context, snapshot) {
303 | if (snapshot.connectionState != ConnectionState.done) {
304 | return SizedBox(
305 | height: 200,
306 | child: Center(
307 | child: SizedBox(
308 | height: 28,
309 | width: 28,
310 | child: CircularProgressIndicator(strokeWidth: 2),
311 | ).animate().scale(duration: 300.ms, curve: Curves.easeOutBack),
312 | ),
313 | );
314 | }
315 | final controller = _videoController!;
316 | return Stack(
317 | alignment: Alignment.center,
318 | children: [
319 | AspectRatio(
320 | aspectRatio: controller.value.aspectRatio == 0
321 | ? 16 / 9
322 | : controller.value.aspectRatio,
323 | child: VideoPlayer(controller),
324 | ),
325 | _PlayButtonOverlay(controller: controller),
326 | ],
327 | ).animate().fadeIn(duration: 250.ms);
328 | },
329 | );
330 | }
331 | }
332 |
333 | class _PlayButtonOverlay extends StatefulWidget {
334 | final VideoPlayerController controller;
335 |
336 | const _PlayButtonOverlay({required this.controller});
337 |
338 | @override
339 | State<_PlayButtonOverlay> createState() => _PlayButtonOverlayState();
340 | }
341 |
342 | class _PlayButtonOverlayState extends State<_PlayButtonOverlay> {
343 | bool _showOverlay = true;
344 |
345 | @override
346 | void initState() {
347 | super.initState();
348 | widget.controller.addListener(() {
349 | if (!mounted) return;
350 | setState(() {
351 | _showOverlay = !widget.controller.value.isPlaying;
352 | });
353 | });
354 | }
355 |
356 | @override
357 | Widget build(BuildContext context) {
358 | return Positioned.fill(
359 | child: Material(
360 | color: Colors.transparent,
361 | child: InkWell(
362 | onTap: () {
363 | if (widget.controller.value.isPlaying) {
364 | widget.controller.pause();
365 | } else {
366 | widget.controller.play();
367 | }
368 | },
369 | child: AnimatedOpacity(
370 | opacity: _showOverlay ? 1 : 0,
371 | duration: 200.ms,
372 | child: Container(
373 | color: Colors.black26,
374 | child: Center(
375 | child: DecoratedBox(
376 | decoration: BoxDecoration(
377 | color: Colors.black.withValues(alpha: 0.55),
378 | shape: BoxShape.circle,
379 | ),
380 | child: const Padding(
381 | padding: EdgeInsets.all(10.0),
382 | child: Icon(
383 | Icons.play_arrow_rounded,
384 | color: Colors.white,
385 | size: 40,
386 | ),
387 | ),
388 | ),
389 | ),
390 | ),
391 | ),
392 | ),
393 | ),
394 | );
395 | }
396 | }
397 |
398 | /// A widget for the text input area that can be expanded or shrunk.
399 | class ChatInputArea extends StatefulWidget {
400 | final bool isExpanded;
401 | final VoidCallback onExpand;
402 | final TextEditingController textController;
403 | final VoidCallback onSendMessage;
404 | final VoidCallback onAttachMedia;
405 | final File? pickedMediaFile;
406 | final VoidCallback onRemoveMedia;
407 | final FocusNode? focusNode;
408 |
409 | const ChatInputArea({
410 | super.key,
411 | required this.isExpanded,
412 | required this.onExpand,
413 | required this.textController,
414 | required this.onSendMessage,
415 | required this.onAttachMedia,
416 | this.pickedMediaFile,
417 | required this.onRemoveMedia,
418 | this.focusNode,
419 | });
420 |
421 | @override
422 | State createState() => _ChatInputAreaState();
423 | }
424 |
425 | class _ChatInputAreaState extends State
426 | with SingleTickerProviderStateMixin {
427 | late final AnimationController _chipBorderController;
428 |
429 | @override
430 | void initState() {
431 | super.initState();
432 | _chipBorderController = AnimationController(
433 | vsync: this,
434 | duration: const Duration(seconds: 3),
435 | )..repeat();
436 | }
437 |
438 | @override
439 | void dispose() {
440 | _chipBorderController.dispose();
441 | super.dispose();
442 | }
443 |
444 | @override
445 | Widget build(BuildContext context) {
446 | // Always transparent background to avoid a fixed bar look.
447 | return Container(
448 | padding: const EdgeInsets.all(12.0),
449 | color: Colors.transparent,
450 | child: SafeArea(
451 | top: false,
452 | child: AnimatedSwitcher(
453 | duration: 300.ms,
454 | switchInCurve: Curves.easeOutQuart,
455 | switchOutCurve: Curves.easeInQuart,
456 | transitionBuilder: (child, animation) =>
457 | ScaleTransition(scale: animation, child: child),
458 | // SizeTransition(sizeFactor: animation, child: child),
459 | child: widget.isExpanded
460 | ? _buildExpandedInput(context)
461 | : _buildShrunkInput(context),
462 | ),
463 | ),
464 | );
465 | }
466 |
467 | Widget _buildShrunkInput(BuildContext context) {
468 | final gradientColors = [
469 | const Color(0xFF00E5FF),
470 | const Color(0xFF7C4DFF),
471 | const Color(0xFFFF4081),
472 | const Color(0xFF00E5FF),
473 | ];
474 |
475 | return Align(
476 | alignment: Alignment.bottomCenter,
477 | child: GestureDetector(
478 | onTap: widget.onExpand,
479 | child: AnimatedBuilder(
480 | animation: _chipBorderController,
481 | builder: (context, child) {
482 | final angle = _chipBorderController.value * 2 * math.pi;
483 | return Container(
484 | key: const ValueKey('shrunkInput'),
485 | decoration: BoxDecoration(
486 | borderRadius: BorderRadius.circular(22),
487 | gradient: SweepGradient(
488 | colors: gradientColors,
489 | startAngle: 0,
490 | endAngle: 2 * math.pi,
491 | transform: GradientRotation(angle),
492 | ),
493 | ),
494 | padding: const EdgeInsets.all(1.6),
495 | child: Container(
496 | decoration: BoxDecoration(
497 | color: Theme.of(context).scaffoldBackgroundColor,
498 | borderRadius: BorderRadius.circular(20),
499 | ),
500 | padding: const EdgeInsets.symmetric(
501 | horizontal: 12,
502 | vertical: 8,
503 | ),
504 | child: Row(
505 | mainAxisSize: MainAxisSize.min,
506 | children: [
507 | HugeIcon(icon: HugeIcons.strokeRoundedEdit03, size: 18),
508 | const Gap(6),
509 | Text(
510 | 'Tap to type...',
511 | style: Theme.of(
512 | context,
513 | ).textTheme.bodyMedium?.copyWith(fontSize: 14.0),
514 | ),
515 | ],
516 | ).animate().scale(duration: 220.ms, curve: Curves.easeOutBack),
517 | ),
518 | );
519 | },
520 | ),
521 | ),
522 | );
523 | }
524 |
525 | Widget _buildExpandedInput(BuildContext context) {
526 | return Column(
527 | key: const ValueKey('expandedInput'),
528 | children: [
529 | if (widget.pickedMediaFile != null)
530 | _buildMediaAttachmentPreview(context),
531 | Row(
532 | crossAxisAlignment: CrossAxisAlignment.end,
533 | children: [
534 | IconButton.filled(
535 | icon: const HugeIcon(icon: HugeIcons.strokeRoundedAttachment),
536 | onPressed: widget.onAttachMedia,
537 | tooltip: 'Attach Media',
538 | ),
539 | Expanded(
540 | child: TextField(
541 | focusNode: widget.focusNode,
542 | controller: widget.textController,
543 | minLines: 1,
544 | maxLines: 5,
545 | keyboardType: TextInputType.multiline,
546 | textInputAction: TextInputAction.newline,
547 | textCapitalization: TextCapitalization.sentences,
548 | decoration: InputDecoration(
549 | hintText: 'Type your message...',
550 | filled: true,
551 | fillColor: Theme.of(context).scaffoldBackgroundColor,
552 | contentPadding: const EdgeInsets.symmetric(
553 | horizontal: 16,
554 | vertical: 12,
555 | ),
556 | border: OutlineInputBorder(
557 | borderRadius: BorderRadius.circular(24),
558 | borderSide: BorderSide.none,
559 | ),
560 | ),
561 | onSubmitted: (_) => widget.onSendMessage(),
562 | ),
563 | ),
564 | ValueListenableBuilder(
565 | valueListenable: widget.textController,
566 | builder: (context, value, _) {
567 | final hasContent =
568 | value.text.trim().isNotEmpty ||
569 | widget.pickedMediaFile != null;
570 | return AnimatedScale(
571 | scale: hasContent ? 1.0 : 0.98,
572 | duration: 200.ms,
573 | curve: Curves.easeOutBack,
574 | child: IconButton.filled(
575 | icon: const HugeIcon(icon: HugeIcons.strokeRoundedSent),
576 | onPressed: hasContent ? widget.onSendMessage : null,
577 | ),
578 | );
579 | },
580 | ),
581 | ],
582 | ),
583 | ],
584 | );
585 | }
586 |
587 | Widget _buildMediaAttachmentPreview(BuildContext context) {
588 | return Padding(
589 | padding: const EdgeInsets.only(bottom: 12.0),
590 | child: Stack(
591 | children: [
592 | ClipRRect(
593 | borderRadius: BorderRadius.circular(12),
594 | child: SizedBox(
595 | height: 100,
596 | width: double.infinity,
597 | child: MediaPreview(file: widget.pickedMediaFile!),
598 | ),
599 | ),
600 | Positioned(
601 | top: 4,
602 | right: 4,
603 | child: CircleAvatar(
604 | backgroundColor: Colors.black.withValues(alpha: 0.5),
605 | radius: 14,
606 | child: IconButton(
607 | iconSize: 14,
608 | padding: EdgeInsets.zero,
609 | icon: const HugeIcon(
610 | icon: HugeIcons.strokeRoundedCancel01,
611 | color: Colors.white,
612 | ),
613 | onPressed: widget.onRemoveMedia,
614 | ),
615 | ),
616 | ),
617 | ],
618 | ),
619 | ).animate().slide(begin: const Offset(0, 0.5)).fadeIn();
620 | }
621 | }
622 |
623 | class _ActionIconButton extends StatelessWidget {
624 | final Widget icon;
625 | final String tooltip;
626 | final VoidCallback onPressed;
627 | final Color color;
628 |
629 | const _ActionIconButton({
630 | required this.icon,
631 | required this.tooltip,
632 | required this.onPressed,
633 | required this.color,
634 | });
635 |
636 | @override
637 | Widget build(BuildContext context) {
638 | return IconButton(
639 | tooltip: tooltip,
640 | onPressed: onPressed,
641 | icon: IconTheme(
642 | data: IconThemeData(color: color, size: 18),
643 | child: icon,
644 | ),
645 | );
646 | }
647 | }
648 |
--------------------------------------------------------------------------------
/lib/ui/screens/home.dart:
--------------------------------------------------------------------------------
1 | // lib/ui/screens/home.dart
2 | import 'dart:async';
3 | import 'dart:io';
4 | import 'dart:ui' show Paint, PaintingStyle, StrokeJoin;
5 | import 'package:flutter/material.dart';
6 | import 'package:flutter/rendering.dart';
7 | import 'package:flutter/services.dart';
8 | import 'package:flutter/foundation.dart';
9 | import 'package:flutter_animate/flutter_animate.dart';
10 | import 'package:gap/gap.dart';
11 | import 'package:hugeicons/hugeicons.dart';
12 | import 'package:image_picker/image_picker.dart';
13 | import '../../api/ai_service.dart';
14 | import '../../models/chat_message.dart';
15 | import 'package:soft_edge_blur/soft_edge_blur.dart';
16 |
17 | import '../widgets/home_page_widgets.dart';
18 |
19 | class HomePage extends StatefulWidget {
20 | const HomePage({super.key});
21 |
22 | static const String routeName = '/';
23 |
24 | @override
25 | State createState() => _HomePageState();
26 | }
27 |
28 | class _HomePageState extends State {
29 | final ScrollController _scrollController = ScrollController();
30 | final TextEditingController _textController = TextEditingController();
31 | final FocusNode _textFocusNode = FocusNode();
32 | final ImagePicker _picker = ImagePicker();
33 | final List _messages = [];
34 |
35 | bool _isInputBarExpanded = true;
36 | File? _pickedMediaFile;
37 | bool _isProgrammaticScroll = false; // guard to prevent shrink during auto scroll
38 | double _topTitleOpacity = 0.0; // Opacity for big top title
39 |
40 | // Model generation shimmer flag
41 | bool _isGenerating = false;
42 |
43 | // Demo seed control
44 | static const bool _enableDemoSeed = false;
45 |
46 | // Quick-start suggestions shown when no user messages yet
47 | static const List _suggestions = [
48 | 'What is GDG and how does GDG Yaoundé fit in?',
49 | 'How can I join GDG Yaoundé and stay updated?',
50 | 'When is the next GDG Yaoundé meetup or DevFest in Cameroon?',
51 | 'Who are the organizers of GDG Yaoundé and how do I reach them?',
52 | 'How do I become a speaker at a GDG Yaoundé event?',
53 | 'Share the GDG Code of Conduct and community guidelines.',
54 | 'List active GDG chapters in Cameroon with their links.',
55 | 'Give me a recap of the last GDG Yaoundé event.',
56 | 'Where can I find GDG Yaoundé on social media?',
57 | 'How can I volunteer or help organize GDG Yaoundé events?',
58 | 'What programs does GDG run (Study Jams, I/O Extended, DevFest)?',
59 | 'Tips for first-time attendees at a GDG Yaoundé meetup.',
60 | ];
61 |
62 | bool get _hasUserMessages => _messages.any((m) => m.sender == MessageSender.user);
63 |
64 | late final AIService _aiService;
65 |
66 | @override
67 | void initState() {
68 | super.initState();
69 | _aiService = AIService();
70 | // Removed initial model message to keep empty state truly empty
71 | if (_enableDemoSeed) {
72 | WidgetsBinding.instance.addPostFrameCallback((_) => _seedSampleMessages());
73 | }
74 | }
75 |
76 | void _seedSampleMessages() {
77 | final baseTexts = [
78 | 'Hey there! 👋',
79 | 'How are you doing today?',
80 | 'Let’s try a longer message to test how wrapping behaves in the chat bubble. It should gracefully wrap to multiple lines and still look nice.',
81 | 'Short one.',
82 | 'Here’s a list: 1) Alpha 2) Beta 3) Gamma 4) Delta 5) Epsilon.',
83 | 'What’s the weather like over there? Do you prefer sunny days or rainy ones?',
84 | 'OK',
85 | 'This is a pretty long paragraph intended to stress-test scrolling performance and bubble constraints. The max width should be respected and content should be readable with proper padding.',
86 | 'Neat!',
87 | 'Let’s add another message to make sure we have plenty of items in the list.',
88 | '# Heading 1\nSome paragraph text with a [link](https://example.com).',
89 | '## Heading 2\n- Bullet item A\n- Bullet item B\n- Bullet item C',
90 | '### Heading 3\n1. Ordered one\n2. Ordered two\n3. Ordered three',
91 | '> Blockquote\n> Another line in the quote.',
92 | 'Inline code like `final x = 42;` and a code block:\n\n```dart\nvoid main() {\n print(\'Hello Markdown!\');\n}\n```',
93 | '| Col A | Col B |\n|---|---|\n| 1 | 2 |\n| 3 | 4 |',
94 | 'Image: ',
95 | ];
96 |
97 | setState(() {
98 | for (int i = 0; i < 24; i++) {
99 | final isUser = i % 2 == 0;
100 | final text = baseTexts[i % baseTexts.length];
101 | _messages.add(
102 | ChatMessage(
103 | text: text,
104 | sender: isUser ? MessageSender.user : MessageSender.model,
105 | ),
106 | );
107 | }
108 | });
109 |
110 | _scrollToBottom();
111 | }
112 |
113 | @override
114 | void dispose() {
115 | _scrollController.dispose();
116 | _textController.dispose();
117 | _textFocusNode.dispose();
118 | super.dispose();
119 | }
120 |
121 | void _sendMessage() {
122 | final text = _textController.text.trim();
123 | if (text.isEmpty && _pickedMediaFile == null) return;
124 |
125 | debugPrint('[HomePage] _sendMessage textLen=${text.length} hasMedia=${_pickedMediaFile != null}');
126 |
127 | HapticFeedback.mediumImpact();
128 | FocusScope.of(context).unfocus();
129 |
130 | final newMessage = ChatMessage(
131 | text: text.isEmpty ? null : text,
132 | mediaFile: _pickedMediaFile,
133 | sender: MessageSender.user,
134 | );
135 |
136 | setState(() {
137 | _messages.add(newMessage);
138 | _textController.clear();
139 | _pickedMediaFile = null;
140 | _isGenerating = true; // show shimmer while model responds
141 | });
142 |
143 | _scrollToBottom();
144 |
145 | // Kick off model generation
146 | final prompt = newMessage.text ?? 'Please help based on the attached media.';
147 | debugPrint('[HomePage] Starting AI generation');
148 | _generateModelResponse(prompt);
149 | }
150 |
151 | Future _generateModelResponse(String prompt) async {
152 | try {
153 | final md = await _aiService.generate(prompt);
154 | if (!mounted) return;
155 | setState(() {
156 | _messages.add(
157 | ChatMessage(
158 | text: md.isEmpty ? '*(No content returned)*' : md,
159 | sender: MessageSender.model,
160 | ),
161 | );
162 | _isGenerating = false;
163 | });
164 | debugPrint('[HomePage] AI generation success, mdLen=${md.length}');
165 | _scrollToBottom();
166 | } catch (e, st) {
167 | if (!mounted) return;
168 | debugPrint('[HomePage] AI generation error: $e\n$st');
169 | setState(() {
170 | _messages.add(
171 | ChatMessage(
172 | text: _friendlyErrorMessage(e),
173 | sender: MessageSender.model,
174 | isError: true,
175 | ),
176 | );
177 | _isGenerating = false;
178 | });
179 | _scrollToBottom();
180 | }
181 | }
182 |
183 | String _friendlyErrorMessage(Object error) {
184 | // Map common exceptions to user-friendly messages
185 | if (error is SocketException) {
186 | return 'Network error. Please check your internet connection and try again.';
187 | }
188 | if (error is TimeoutException) {
189 | return 'The AI service took too long to respond. Please try again in a moment.';
190 | }
191 | if (error is PlatformException) {
192 | final code = error.code.isNotEmpty ? ' (code: ${error.code})' : '';
193 | return 'A platform error occurred$code. Please try again.';
194 | }
195 | final type = error.runtimeType.toString();
196 | if (type.contains('FirebaseException')) {
197 | return 'A Firebase error occurred. Please verify your configuration and try again.';
198 | }
199 | if (type.contains('ServerException')) {
200 | return 'The AI service is temporarily unavailable. Please try again shortly.';
201 | }
202 | return 'Sorry, I couldn\'t generate a response. Please try again.';
203 | }
204 |
205 | void _pickMedia() async {
206 | HapticFeedback.selectionClick();
207 | showModalBottomSheet(
208 | context: context,
209 | showDragHandle: true,
210 | shape: const RoundedRectangleBorder(
211 | borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
212 | ),
213 | builder: (context) {
214 | return SafeArea(
215 | child: Padding(
216 | padding: const EdgeInsets.symmetric(horizontal: 16.0),
217 | child: Column(
218 | mainAxisSize: MainAxisSize.min,
219 | children: [
220 | ListTile(
221 | shape: RoundedRectangleBorder(
222 | borderRadius: BorderRadius.circular(12.0),
223 | ),
224 | leading: const HugeIcon(icon: HugeIcons.strokeRoundedImage01),
225 | title: const Text('Pick Image'),
226 | onTap: () async {
227 | Navigator.pop(context);
228 | final XFile? image = await _picker.pickImage(
229 | source: ImageSource.gallery,
230 | );
231 | if (image != null) {
232 | setState(() {
233 | _pickedMediaFile = File(image.path);
234 | _expandInputBar();
235 | });
236 | HapticFeedback.lightImpact();
237 | }
238 | },
239 | ),
240 | ListTile(
241 | shape: RoundedRectangleBorder(
242 | borderRadius: BorderRadius.circular(12.0),
243 | ),
244 | leading: const HugeIcon(icon: HugeIcons.strokeRoundedVideo01),
245 | title: const Text('Pick Video'),
246 | onTap: () async {
247 | Navigator.pop(context);
248 | final XFile? video = await _picker.pickVideo(
249 | source: ImageSource.gallery,
250 | );
251 | if (video != null) {
252 | setState(() {
253 | _pickedMediaFile = File(video.path);
254 | _expandInputBar();
255 | });
256 | HapticFeedback.lightImpact();
257 | }
258 | },
259 | ),
260 | ],
261 | ),
262 | ),
263 | );
264 | },
265 | );
266 | }
267 |
268 | Future _scrollToBottom({ Duration duration = const Duration(milliseconds: 300), }) async {
269 | final completer = Completer();
270 | WidgetsBinding.instance.addPostFrameCallback((_) async {
271 | if (_scrollController.hasClients) {
272 | try {
273 | _isProgrammaticScroll = true;
274 | await _scrollController.animateTo(
275 | _scrollController.position.maxScrollExtent,
276 | duration: duration,
277 | curve: Curves.easeOut,
278 | );
279 | } finally {
280 | _isProgrammaticScroll = false;
281 | }
282 | }
283 | if (!completer.isCompleted) completer.complete();
284 | });
285 | return completer.future;
286 | }
287 |
288 | Future _expandInputBar() async {
289 | if (!_isInputBarExpanded) {
290 | setState(() => _isInputBarExpanded = true);
291 | HapticFeedback.lightImpact();
292 | }
293 | await _scrollToBottom(duration: const Duration(milliseconds: 250));
294 | WidgetsBinding.instance.addPostFrameCallback((_) {
295 | if (mounted) _textFocusNode.requestFocus();
296 | });
297 | }
298 |
299 | void _scheduleExpand(bool expand) {
300 | if (expand == _isInputBarExpanded) return;
301 | WidgetsBinding.instance.addPostFrameCallback((_) {
302 | if (!mounted) return;
303 | setState(() => _isInputBarExpanded = expand);
304 | });
305 | }
306 |
307 | @override
308 | Widget build(BuildContext context) {
309 | final viewInsets = MediaQuery.viewInsetsOf(context);
310 | final hasUser = _hasUserMessages;
311 | return GestureDetector(
312 | onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
313 | child: Scaffold(
314 | resizeToAvoidBottomInset: false,
315 | body: Stack(
316 | children: [
317 | Positioned.fill(
318 | child: SoftEdgeBlur(
319 | edges: [
320 | EdgeBlur(
321 | type: EdgeType.topEdge,
322 | size: 150,
323 | sigma: 50,
324 | controlPoints: [
325 | ControlPoint(position: 0.5, type: ControlPointType.visible),
326 | ControlPoint(position: 1, type: ControlPointType.transparent),
327 | ],
328 | ),
329 | ],
330 | child: _buildChatList(viewInsets.bottom),
331 | ),
332 | ),
333 | if (!hasUser) _buildEmptyStateOverlay(context),
334 | Positioned(
335 | left: 0,
336 | right: 0,
337 | bottom: 0,
338 | child: AnimatedPadding(
339 | padding: EdgeInsets.only(bottom: viewInsets.bottom),
340 | duration: const Duration(milliseconds: 200),
341 | curve: Curves.easeOut,
342 | child: OrientationBuilder(
343 | builder: (context, orientation) {
344 | return ConstrainedBox(
345 | constraints: BoxConstraints(
346 | maxWidth: orientation == Orientation.portrait ? double.infinity : 900,
347 | ),
348 | child: ChatInputArea(
349 | isExpanded: _isInputBarExpanded,
350 | onExpand: () { _expandInputBar(); },
351 | textController: _textController,
352 | onSendMessage: _sendMessage,
353 | onAttachMedia: _pickMedia,
354 | pickedMediaFile: _pickedMediaFile,
355 | onRemoveMedia: () => setState(() => _pickedMediaFile = null),
356 | focusNode: _textFocusNode,
357 | ),
358 | );
359 | }
360 | ),
361 | ),
362 | ),
363 | ],
364 | ),
365 | ),
366 | );
367 | }
368 |
369 | Widget _buildChatList(double keyboardInset) {
370 | const double kBottomThreshold = 56.0;
371 | const double kDecelThreshold = 120.0;
372 | const double kTitleFadeRange = 220.0;
373 | return NotificationListener(
374 | onNotification: (notification) {
375 | final metrics = notification.metrics;
376 | final double extentAfter = metrics.extentAfter;
377 |
378 | final double pixels = metrics.pixels;
379 | double newOpacity = 1.0 - (pixels / kTitleFadeRange);
380 | newOpacity = newOpacity.clamp(0.0, 1.0);
381 | if ((newOpacity - _topTitleOpacity).abs() > 0.02) {
382 | // Defer setState to avoid scheduling build during current frame
383 | WidgetsBinding.instance.addPostFrameCallback((_) {
384 | if (!mounted) return;
385 | if ((newOpacity - _topTitleOpacity).abs() > 0.01) {
386 | setState(() => _topTitleOpacity = newOpacity);
387 | }
388 | });
389 | }
390 |
391 | if (_isProgrammaticScroll) {
392 | if (extentAfter <= kBottomThreshold) _scheduleExpand(true);
393 | return false;
394 | }
395 | if (extentAfter <= kBottomThreshold) {
396 | _scheduleExpand(true);
397 | return false;
398 | }
399 | if (extentAfter > kDecelThreshold && (notification is ScrollUpdateNotification || notification is UserScrollNotification)) {
400 | _scheduleExpand(false);
401 | }
402 | return false;
403 | },
404 | child: ListView.builder(
405 | controller: _scrollController,
406 | padding: EdgeInsets.fromLTRB(16, 16, 16, 120 + keyboardInset),
407 | itemCount: _messages.length + 1 + (_isGenerating ? 1 : 0),
408 | itemBuilder: (context, index) {
409 | if (index == 0) return _buildTopTitleHeader(context);
410 | final lastIndex = _messages.length + 1;
411 | if (_isGenerating && index == lastIndex) return _buildShimmerBubble(context);
412 | final message = _messages[index - 1];
413 | final isUser = message.sender == MessageSender.user;
414 | return MessageBubble(message: message)
415 | .animate()
416 | .fadeIn(duration: 350.ms, curve: Curves.easeOutCubic)
417 | .slideX(begin: isUser ? 0.15 : -0.15, end: 0, curve: Curves.easeOutBack, duration: 400.ms);
418 | },
419 | ),
420 | );
421 | }
422 |
423 | Widget _buildTopTitleHeader(BuildContext context) {
424 | final color = Theme.of(context).colorScheme.onSurface;
425 | final stroke = Paint()
426 | ..style = PaintingStyle.stroke
427 | ..strokeWidth = 2.2
428 | ..strokeJoin = StrokeJoin.round
429 | ..color = color.withValues(alpha: 0.35);
430 |
431 | final fill = Paint()
432 | ..style = PaintingStyle.fill
433 | ..color = color.withValues(alpha: 0.04);
434 |
435 | return IgnorePointer(
436 | child: AnimatedOpacity(
437 | opacity: _topTitleOpacity,
438 | duration: const Duration(milliseconds: 120),
439 | curve: Curves.easeOut,
440 | child: Padding(
441 | padding: const EdgeInsets.only(top: 12.0, bottom: 12.0),
442 | child: SizedBox(
443 | height: 200,
444 | child: Center(
445 | child: Stack(
446 | alignment: Alignment.center,
447 | children: [
448 | Text(
449 | 'Gyde',
450 | textAlign: TextAlign.center,
451 | style: TextStyle(
452 | fontSize: 72,
453 | fontWeight: FontWeight.w400,
454 | letterSpacing: -1.0,
455 | foreground: fill,
456 | ),
457 | ),
458 | Text(
459 | 'Gyde',
460 | textAlign: TextAlign.center,
461 | style: TextStyle(
462 | fontSize: 72,
463 | fontWeight: FontWeight.w400,
464 | letterSpacing: -1.0,
465 | foreground: stroke,
466 | ),
467 | ),
468 | ],
469 | ),
470 | ),
471 | ),
472 | ),
473 | ),
474 | );
475 | }
476 |
477 | Widget _buildShimmerBubble(BuildContext context) {
478 | final theme = Theme.of(context);
479 | final base = theme.colorScheme.surfaceContainerHighest;
480 | final onBase = theme.colorScheme.onSurface.withValues(alpha: 0.08);
481 |
482 | Widget line(double width, double height) => Container(
483 | width: width,
484 | height: height,
485 | decoration: BoxDecoration(
486 | color: base,
487 | borderRadius: BorderRadius.circular(8),
488 | ),
489 | ).animate(onPlay: (c) => c.repeat())
490 | .shimmer(duration: 1200.ms, color: onBase);
491 |
492 | final screenW = MediaQuery.of(context).size.width;
493 |
494 | return Align(
495 | alignment: Alignment.centerLeft,
496 | child: ConstrainedBox(
497 | constraints: BoxConstraints(maxWidth: screenW * 0.9),
498 | child: Padding(
499 | padding: const EdgeInsets.symmetric(vertical: 6.0),
500 | child: Column(
501 | crossAxisAlignment: CrossAxisAlignment.start,
502 | mainAxisSize: MainAxisSize.min,
503 | children: [
504 | line(screenW * 0.6, 12),
505 | const SizedBox(height: 8),
506 | line(screenW * 0.8, 12),
507 | const SizedBox(height: 8),
508 | line(screenW * 0.45, 12),
509 | ],
510 | ),
511 | ),
512 | ),
513 | );
514 | }
515 |
516 | Widget _buildEmptyStateOverlay(BuildContext context) {
517 | final theme = Theme.of(context);
518 | final viewInsets = MediaQuery.viewInsetsOf(context);
519 | return Positioned(
520 | left: 0,
521 | right: 0,
522 | bottom: 0,
523 | child: IgnorePointer(
524 | ignoring: false,
525 | child: Padding(
526 | padding: EdgeInsets.fromLTRB(0, 0, 0, 120 + viewInsets.bottom),
527 | child: Column(
528 | mainAxisSize: MainAxisSize.min,
529 | crossAxisAlignment: CrossAxisAlignment.center,
530 | children: [
531 | // App name on empty state
532 | Text(
533 | 'Gyde',
534 | style: theme.textTheme.displaySmall?.copyWith(
535 | fontWeight: FontWeight.w600,
536 | letterSpacing: -0.5,
537 | color: theme.colorScheme.onSurface.withValues(alpha: 0.5),
538 | ),
539 | ),
540 | const Gap(8),
541 | Text(
542 | "What can I help you with today?",
543 | textAlign: TextAlign.center,
544 | style: theme.textTheme.titleMedium?.copyWith(
545 | color: theme.colorScheme.onSurface.withValues(alpha: 0.7),
546 | ),
547 | ),
548 | const Gap(12),
549 | _buildInfiniteSuggestionsStrip(context),
550 | const Gap(8),
551 | _buildInfiniteSuggestionsStrip(context),
552 | ],
553 | ).animate().fadeIn(duration: 300.ms, curve: Curves.easeOut),
554 | ),
555 | ),
556 | );
557 | }
558 |
559 | Widget _buildInfiniteSuggestionsStrip(BuildContext context) {
560 | // Infinite horizontal scroll of suggestion chips by repeating the list.
561 | return SizedBox(
562 | height: 44,
563 | child: ListView.builder(
564 | scrollDirection: Axis.horizontal,
565 | physics: const BouncingScrollPhysics(),
566 | padding: const EdgeInsets.symmetric(horizontal: 4.0),
567 | itemBuilder: (context, index) {
568 | final s = _suggestions[index % _suggestions.length];
569 | return Padding(
570 | padding: const EdgeInsets.symmetric(horizontal: 4.0),
571 | child: _SuggestionChip(
572 | label: s,
573 | onTap: () {
574 | setState(() { _textController.text = s; });
575 | _expandInputBar();
576 | },
577 | ),
578 | );
579 | },
580 | ),
581 | );
582 | }
583 | }
584 |
585 | class _SuggestionChip extends StatelessWidget {
586 | final String label;
587 | final VoidCallback onTap;
588 | const _SuggestionChip({required this.label, required this.onTap});
589 |
590 | @override
591 | Widget build(BuildContext context) {
592 | final theme = Theme.of(context);
593 | return Material(
594 | color: theme.colorScheme.surfaceContainerHighest.withValues(alpha: 0.5),
595 | shape: const StadiumBorder(),
596 | child: InkWell(
597 | customBorder: const StadiumBorder(),
598 | onTap: onTap,
599 | child: Padding(
600 | padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
601 | child: Row(
602 | mainAxisSize: MainAxisSize.min,
603 | children: [
604 | const Icon(Icons.bolt_rounded, size: 16),
605 | const SizedBox(width: 6),
606 | Text(label, style: theme.textTheme.bodyMedium),
607 | ],
608 | ),
609 | ),
610 | ),
611 | );
612 | }
613 | }
614 |
--------------------------------------------------------------------------------