├── 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 | | ![Splash](./assets/screenshots/splash.png) | ![Home](./assets/screenshots/home.png) | ![Chat](./assets/screenshots/chat.png) | 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: ![Alt text](https://picsum.photos/400/200)', 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 | --------------------------------------------------------------------------------