Initial push to new remote
45
.gitignore
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.build/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
.swiftpm/
|
||||||
|
migrate_working_dir/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
|
# is commented out by default.
|
||||||
|
#.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
/build/
|
||||||
|
|
||||||
|
# Symbolication related
|
||||||
|
app.*.symbols
|
||||||
|
|
||||||
|
# Obfuscation related
|
||||||
|
app.*.map.json
|
||||||
|
|
||||||
|
# Android Studio will place build artifacts here
|
||||||
|
/android/app/debug
|
||||||
|
/android/app/profile
|
||||||
|
/android/app/release
|
||||||
45
.metadata
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# This file tracks properties of this Flutter project.
|
||||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
|
#
|
||||||
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: "fcf2c11572af6f390246c056bc905eca609533a0"
|
||||||
|
channel: "stable"
|
||||||
|
|
||||||
|
project_type: app
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
||||||
|
base_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
||||||
|
- platform: android
|
||||||
|
create_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
||||||
|
base_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
||||||
|
- platform: ios
|
||||||
|
create_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
||||||
|
base_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
||||||
|
- platform: linux
|
||||||
|
create_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
||||||
|
base_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
||||||
|
- platform: macos
|
||||||
|
create_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
||||||
|
base_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
||||||
|
- platform: web
|
||||||
|
create_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
||||||
|
base_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
||||||
|
- platform: windows
|
||||||
|
create_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
||||||
|
base_revision: fcf2c11572af6f390246c056bc905eca609533a0
|
||||||
|
|
||||||
|
# User provided section
|
||||||
|
|
||||||
|
# List of Local paths (relative to this file) that should be
|
||||||
|
# ignored by the migrate tool.
|
||||||
|
#
|
||||||
|
# Files that are not part of the templates will be ignored by default.
|
||||||
|
unmanaged_files:
|
||||||
|
- 'lib/main.dart'
|
||||||
|
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||||
32
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "coffee_at_home",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "dart",
|
||||||
|
"program": "lib/main.dart",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"flutterMode": "debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "coffee_at_home (profile mode)",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "dart",
|
||||||
|
"program": "lib/main.dart",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"flutterMode": "profile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "coffee_at_home (release mode)",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "dart",
|
||||||
|
"program": "lib/main.dart",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"flutterMode": "release"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
72
.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "Flutter: Get Dependencies",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "C:\\Users\\meinr\\OneDrive\\Desktop\\development\\Flutter\\flutter\\bin\\flutter",
|
||||||
|
"args": ["pub", "get"],
|
||||||
|
"group": "build",
|
||||||
|
"presentation": {
|
||||||
|
"echo": true,
|
||||||
|
"reveal": "always",
|
||||||
|
"focus": false,
|
||||||
|
"panel": "shared",
|
||||||
|
"showReuseMessage": true,
|
||||||
|
"clear": false
|
||||||
|
},
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Flutter: Run App",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "C:\\Users\\meinr\\OneDrive\\Desktop\\development\\Flutter\\flutter\\bin\\flutter",
|
||||||
|
"args": ["run"],
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
},
|
||||||
|
"presentation": {
|
||||||
|
"echo": true,
|
||||||
|
"reveal": "always",
|
||||||
|
"focus": false,
|
||||||
|
"panel": "shared",
|
||||||
|
"showReuseMessage": true,
|
||||||
|
"clear": false
|
||||||
|
},
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Flutter: Build APK",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "C:\\Users\\meinr\\OneDrive\\Desktop\\development\\Flutter\\flutter\\bin\\flutter",
|
||||||
|
"args": ["build", "apk"],
|
||||||
|
"group": "build",
|
||||||
|
"presentation": {
|
||||||
|
"echo": true,
|
||||||
|
"reveal": "always",
|
||||||
|
"focus": false,
|
||||||
|
"panel": "shared",
|
||||||
|
"showReuseMessage": true,
|
||||||
|
"clear": false
|
||||||
|
},
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Flutter: Clean",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "C:\\Users\\meinr\\OneDrive\\Desktop\\development\\Flutter\\flutter\\bin\\flutter",
|
||||||
|
"args": ["clean"],
|
||||||
|
"group": "build",
|
||||||
|
"presentation": {
|
||||||
|
"echo": true,
|
||||||
|
"reveal": "always",
|
||||||
|
"focus": false,
|
||||||
|
"panel": "shared",
|
||||||
|
"showReuseMessage": true,
|
||||||
|
"clear": false
|
||||||
|
},
|
||||||
|
"problemMatcher": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
300
README_iOS.md
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
# Coffee at Home - iOS Development Guide
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Your iOS developer will need:
|
||||||
|
|
||||||
|
- **macOS** (iOS development only works on Mac)
|
||||||
|
- **Xcode 15.0+** (latest stable version from Mac App Store)
|
||||||
|
- **Flutter SDK** (latest stable version)
|
||||||
|
- **CocoaPods** (dependency manager for iOS)
|
||||||
|
- **iOS Simulator** or physical iOS device for testing
|
||||||
|
|
||||||
|
## Setup Instructions
|
||||||
|
|
||||||
|
### 1. Install Flutter
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Download Flutter SDK from https://flutter.dev/docs/get-started/install/macos
|
||||||
|
# Or use Homebrew:
|
||||||
|
brew install --cask flutter
|
||||||
|
|
||||||
|
# Verify installation
|
||||||
|
flutter doctor
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Install Xcode and iOS Tools
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install Xcode from Mac App Store
|
||||||
|
# Accept Xcode license
|
||||||
|
sudo xcodebuild -license accept
|
||||||
|
|
||||||
|
# Install iOS Simulator
|
||||||
|
sudo xcode-select --install
|
||||||
|
|
||||||
|
# Install CocoaPods
|
||||||
|
sudo gem install cocoapods
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Clone and Setup Project
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone <your-repo-url>
|
||||||
|
cd CoffeeAtHomeFlutter
|
||||||
|
|
||||||
|
# Get Flutter dependencies
|
||||||
|
flutter pub get
|
||||||
|
|
||||||
|
# Install iOS dependencies
|
||||||
|
cd ios
|
||||||
|
pod install
|
||||||
|
cd ..
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building for iOS
|
||||||
|
|
||||||
|
### Development Build (iOS Simulator)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List available simulators
|
||||||
|
flutter emulators
|
||||||
|
|
||||||
|
# Launch iOS Simulator
|
||||||
|
open -a Simulator
|
||||||
|
|
||||||
|
# Run app in debug mode
|
||||||
|
flutter run -d ios
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development Build (Physical Device)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Connect iOS device via USB
|
||||||
|
# Ensure device is in Developer Mode (Settings > Privacy & Security > Developer Mode)
|
||||||
|
|
||||||
|
# List connected devices
|
||||||
|
flutter devices
|
||||||
|
|
||||||
|
# Run on connected device
|
||||||
|
flutter run -d <device-id>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Release Build (App Store)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build release version
|
||||||
|
flutter build ios --release
|
||||||
|
|
||||||
|
# This creates: build/ios/archive/Runner.xcarchive
|
||||||
|
```
|
||||||
|
|
||||||
|
## Xcode Configuration
|
||||||
|
|
||||||
|
### 1. Open iOS Project in Xcode
|
||||||
|
|
||||||
|
```bash
|
||||||
|
open ios/Runner.xcworkspace
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Configure App Settings
|
||||||
|
|
||||||
|
In Xcode, update these settings:
|
||||||
|
|
||||||
|
- **Bundle Identifier**: `com.yourcompany.coffeeatHome`
|
||||||
|
- **Display Name**: `Coffee at Home`
|
||||||
|
- **Version**: `1.0.0`
|
||||||
|
- **Build Number**: `1`
|
||||||
|
- **Deployment Target**: `iOS 12.0+`
|
||||||
|
|
||||||
|
### 3. Code Signing
|
||||||
|
|
||||||
|
- **Team**: Select your Apple Developer Team
|
||||||
|
- **Provisioning Profile**: Automatic (or select specific profile)
|
||||||
|
- **Signing Certificate**: Developer/Distribution certificate
|
||||||
|
|
||||||
|
### 4. App Icons and Launch Screen
|
||||||
|
|
||||||
|
- Replace icons in `ios/Runner/Assets.xcassets/AppIcon.appiconset/`
|
||||||
|
- Update launch screen in `ios/Runner/Base.lproj/LaunchScreen.storyboard`
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Unit Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run Flutter tests
|
||||||
|
flutter test
|
||||||
|
```
|
||||||
|
|
||||||
|
### Integration Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run integration tests on iOS
|
||||||
|
flutter drive --target=test_driver/app.dart -d ios
|
||||||
|
```
|
||||||
|
|
||||||
|
## App Store Submission
|
||||||
|
|
||||||
|
### 1. Create App Store Connect Entry
|
||||||
|
|
||||||
|
- Go to [App Store Connect](https://appstoreconnect.apple.com)
|
||||||
|
- Create new app with same Bundle ID
|
||||||
|
- Fill in app metadata
|
||||||
|
|
||||||
|
### 2. Build and Archive
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clean previous builds
|
||||||
|
flutter clean
|
||||||
|
flutter pub get
|
||||||
|
|
||||||
|
# Build for release
|
||||||
|
flutter build ios --release
|
||||||
|
|
||||||
|
# Open in Xcode for archiving
|
||||||
|
open ios/Runner.xcworkspace
|
||||||
|
```
|
||||||
|
|
||||||
|
In Xcode:
|
||||||
|
1. Select **Generic iOS Device** as destination
|
||||||
|
2. Go to **Product > Archive**
|
||||||
|
3. Once archived, click **Distribute App**
|
||||||
|
4. Choose **App Store Connect**
|
||||||
|
5. Upload to App Store Connect
|
||||||
|
|
||||||
|
### 3. Submit for Review
|
||||||
|
|
||||||
|
- Complete app metadata in App Store Connect
|
||||||
|
- Add screenshots (iPhone 6.7", 6.5", 5.5" and iPad Pro)
|
||||||
|
- Submit for App Store review
|
||||||
|
|
||||||
|
## Platform-Specific Features
|
||||||
|
|
||||||
|
### iOS-Specific Dependencies
|
||||||
|
|
||||||
|
The app uses these iOS-compatible packages:
|
||||||
|
|
||||||
|
- `shared_preferences` - Local storage
|
||||||
|
- `sqflite` - SQLite database
|
||||||
|
- `image_picker` - Camera/gallery access
|
||||||
|
- `go_router` - Navigation
|
||||||
|
- `provider` - State management
|
||||||
|
|
||||||
|
### Permissions
|
||||||
|
|
||||||
|
Add these to `ios/Runner/Info.plist` if needed:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<key>NSCameraUsageDescription</key>
|
||||||
|
<string>This app needs camera access to take photos of coffee.</string>
|
||||||
|
|
||||||
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
|
<string>This app needs photo library access to select coffee images.</string>
|
||||||
|
```
|
||||||
|
|
||||||
|
## App Architecture
|
||||||
|
|
||||||
|
### Data Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
CSV Files (Assets) → CsvDataService → StorageService → UI
|
||||||
|
User Data → SQLite → UserDataService → StorageService → UI
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Features Working on iOS
|
||||||
|
|
||||||
|
- ✅ **CSV Data Loading**: Coffee catalog from bundled CSV files
|
||||||
|
- ✅ **SQLite Storage**: User collections and journal entries
|
||||||
|
- ✅ **Material Design**: Consistent UI across platforms
|
||||||
|
- ✅ **Dark Theme**: Coffee-themed color scheme
|
||||||
|
- ✅ **Navigation**: Bottom navigation with go_router
|
||||||
|
- ✅ **State Management**: Provider pattern
|
||||||
|
- ✅ **Image Handling**: Coffee photos and gallery
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
1. **Pod Install Fails**
|
||||||
|
```bash
|
||||||
|
cd ios
|
||||||
|
pod repo update
|
||||||
|
pod install --repo-update
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Xcode Build Errors**
|
||||||
|
```bash
|
||||||
|
flutter clean
|
||||||
|
flutter pub get
|
||||||
|
cd ios && pod install
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Signing Issues**
|
||||||
|
- Ensure Apple Developer account is active
|
||||||
|
- Check Bundle ID matches App Store Connect
|
||||||
|
- Verify certificates are valid
|
||||||
|
|
||||||
|
4. **Simulator Not Found**
|
||||||
|
```bash
|
||||||
|
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
|
||||||
|
flutter doctor
|
||||||
|
```
|
||||||
|
|
||||||
|
### Performance Optimization
|
||||||
|
|
||||||
|
For iOS release builds:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build with optimization
|
||||||
|
flutter build ios --release --split-debug-info=debug-symbols --obfuscate
|
||||||
|
|
||||||
|
# Reduce app size
|
||||||
|
flutter build ios --release --tree-shake-icons
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### App Icon Requirements
|
||||||
|
|
||||||
|
- 1024x1024 PNG for App Store
|
||||||
|
- Various sizes generated automatically by Xcode
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
### Debug Information
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Get detailed device info
|
||||||
|
flutter doctor -v
|
||||||
|
|
||||||
|
# Check iOS specific setup
|
||||||
|
flutter doctor --verbose
|
||||||
|
|
||||||
|
# View logs during development
|
||||||
|
flutter logs
|
||||||
|
```
|
||||||
|
|
||||||
|
### Contact
|
||||||
|
|
||||||
|
For iOS-specific issues, the developer can:
|
||||||
|
|
||||||
|
1. Check Flutter iOS documentation: https://flutter.dev/docs/deployment/ios
|
||||||
|
2. Review Apple Developer guidelines: https://developer.apple.com/ios/
|
||||||
|
3. Contact the main development team with specific error messages
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Start Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Setup (one-time)
|
||||||
|
flutter pub get
|
||||||
|
cd ios && pod install && cd ..
|
||||||
|
|
||||||
|
# Development
|
||||||
|
flutter run -d ios
|
||||||
|
|
||||||
|
# Release Build
|
||||||
|
flutter build ios --release
|
||||||
|
open ios/Runner.xcworkspace
|
||||||
|
```
|
||||||
28
analysis_options.yaml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# This file configures the analyzer, which statically analyzes Dart code to
|
||||||
|
# check for errors, warnings, and lints.
|
||||||
|
#
|
||||||
|
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||||
|
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||||
|
# invoked from the command line by running `flutter analyze`.
|
||||||
|
|
||||||
|
# The following line activates a set of recommended lints for Flutter apps,
|
||||||
|
# packages, and plugins designed to encourage good coding practices.
|
||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
linter:
|
||||||
|
# The lint rules applied to this project can be customized in the
|
||||||
|
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||||
|
# included above or to enable additional rules. A list of all available lints
|
||||||
|
# and their documentation is published at https://dart.dev/lints.
|
||||||
|
#
|
||||||
|
# Instead of disabling a lint rule for the entire project in the
|
||||||
|
# section below, it can also be suppressed for a single line of code
|
||||||
|
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||||
|
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||||
|
# producing the lint.
|
||||||
|
rules:
|
||||||
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
|
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
|
|
||||||
|
# Additional information about this file can be found at
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
||||||
14
android/.gitignore
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
gradle-wrapper.jar
|
||||||
|
/.gradle
|
||||||
|
/captures/
|
||||||
|
/gradlew
|
||||||
|
/gradlew.bat
|
||||||
|
/local.properties
|
||||||
|
GeneratedPluginRegistrant.java
|
||||||
|
.cxx/
|
||||||
|
|
||||||
|
# Remember to never publicly share your keystore.
|
||||||
|
# See https://flutter.dev/to/reference-keystore
|
||||||
|
key.properties
|
||||||
|
**/*.keystore
|
||||||
|
**/*.jks
|
||||||
44
android/app/build.gradle.kts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
plugins {
|
||||||
|
id("com.android.application")
|
||||||
|
id("kotlin-android")
|
||||||
|
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||||
|
id("dev.flutter.flutter-gradle-plugin")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.example.coffee_at_home"
|
||||||
|
compileSdk = flutter.compileSdkVersion
|
||||||
|
ndkVersion = flutter.ndkVersion
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = JavaVersion.VERSION_11.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
|
applicationId = "com.example.coffee_at_home"
|
||||||
|
// You can update the following values to match your application needs.
|
||||||
|
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||||
|
minSdk = flutter.minSdkVersion
|
||||||
|
targetSdk = flutter.targetSdkVersion
|
||||||
|
versionCode = flutter.versionCode
|
||||||
|
versionName = flutter.versionName
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
// TODO: Add your own signing config for the release build.
|
||||||
|
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||||
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flutter {
|
||||||
|
source = "../.."
|
||||||
|
}
|
||||||
7
android/app/src/debug/AndroidManifest.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
||||||
45
android/app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<application
|
||||||
|
android:label="coffee_at_home"
|
||||||
|
android:name="${applicationName}"
|
||||||
|
android:icon="@mipmap/ic_launcher">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:taskAffinity=""
|
||||||
|
android:theme="@style/LaunchTheme"
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
|
the Android process has started. This theme is visible to the user
|
||||||
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
|
to determine the Window background behind the Flutter UI. -->
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
|
android:resource="@style/NormalTheme"
|
||||||
|
/>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<!-- Don't delete the meta-data below.
|
||||||
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
<meta-data
|
||||||
|
android:name="flutterEmbedding"
|
||||||
|
android:value="2" />
|
||||||
|
</application>
|
||||||
|
<!-- Required to query activities that can process text, see:
|
||||||
|
https://developer.android.com/training/package-visibility and
|
||||||
|
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||||
|
|
||||||
|
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||||
|
<queries>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||||
|
<data android:mimeType="text/plain"/>
|
||||||
|
</intent>
|
||||||
|
</queries>
|
||||||
|
</manifest>
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
package com.example.coffee_at_home
|
||||||
|
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
|
class MainActivity : FlutterActivity()
|
||||||
12
android/app/src/main/res/drawable-v21/launch_background.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="?android:colorBackground" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
||||||
12
android/app/src/main/res/drawable/launch_background.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@android:color/white" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
||||||
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 544 B |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 442 B |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 721 B |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
18
android/app/src/main/res/values-night/styles.xml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
the Flutter engine draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
18
android/app/src/main/res/values/styles.xml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
the Flutter engine draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
7
android/app/src/profile/AndroidManifest.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
||||||
21
android/build.gradle.kts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
|
||||||
|
rootProject.layout.buildDirectory.value(newBuildDir)
|
||||||
|
|
||||||
|
subprojects {
|
||||||
|
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
|
||||||
|
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
||||||
|
}
|
||||||
|
subprojects {
|
||||||
|
project.evaluationDependsOn(":app")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register<Delete>("clean") {
|
||||||
|
delete(rootProject.layout.buildDirectory)
|
||||||
|
}
|
||||||
3
android/gradle.properties
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=true
|
||||||
5
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
|
||||||
25
android/settings.gradle.kts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
pluginManagement {
|
||||||
|
val flutterSdkPath = run {
|
||||||
|
val properties = java.util.Properties()
|
||||||
|
file("local.properties").inputStream().use { properties.load(it) }
|
||||||
|
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
|
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
||||||
|
flutterSdkPath
|
||||||
|
}
|
||||||
|
|
||||||
|
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||||
|
id("com.android.application") version "8.7.3" apply false
|
||||||
|
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
|
||||||
|
}
|
||||||
|
|
||||||
|
include(":app")
|
||||||
51
csv_backups/backup_20250704_103534/Brew_Recipes.csv
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
id,name,servingTemp,milkType,brewMethod,grindSize,coffeeAmount,waterAmount,brewTime,instructions,notes,difficulty,equipmentNeeded,yieldAmount,caffeinePer100ml,waterTemperature,bloomTime,totalExtractionTime,grindToWaterRatio,tags,origin,rating,popularity,createdBy,isPublic,lastModified
|
||||||
|
00ba5a1a-fa7f-4506-83f7-b83cb24f8205,Espresso Brew,Hot,Skim,Espresso,Fine,20.7,252.3,261,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Espresso Machine', 'Grinder', 'Scale']",203.8,45.5,92,47,270,1:16,"['Balanced', 'Rich', 'Fruity']",James Hoffmann,4.2,779,user123,True,2025-04-01
|
||||||
|
4a6f6b52-a51c-413d-a1ef-28c79389b9ea,Pour Over Brew,Cold,Skim,Pour Over,Coarse,24.5,276.0,198,Step-by-step brewing instructions.,Use fresh filtered water.,Intermediate,"['Grinder', 'Espresso Machine', 'Filter']",265.9,55.8,91,21,285,1:16,"['Balanced', 'Fruity', 'Rich']",James Hoffmann,1.9,433,user123,True,2024-09-11
|
||||||
|
dcfb878f-8fe6-4ab9-9cbb-2147b478fdc6,Pour Over Brew,Cold,Coconut,Drip,Medium,20.0,266.2,263,Step-by-step brewing instructions.,Use fresh filtered water.,Intermediate,"['Scale', 'Grinder', 'Kettle']",227.1,67.6,93,20,213,1:16,"['Rich', 'Balanced', 'Fruity']",James Hoffmann,4.1,422,user123,True,2025-04-17
|
||||||
|
758ceee8-b60f-4146-9bb1-698b33397fa2,French Press Brew,Iced,Soy,Pour Over,Fine,15.2,282.3,270,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Grinder', 'Espresso Machine', 'Scale']",283.5,68.3,95,47,240,1:16,"['Balanced', 'Rich', 'Fruity']",James Hoffmann,4.5,376,user123,True,2024-09-24
|
||||||
|
4cb440cf-0ad6-49ae-b5ae-597fac9a902f,Espresso Brew,Hot,Almond,Pour Over,Medium,21.0,205.8,198,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Kettle', 'Scale', 'Espresso Machine']",244.0,68.8,95,41,232,1:16,"['Rich', 'Balanced', 'Fruity']",James Hoffmann,4.2,756,user123,True,2024-07-09
|
||||||
|
86c81bad-9f3b-421c-8fd3-a804e50876d1,Drip Brew,Hot,Soy,Espresso,Coarse,15.6,224.2,234,Step-by-step brewing instructions.,Use fresh filtered water.,Intermediate,"['Kettle', 'Grinder', 'Filter']",273.0,48.1,96,37,222,1:16,"['Fruity', 'Rich', 'Balanced']",James Hoffmann,2.0,741,user123,True,2025-02-25
|
||||||
|
47b9fc53-5610-4b15-a607-f69c6e18d73e,French Press Brew,Iced,Soy,Drip,Medium,20.8,245.4,218,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Espresso Machine', 'Kettle', 'Filter']",235.6,75.6,96,39,283,1:16,"['Balanced', 'Rich', 'Fruity']",James Hoffmann,2.8,748,user123,True,2025-05-12
|
||||||
|
59047b27-9813-4f5b-bf18-2932ceb35123,Pour Over Brew,Iced,Whole,Pour Over,Medium,20.9,206.2,150,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Kettle', 'Filter', 'Espresso Machine']",208.2,51.4,94,27,194,1:16,"['Balanced', 'Fruity', 'Rich']",James Hoffmann,2.1,661,user123,True,2025-03-05
|
||||||
|
8f3da6c5-de6f-4201-b796-487486357d5d,Drip Brew,Cold,Almond,French Press,Medium,15.3,258.2,134,Step-by-step brewing instructions.,Use fresh filtered water.,Intermediate,"['Scale', 'Grinder', 'Filter']",287.3,53.6,95,41,207,1:16,"['Fruity', 'Rich', 'Balanced']",James Hoffmann,2.0,69,user123,True,2025-01-17
|
||||||
|
faafd1f3-dd01-4578-8a38-0b77e1b83202,Pour Over Brew,Hot,Soy,Pour Over,Fine,21.9,230.6,248,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Kettle', 'Espresso Machine', 'Filter']",231.7,50.0,95,36,266,1:16,"['Balanced', 'Fruity', 'Rich']",James Hoffmann,4.7,93,user123,True,2024-10-20
|
||||||
|
20f8d4b1-5ae5-49f1-96be-906fcf82e0e7,French Press Brew,Hot,Whole,Espresso,Medium,21.9,205.1,182,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Espresso Machine', 'Kettle', 'Filter']",272.8,72.7,94,25,290,1:16,"['Balanced', 'Fruity', 'Rich']",James Hoffmann,1.0,515,user123,True,2025-04-26
|
||||||
|
30f6b268-1ba3-4fd6-8eab-50c508fc68a5,French Press Brew,Cold,Soy,Pour Over,Medium,19.4,258.2,190,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Filter', 'Grinder', 'Scale']",210.6,44.7,90,50,242,1:16,"['Balanced', 'Fruity', 'Rich']",James Hoffmann,3.0,718,user123,True,2025-04-17
|
||||||
|
6aa0404a-9f89-4454-a75e-8cf14255b8b4,Pour Over Brew,Cold,Whole,Pour Over,Fine,16.0,245.1,221,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Filter', 'Grinder', 'Espresso Machine']",264.3,65.4,92,21,259,1:16,"['Fruity', 'Balanced', 'Rich']",James Hoffmann,2.9,747,user123,True,2025-04-30
|
||||||
|
8138f04a-eac7-4d24-8b44-c28ccfd883d1,Drip Brew,Hot,Oat,French Press,Medium,20.5,217.7,208,Step-by-step brewing instructions.,Use fresh filtered water.,Intermediate,"['Kettle', 'Scale', 'Filter']",233.5,76.5,94,22,213,1:16,"['Balanced', 'Fruity', 'Rich']",James Hoffmann,2.0,435,user123,True,2025-03-29
|
||||||
|
6ce67a9d-9c76-4763-8fec-e09dd45b917c,Espresso Brew,Hot,Almond,French Press,Coarse,21.2,216.2,144,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Espresso Machine', 'Scale', 'Grinder']",217.2,54.7,94,43,298,1:16,"['Rich', 'Balanced', 'Fruity']",James Hoffmann,4.3,799,user123,True,2025-03-27
|
||||||
|
ed2c15a0-f904-436a-8022-a35ea56adeca,Pour Over Brew,Hot,Skim,Pour Over,Coarse,16.7,294.3,278,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Scale', 'Grinder', 'Filter']",291.9,43.5,95,21,229,1:16,"['Balanced', 'Rich', 'Fruity']",James Hoffmann,4.0,144,user123,True,2025-02-25
|
||||||
|
8b780ba6-9af9-45fc-92f4-d3cf9518ffbc,Espresso Brew,Iced,Oat,Drip,Coarse,17.6,276.3,252,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Kettle', 'Filter', 'Espresso Machine']",242.2,53.6,95,39,206,1:16,"['Balanced', 'Rich', 'Fruity']",James Hoffmann,5.0,891,user123,True,2025-04-04
|
||||||
|
0f75e1c3-d5e1-4774-8f29-a26131ce4567,Drip Brew,Cold,Whole,Espresso,Coarse,19.0,299.8,171,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Espresso Machine', 'Filter', 'Scale']",221.1,55.2,95,59,203,1:16,"['Fruity', 'Balanced', 'Rich']",James Hoffmann,2.4,627,user123,True,2025-05-17
|
||||||
|
10568a68-d7cc-46d0-8ec5-0f9171d973cc,Pour Over Brew,Hot,Soy,Drip,Fine,16.0,276.4,252,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Espresso Machine', 'Grinder', 'Scale']",258.7,60.8,96,26,201,1:16,"['Fruity', 'Balanced', 'Rich']",James Hoffmann,1.0,480,user123,True,2024-12-13
|
||||||
|
e453617a-8d19-456d-8ee4-3170b77f0d46,Drip Brew,Cold,Whole,French Press,Coarse,15.3,215.5,132,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Scale', 'Filter', 'Espresso Machine']",250.7,68.2,91,55,245,1:16,"['Fruity', 'Balanced', 'Rich']",James Hoffmann,3.2,16,user123,True,2024-12-14
|
||||||
|
273fb235-2fe8-4e85-93e2-34bfab3327de,French Press Brew,Hot,Soy,French Press,Medium,20.5,255.2,275,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Filter', 'Scale', 'Espresso Machine']",284.7,57.0,96,20,270,1:16,"['Fruity', 'Balanced', 'Rich']",James Hoffmann,4.6,963,user123,True,2024-07-10
|
||||||
|
23157240-472d-40b0-9412-3e527f06ea96,Pour Over Brew,Cold,Almond,Drip,Fine,18.1,202.7,139,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Espresso Machine', 'Filter', 'Grinder']",228.4,60.7,94,28,272,1:16,"['Fruity', 'Rich', 'Balanced']",James Hoffmann,4.0,197,user123,True,2025-01-28
|
||||||
|
2de45288-81a5-4ee4-b890-592ecfac6fa9,Pour Over Brew,Iced,Soy,Espresso,Medium,16.8,267.2,214,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Grinder', 'Scale', 'Kettle']",241.9,53.8,95,44,276,1:16,"['Balanced', 'Fruity', 'Rich']",James Hoffmann,3.2,210,user123,True,2025-03-03
|
||||||
|
f8fc1981-20fd-4658-a4ff-f7c92b3302e9,French Press Brew,Iced,Almond,Espresso,Medium,21.9,230.1,162,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Espresso Machine', 'Filter', 'Scale']",209.2,70.2,93,50,274,1:16,"['Balanced', 'Fruity', 'Rich']",James Hoffmann,1.4,359,user123,True,2024-10-22
|
||||||
|
b08bfdf8-3f20-4987-893a-bab5a8df51eb,Drip Brew,Iced,Coconut,Espresso,Medium,17.2,211.7,231,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Scale', 'Filter', 'Kettle']",202.8,75.4,94,57,300,1:16,"['Balanced', 'Fruity', 'Rich']",James Hoffmann,3.6,372,user123,True,2024-10-04
|
||||||
|
63b4adf7-791f-4b2f-bce2-c95570c1e475,French Press Brew,Hot,Almond,Drip,Medium,18.0,240.2,289,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Kettle', 'Grinder', 'Espresso Machine']",217.2,60.3,91,59,187,1:16,"['Balanced', 'Rich', 'Fruity']",James Hoffmann,3.3,859,user123,True,2025-05-14
|
||||||
|
331180a0-54a2-4e16-9f49-6a379bfe7a04,Drip Brew,Hot,Oat,Pour Over,Medium,25.0,256.2,214,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Grinder', 'Espresso Machine', 'Scale']",247.7,56.4,94,27,221,1:16,"['Rich', 'Balanced', 'Fruity']",James Hoffmann,1.4,383,user123,True,2024-12-30
|
||||||
|
cba4d200-b318-44e7-9165-f901e07d97fc,Drip Brew,Cold,Coconut,Drip,Medium,18.3,242.2,194,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Scale', 'Filter', 'Kettle']",284.6,64.9,95,56,187,1:16,"['Fruity', 'Rich', 'Balanced']",James Hoffmann,2.5,392,user123,True,2024-09-09
|
||||||
|
9b0c3b3f-d134-487b-a286-50f1d6d22b56,French Press Brew,Cold,Almond,Drip,Fine,23.2,228.1,199,Step-by-step brewing instructions.,Use fresh filtered water.,Intermediate,"['Grinder', 'Kettle', 'Filter']",291.1,66.2,91,58,209,1:16,"['Balanced', 'Rich', 'Fruity']",James Hoffmann,3.7,731,user123,True,2024-09-21
|
||||||
|
eb307dae-ee9e-429d-bb92-cba33510048b,French Press Brew,Cold,Skim,Pour Over,Medium,18.9,248.2,280,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Grinder', 'Filter', 'Kettle']",296.5,72.1,92,24,230,1:16,"['Fruity', 'Balanced', 'Rich']",James Hoffmann,1.7,393,user123,True,2024-09-13
|
||||||
|
4f15a395-c39a-40b3-b79e-7ee5c2a66fae,Drip Brew,Cold,Coconut,Drip,Fine,24.1,272.4,238,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Espresso Machine', 'Kettle', 'Scale']",249.3,65.4,91,45,285,1:16,"['Fruity', 'Balanced', 'Rich']",James Hoffmann,2.4,13,user123,True,2025-04-13
|
||||||
|
5d9c02a6-e537-4a1d-b46f-00a242d26a3c,Espresso Brew,Cold,Oat,Drip,Medium,22.3,252.5,187,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Scale', 'Grinder', 'Filter']",212.4,74.0,90,46,273,1:16,"['Fruity', 'Rich', 'Balanced']",James Hoffmann,3.2,288,user123,True,2024-11-03
|
||||||
|
b2fabec0-d495-4f17-92ee-d0cc9c0ea1c5,French Press Brew,Iced,Skim,Drip,Coarse,16.3,233.9,199,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Kettle', 'Filter', 'Espresso Machine']",244.8,75.5,91,35,190,1:16,"['Balanced', 'Fruity', 'Rich']",James Hoffmann,3.7,465,user123,True,2024-07-28
|
||||||
|
c48b89e9-239d-4027-bc2a-7bae2ec88367,Pour Over Brew,Cold,Whole,Pour Over,Coarse,15.9,220.6,145,Step-by-step brewing instructions.,Use fresh filtered water.,Intermediate,"['Filter', 'Espresso Machine', 'Kettle']",201.4,51.1,96,53,218,1:16,"['Rich', 'Fruity', 'Balanced']",James Hoffmann,3.1,340,user123,True,2024-07-10
|
||||||
|
68c4d331-50c0-4b7a-8a96-4aca650c3c86,Espresso Brew,Hot,Oat,Espresso,Fine,15.2,254.8,132,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Scale', 'Filter', 'Kettle']",267.0,52.0,93,22,253,1:16,"['Fruity', 'Rich', 'Balanced']",James Hoffmann,2.6,682,user123,True,2024-12-15
|
||||||
|
84fdd0f1-dde6-4271-8c22-df92c993baeb,Drip Brew,Hot,Whole,French Press,Medium,24.9,210.8,267,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Filter', 'Espresso Machine', 'Kettle']",217.0,65.5,94,30,272,1:16,"['Fruity', 'Balanced', 'Rich']",James Hoffmann,5.0,927,user123,True,2024-10-18
|
||||||
|
d64efb2c-bd9d-42c4-81ab-e48a08152e1c,Drip Brew,Cold,Whole,French Press,Medium,16.0,228.9,231,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Grinder', 'Filter', 'Kettle']",211.0,78.8,93,40,281,1:16,"['Rich', 'Balanced', 'Fruity']",James Hoffmann,1.7,666,user123,True,2024-10-08
|
||||||
|
4180d9ad-43ee-4bc5-844e-b240ba41f809,Drip Brew,Hot,Soy,Pour Over,Medium,24.1,218.4,138,Step-by-step brewing instructions.,Use fresh filtered water.,Intermediate,"['Grinder', 'Kettle', 'Espresso Machine']",237.1,47.7,96,28,291,1:16,"['Rich', 'Balanced', 'Fruity']",James Hoffmann,2.2,328,user123,True,2025-01-30
|
||||||
|
4558bfb0-fb12-4b57-a969-fd84936c88ae,French Press Brew,Iced,Oat,French Press,Coarse,20.5,213.4,268,Step-by-step brewing instructions.,Use fresh filtered water.,Intermediate,"['Kettle', 'Espresso Machine', 'Filter']",260.3,60.2,92,36,277,1:16,"['Rich', 'Balanced', 'Fruity']",James Hoffmann,1.1,52,user123,True,2025-02-09
|
||||||
|
10e001a7-7458-4b8e-b4e6-f032cc7b36a0,Espresso Brew,Cold,Coconut,Pour Over,Coarse,17.6,216.8,166,Step-by-step brewing instructions.,Use fresh filtered water.,Intermediate,"['Scale', 'Grinder', 'Espresso Machine']",205.5,56.9,92,51,223,1:16,"['Balanced', 'Rich', 'Fruity']",James Hoffmann,2.7,357,user123,True,2024-11-15
|
||||||
|
94968ed4-f573-446e-8036-9450d29a9614,Pour Over Brew,Cold,Whole,Pour Over,Coarse,21.9,237.1,235,Step-by-step brewing instructions.,Use fresh filtered water.,Intermediate,"['Espresso Machine', 'Filter', 'Kettle']",245.5,57.5,94,23,181,1:16,"['Rich', 'Balanced', 'Fruity']",James Hoffmann,4.1,759,user123,True,2024-12-19
|
||||||
|
2c0e107e-6c24-4957-8750-9e8810dd2b23,Espresso Brew,Iced,Pistachio,Pour Over,Coarse,22.5,258.3,270,Step-by-step brewing instructions.,Use fresh filtered water.,Intermediate,"['Espresso Machine', 'Grinder', 'Filter']",261.1,61.5,92,45,268,1:16,"['Rich', 'Fruity', 'Balanced']",James Hoffmann,2.9,645,user123,True,2024-12-26
|
||||||
|
fa1a620d-0401-42fe-90dd-e40aa012ccd9,Espresso Brew,Iced,Oat,French Press,Fine,17.8,233.0,293,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Kettle', 'Espresso Machine', 'Grinder']",243.9,41.3,94,23,300,1:16,"['Rich', 'Balanced', 'Fruity']",James Hoffmann,4.5,499,user123,True,2025-04-13
|
||||||
|
e1eabec5-4e95-4240-9df6-1db5364417d5,French Press Brew,Cold,Coconut,Pour Over,Coarse,19.9,260.4,241,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Espresso Machine', 'Grinder', 'Scale']",268.3,62.9,96,57,260,1:16,"['Balanced', 'Rich', 'Fruity']",James Hoffmann,1.4,684,user123,True,2025-04-06
|
||||||
|
872282ae-a31f-4e3f-a566-915b57a3292f,Drip Brew,Cold,Oat,Pour Over,Coarse,18.0,252.7,300,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Grinder', 'Espresso Machine', 'Filter']",244.3,72.7,92,25,221,1:16,"['Balanced', 'Rich', 'Fruity']",James Hoffmann,1.1,714,user123,True,2024-11-09
|
||||||
|
a2e830bb-d45a-4160-bb5f-45d93cf262ef,Pour Over Brew,Cold,Pistachio,French Press,Fine,20.7,244.3,173,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Kettle', 'Espresso Machine', 'Filter']",270.1,66.1,93,54,294,1:16,"['Balanced', 'Rich', 'Fruity']",James Hoffmann,4.2,781,user123,True,2025-01-05
|
||||||
|
4b5c76c2-1024-4225-8060-01b8a6edb042,Drip Brew,Iced,Skim,Pour Over,Fine,17.4,201.2,156,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Scale', 'Grinder', 'Kettle']",296.2,47.5,92,29,235,1:16,"['Fruity', 'Rich', 'Balanced']",James Hoffmann,3.8,624,user123,True,2024-11-11
|
||||||
|
ecfa9ff0-6fb3-4f57-85c7-aa4eb05a97da,French Press Brew,Cold,Whole,Pour Over,Coarse,16.0,295.7,273,Step-by-step brewing instructions.,Use fresh filtered water.,Intermediate,"['Espresso Machine', 'Grinder', 'Scale']",237.8,78.4,94,49,208,1:16,"['Rich', 'Balanced', 'Fruity']",James Hoffmann,3.2,842,user123,True,2024-07-22
|
||||||
|
f5e83135-365a-4a5c-9204-21e7d9c2b1ca,Pour Over Brew,Hot,Oat,Espresso,Medium,21.8,264.8,175,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Grinder', 'Kettle', 'Scale']",270.9,64.7,93,22,186,1:16,"['Rich', 'Balanced', 'Fruity']",James Hoffmann,2.2,32,user123,True,2024-07-28
|
||||||
|
caf68c12-38f4-4d80-b981-26aae300662d,French Press Brew,Cold,Coconut,Drip,Medium,22.9,277.1,123,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Scale', 'Grinder', 'Kettle']",204.5,75.0,95,38,300,1:16,"['Balanced', 'Fruity', 'Rich']",James Hoffmann,1.3,82,user123,True,2024-07-19
|
||||||
|
51
csv_backups/backup_20250704_103534/Coffee_Beans.csv
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
id,name,origin,farm,producer,varietal,altitude,processingMethod,harvestSeason,flavorNotes,acidity,body,sweetness,roastLevel,cupScore,price,availability,certifications,roaster,roastDate,bestByDate,brewingMethods,isOwned,quantity,notes
|
||||||
|
006cc56c-37c6-41cd-b14d-1b7727459b0a,Single Origin Panama,Ethiopia,Finca Esperanza,Juan Valdez,4cb8c887-67a0-4fcd-a0a2-56e42b1b44b2,1870,Wet-hulled,October-February,"['Chocolate', 'Floral', 'Nutty']",Medium-High,Full,2,Medium-Dark,84.1,14.5,Sold Out,"['Direct Trade', 'Fair Trade', 'Organic']",Onyx Coffee Lab,2024-08-14,2024-07-21,"['Drip', 'Espresso', 'French Press']",False,111.5,Great for espresso and pour over.
|
||||||
|
1779f0d7-4c6a-4fe5-9f6e-6dc1282d7db2,Single Origin Brazil,Ethiopia,Finca Esperanza,Juan Valdez,defaec07-2383-42b2-9cb7-b7d1534adc36,2050,Natural,October-February,"['Spicy', 'Berry', 'Chocolate']",Medium,Full,1,Light,91.6,14.2,Available,"['Organic', 'Rainforest Alliance', 'Direct Trade']",Onyx Coffee Lab,2024-09-05,2025-06-02,"['Pour Over', 'Drip', 'French Press']",False,276.9,Great for espresso and pour over.
|
||||||
|
c3f9c312-6626-4855-ab7e-46441c0ae019,Single Origin Yemen,Panama,Finca Esperanza,Juan Valdez,cb9ffb87-251f-42e1-a64e-eb8fbb1883a5,1367,Semi-washed,October-February,"['Fruity', 'Chocolate', 'Citrus']",High,Medium-Light,10,Light,89.5,17.4,Seasonal,"['Fair Trade', 'Direct Trade', 'Rainforest Alliance']",Onyx Coffee Lab,2024-11-02,2025-06-01,"['Drip', 'Espresso', 'French Press']",False,462.7,Great for espresso and pour over.
|
||||||
|
656c7024-d68d-4c55-907a-e45ce246f4b5,Single Origin Honduras,Panama,Finca Esperanza,Juan Valdez,dc90c31d-5184-4f81-9fa9-e53a219366f1,1302,Natural,October-February,"['Floral', 'Citrus', 'Berry']",Medium-High,Medium-Light,7,Medium,85.9,15.3,Sold Out,"['Rainforest Alliance', 'Organic', 'Fair Trade']",Onyx Coffee Lab,2024-08-06,2024-09-28,"['French Press', 'Espresso', 'Drip']",True,135.2,Great for espresso and pour over.
|
||||||
|
1aae3999-0487-46b7-a45d-8bdb92ff4fb7,Single Origin Yemen,Yemen,Finca Esperanza,Juan Valdez,1db22ac4-4abe-4608-ba28-91de51598a8b,1717,Washed,October-February,"['Fruity', 'Floral', 'Berry']",Medium-High,Medium-Full,8,Dark,83.0,30.0,Sold Out,"['Fair Trade', 'Rainforest Alliance', 'Direct Trade']",Onyx Coffee Lab,2024-10-23,2025-05-21,"['Pour Over', 'French Press', 'Espresso']",True,230.2,Great for espresso and pour over.
|
||||||
|
b5ea3e07-5ce1-41dd-87d5-a3f1fcedef4a,Single Origin Honduras,Panama,Finca Esperanza,Juan Valdez,b5098c67-16b0-4e25-8569-98b2df912df4,1785,Wet-hulled,October-February,"['Caramel', 'Floral', 'Berry']",Medium-High,Medium-Light,3,Medium-Dark,91.7,23.9,Seasonal,"['Rainforest Alliance', 'Direct Trade', 'Fair Trade']",Onyx Coffee Lab,2024-09-16,2024-12-31,"['Espresso', 'Drip', 'Pour Over']",True,145.3,Great for espresso and pour over.
|
||||||
|
81364c47-3c1a-46e0-a163-17bf00dca3f6,Single Origin Ethiopia,Kenya,Finca Esperanza,Juan Valdez,25864ddb-5277-4be3-9c1b-e84f8c42697a,1397,Washed,October-February,"['Citrus', 'Spicy', 'Berry']",Medium-High,Medium-Light,2,Medium,88.8,28.7,Seasonal,"['Direct Trade', 'Organic', 'Fair Trade']",Onyx Coffee Lab,2024-08-04,2025-01-14,"['French Press', 'Drip', 'Pour Over']",True,152.9,Great for espresso and pour over.
|
||||||
|
8e7edbb6-b950-41e4-90c2-5d87cbc66146,Single Origin Panama,Kenya,Finca Esperanza,Juan Valdez,1bf9739d-ac22-40ac-844f-fe7e7db39e76,1591,Wet-hulled,October-February,"['Chocolate', 'Spicy', 'Fruity']",Medium,Medium-Light,9,Dark,88.2,15.5,Limited,"['Direct Trade', 'Organic', 'Fair Trade']",Onyx Coffee Lab,2025-05-03,2025-02-23,"['French Press', 'Drip', 'Espresso']",True,249.4,Great for espresso and pour over.
|
||||||
|
e5122acf-4b5b-4393-979d-f1c048116040,Single Origin Honduras,Colombia,Finca Esperanza,Juan Valdez,1e1811a3-b567-47c0-b8ce-a720010cd280,1662,Honey,October-February,"['Chocolate', 'Citrus', 'Fruity']",Medium,Medium,3,Medium-Dark,92.5,10.2,Available,"['Rainforest Alliance', 'Fair Trade', 'Organic']",Onyx Coffee Lab,2024-10-13,2024-12-05,"['French Press', 'Drip', 'Pour Over']",False,64.8,Great for espresso and pour over.
|
||||||
|
9b9cb407-250d-4e3a-9854-860ce72e46c8,Single Origin Costa Rica,Jamaica,Finca Esperanza,Juan Valdez,35aa7bc2-2ebc-4136-b028-90c6e6eccbfd,1491,Natural,October-February,"['Berry', 'Caramel', 'Fruity']",Medium-High,Medium-Light,6,Medium-Dark,81.5,10.7,Seasonal,"['Direct Trade', 'Organic', 'Fair Trade']",Onyx Coffee Lab,2024-10-22,2025-06-13,"['Pour Over', 'French Press', 'Drip']",True,323.2,Great for espresso and pour over.
|
||||||
|
6bc4d458-6039-4b4a-a1aa-7073a419101b,Single Origin Costa Rica,Ethiopia,Finca Esperanza,Juan Valdez,176ed0db-b8b0-4572-a5d3-f770170f78b1,1255,Washed,October-February,"['Caramel', 'Chocolate', 'Spicy']",High,Full,3,Medium-Dark,85.9,16.6,Available,"['Organic', 'Direct Trade', 'Rainforest Alliance']",Onyx Coffee Lab,2025-01-23,2024-09-21,"['Pour Over', 'Drip', 'French Press']",False,164.3,Great for espresso and pour over.
|
||||||
|
736a8934-4109-48d9-bf93-4c83c1090754,Single Origin Jamaica,Honduras,Finca Esperanza,Juan Valdez,911a40dd-7f2f-4730-8089-b737c8a8f2de,1724,Semi-washed,October-February,"['Floral', 'Nutty', 'Citrus']",Low,Full,4,Medium,92.0,16.6,Limited,"['Organic', 'Fair Trade', 'Direct Trade']",Onyx Coffee Lab,2024-10-19,2025-06-04,"['Pour Over', 'Drip', 'French Press']",True,366.9,Great for espresso and pour over.
|
||||||
|
df66be3f-ecee-47e0-9ca2-c420ab21e70a,Single Origin Jamaica,Ethiopia,Finca Esperanza,Juan Valdez,4912d9f9-cb2a-41ce-a4dc-879f347b9bb5,1475,Natural,October-February,"['Fruity', 'Berry', 'Floral']",Medium,Medium-Light,5,Light,82.7,17.1,Limited,"['Fair Trade', 'Rainforest Alliance', 'Organic']",Onyx Coffee Lab,2024-12-29,2025-03-20,"['French Press', 'Espresso', 'Pour Over']",True,453.5,Great for espresso and pour over.
|
||||||
|
5e54067b-d2ba-45be-b398-c989217643da,Single Origin Colombia,Jamaica,Finca Esperanza,Juan Valdez,f72494df-f47e-4a47-86dd-2faf385c9200,1716,Wet-hulled,October-February,"['Fruity', 'Citrus', 'Berry']",Low,Medium-Light,6,Medium-Light,84.8,15.2,Limited,"['Direct Trade', 'Organic', 'Rainforest Alliance']",Onyx Coffee Lab,2025-06-06,2024-07-15,"['Espresso', 'French Press', 'Pour Over']",False,413.7,Great for espresso and pour over.
|
||||||
|
fb4af9c3-0ed3-4b46-9444-cb3d26cf30d4,Single Origin Ethiopia,Honduras,Finca Esperanza,Juan Valdez,11a7e600-7861-49ea-9536-fa67c689f63a,1356,Washed,October-February,"['Chocolate', 'Nutty', 'Fruity']",High,Light,1,Medium-Light,82.8,12.4,Sold Out,"['Fair Trade', 'Direct Trade', 'Rainforest Alliance']",Onyx Coffee Lab,2025-04-24,2024-07-10,"['Pour Over', 'Drip', 'Espresso']",True,402.2,Great for espresso and pour over.
|
||||||
|
bcb4d8a6-6b86-4863-8900-c22994993e6f,Single Origin Costa Rica,Honduras,Finca Esperanza,Juan Valdez,c90b8225-53d1-4280-bba9-0c4133b4b975,1713,Semi-washed,October-February,"['Chocolate', 'Caramel', 'Fruity']",Medium,Medium,8,Medium,94.7,20.4,Sold Out,"['Fair Trade', 'Organic', 'Rainforest Alliance']",Onyx Coffee Lab,2024-11-27,2024-07-05,"['Pour Over', 'French Press', 'Drip']",True,473.7,Great for espresso and pour over.
|
||||||
|
78ecb4d2-4990-4558-a30c-7ade7723d4a7,Single Origin Costa Rica,Panama,Finca Esperanza,Juan Valdez,5d6feb96-4633-43b9-b44f-6579418c5994,1754,Washed,October-February,"['Floral', 'Berry', 'Citrus']",Low,Medium-Full,10,Medium,93.8,15.1,Available,"['Direct Trade', 'Rainforest Alliance', 'Organic']",Onyx Coffee Lab,2024-07-12,2025-06-13,"['Drip', 'Espresso', 'French Press']",False,437.7,Great for espresso and pour over.
|
||||||
|
27528cec-6823-4125-bd06-c36382d5d0ea,Single Origin Guatemala,Brazil,Finca Esperanza,Juan Valdez,32cfc671-0ec5-49ac-b507-894776fd3f11,1982,Semi-washed,October-February,"['Spicy', 'Citrus', 'Fruity']",Medium-Low,Full,8,Medium-Dark,94.3,21.5,Sold Out,"['Organic', 'Direct Trade', 'Rainforest Alliance']",Onyx Coffee Lab,2024-07-18,2024-07-25,"['Drip', 'Pour Over', 'French Press']",False,329.1,Great for espresso and pour over.
|
||||||
|
4600df1d-deb8-495e-b121-663fee586a0f,Single Origin Brazil,Costa Rica,Finca Esperanza,Juan Valdez,a692d28b-de2f-4a6e-b056-b34ffa8abedb,1521,Natural,October-February,"['Chocolate', 'Berry', 'Floral']",Low,Medium-Full,2,Medium-Light,90.8,16.1,Limited,"['Fair Trade', 'Organic', 'Direct Trade']",Onyx Coffee Lab,2025-01-27,2025-04-26,"['Espresso', 'Pour Over', 'Drip']",True,132.0,Great for espresso and pour over.
|
||||||
|
f43b95a1-47c4-48df-83a9-5bf66008d7ff,Single Origin Jamaica,Panama,Finca Esperanza,Juan Valdez,d3858040-88a3-48dc-a5f9-a372619a1ef1,1571,Wet-hulled,October-February,"['Chocolate', 'Fruity', 'Nutty']",Medium,Medium-Light,8,Medium,93.5,28.8,Seasonal,"['Fair Trade', 'Rainforest Alliance', 'Organic']",Onyx Coffee Lab,2025-03-19,2024-10-06,"['Espresso', 'Drip', 'Pour Over']",True,419.3,Great for espresso and pour over.
|
||||||
|
41cbf6a3-12b5-4c5f-a2dd-cd2d1dde14b0,Single Origin Honduras,Colombia,Finca Esperanza,Juan Valdez,8998ecd3-426e-4875-bd97-44cf0d16126c,1578,Semi-washed,October-February,"['Berry', 'Floral', 'Chocolate']",Medium,Medium,7,Light,91.9,29.8,Sold Out,"['Rainforest Alliance', 'Organic', 'Direct Trade']",Onyx Coffee Lab,2024-11-29,2024-11-05,"['Drip', 'Pour Over', 'Espresso']",True,336.1,Great for espresso and pour over.
|
||||||
|
d590fe31-383d-4f78-809e-b2cd1f758c02,Single Origin Guatemala,Honduras,Finca Esperanza,Juan Valdez,c5986848-f4f5-4623-bcf0-1c8089cb59df,1796,Semi-washed,October-February,"['Caramel', 'Nutty', 'Chocolate']",Medium-Low,Full,4,Medium-Dark,86.7,21.0,Available,"['Organic', 'Fair Trade', 'Rainforest Alliance']",Onyx Coffee Lab,2025-06-16,2024-10-03,"['French Press', 'Espresso', 'Drip']",False,219.9,Great for espresso and pour over.
|
||||||
|
14f444a1-4e1d-4c22-867f-25d4931149fc,Single Origin Jamaica,Honduras,Finca Esperanza,Juan Valdez,b5fc0ec8-cb76-4a56-ae5d-a751374d7be9,1594,Natural,October-February,"['Nutty', 'Chocolate', 'Citrus']",Medium,Medium,9,Dark,83.8,13.5,Seasonal,"['Direct Trade', 'Rainforest Alliance', 'Organic']",Onyx Coffee Lab,2025-05-18,2025-05-30,"['French Press', 'Pour Over', 'Espresso']",False,494.7,Great for espresso and pour over.
|
||||||
|
729c3a6c-b922-4a1b-8bea-b998ae944429,Single Origin Jamaica,Ethiopia,Finca Esperanza,Juan Valdez,71d5b237-37b7-412a-86f9-8cf2120cc6c0,1611,Washed,October-February,"['Nutty', 'Berry', 'Caramel']",Medium,Full,1,Medium,87.5,20.4,Sold Out,"['Direct Trade', 'Organic', 'Fair Trade']",Onyx Coffee Lab,2025-05-02,2025-06-06,"['Drip', 'Espresso', 'Pour Over']",False,66.4,Great for espresso and pour over.
|
||||||
|
2a59bf8c-b3e5-41ca-8dfe-0356422b7422,Single Origin Costa Rica,Honduras,Finca Esperanza,Juan Valdez,bf45f0d8-3ca7-41a7-a3b9-85cb33d827f0,1290,Semi-washed,October-February,"['Chocolate', 'Citrus', 'Fruity']",High,Medium,7,Medium,86.8,23.6,Available,"['Organic', 'Direct Trade', 'Rainforest Alliance']",Onyx Coffee Lab,2025-06-02,2025-04-06,"['French Press', 'Drip', 'Espresso']",True,333.1,Great for espresso and pour over.
|
||||||
|
4cc02da6-8e47-4f33-a909-12b6bc77bf9c,Single Origin Honduras,Ethiopia,Finca Esperanza,Juan Valdez,1f73a289-b3a0-4098-af8f-f34cb9af4149,1305,Honey,October-February,"['Nutty', 'Fruity', 'Floral']",Medium-High,Light,3,Medium,89.1,30.0,Available,"['Organic', 'Fair Trade', 'Rainforest Alliance']",Onyx Coffee Lab,2024-07-10,2024-07-28,"['Drip', 'Pour Over', 'French Press']",True,491.1,Great for espresso and pour over.
|
||||||
|
cdaa60df-8fea-44e8-8902-f94b594433a3,Single Origin Yemen,Guatemala,Finca Esperanza,Juan Valdez,67274ddf-a2d8-4105-85b3-ea51e2c6fe5c,2035,Washed,October-February,"['Chocolate', 'Fruity', 'Floral']",Medium-High,Full,9,Medium-Dark,92.3,29.5,Limited,"['Organic', 'Rainforest Alliance', 'Fair Trade']",Onyx Coffee Lab,2025-02-11,2024-12-15,"['Pour Over', 'Espresso', 'Drip']",True,164.1,Great for espresso and pour over.
|
||||||
|
5f4a4c34-7695-403a-995c-3b26309cc617,Single Origin Colombia,Guatemala,Finca Esperanza,Juan Valdez,cde7eef0-1cce-418f-a2b3-403e856573de,1849,Washed,October-February,"['Citrus', 'Spicy', 'Caramel']",High,Medium,8,Dark,93.0,15.7,Sold Out,"['Fair Trade', 'Organic', 'Rainforest Alliance']",Onyx Coffee Lab,2025-02-24,2024-09-17,"['Pour Over', 'Drip', 'Espresso']",True,344.5,Great for espresso and pour over.
|
||||||
|
8438ed6c-c20a-4343-9105-18d451c7de82,Single Origin Brazil,Jamaica,Finca Esperanza,Juan Valdez,734335e8-9379-4b47-9490-5b7500c1bc15,1830,Wet-hulled,October-February,"['Chocolate', 'Citrus', 'Floral']",Low,Medium,4,Dark,88.2,15.2,Sold Out,"['Rainforest Alliance', 'Direct Trade', 'Organic']",Onyx Coffee Lab,2024-12-16,2024-10-29,"['Pour Over', 'Espresso', 'French Press']",False,401.8,Great for espresso and pour over.
|
||||||
|
1f75f995-88dc-45cf-9c50-652ea77503a6,Single Origin Brazil,Yemen,Finca Esperanza,Juan Valdez,cadd51b2-8f05-4f8e-a8d9-0855a65a17cf,1921,Honey,October-February,"['Citrus', 'Fruity', 'Caramel']",High,Medium-Light,10,Light,83.9,26.1,Available,"['Organic', 'Direct Trade', 'Fair Trade']",Onyx Coffee Lab,2025-06-26,2024-08-20,"['Drip', 'Pour Over', 'French Press']",False,163.5,Great for espresso and pour over.
|
||||||
|
59c0cef9-aebb-4d01-bfb5-743cca07be36,Single Origin Guatemala,Jamaica,Finca Esperanza,Juan Valdez,42ff6b25-902c-471d-a3f7-89475d6b8de4,2067,Semi-washed,October-February,"['Caramel', 'Nutty', 'Spicy']",Medium-High,Light,3,Light,90.8,21.7,Seasonal,"['Fair Trade', 'Direct Trade', 'Rainforest Alliance']",Onyx Coffee Lab,2024-08-17,2025-05-27,"['Drip', 'French Press', 'Pour Over']",False,137.4,Great for espresso and pour over.
|
||||||
|
6ca36fab-cb8a-4806-8ee2-f313fd2faa28,Single Origin Kenya,Ethiopia,Finca Esperanza,Juan Valdez,acbf9271-9c75-457c-b69d-6e1eee47aec9,1352,Honey,October-February,"['Spicy', 'Nutty', 'Caramel']",High,Medium-Full,10,Medium-Light,84.4,10.0,Available,"['Rainforest Alliance', 'Direct Trade', 'Fair Trade']",Onyx Coffee Lab,2025-03-11,2024-11-10,"['Pour Over', 'French Press', 'Espresso']",True,464.6,Great for espresso and pour over.
|
||||||
|
cfeac1a5-b263-465a-b48a-ed16091db208,Single Origin Ethiopia,Costa Rica,Finca Esperanza,Juan Valdez,5669dad0-9a6f-469e-b00e-216cec82b9c9,1402,Wet-hulled,October-February,"['Citrus', 'Chocolate', 'Caramel']",Low,Medium-Full,10,Medium-Light,90.2,18.1,Sold Out,"['Organic', 'Direct Trade', 'Fair Trade']",Onyx Coffee Lab,2024-09-15,2024-11-06,"['French Press', 'Pour Over', 'Espresso']",False,243.8,Great for espresso and pour over.
|
||||||
|
c6d7471a-d323-4328-8265-8bd46f3cea65,Single Origin Kenya,Yemen,Finca Esperanza,Juan Valdez,fd9c9ed4-9c90-4606-98ec-7a12b1e6e546,1542,Washed,October-February,"['Citrus', 'Nutty', 'Floral']",Medium-High,Full,6,Dark,90.9,13.3,Limited,"['Organic', 'Direct Trade', 'Rainforest Alliance']",Onyx Coffee Lab,2025-02-19,2024-07-21,"['Pour Over', 'French Press', 'Drip']",True,210.4,Great for espresso and pour over.
|
||||||
|
ac77a9d6-4c03-421a-88cd-b443f9f0f697,Single Origin Ethiopia,Ethiopia,Finca Esperanza,Juan Valdez,ee28a942-a5d2-4234-a448-e19832b20e41,1244,Semi-washed,October-February,"['Chocolate', 'Citrus', 'Berry']",Medium,Medium-Full,7,Medium,82.0,13.5,Sold Out,"['Rainforest Alliance', 'Fair Trade', 'Organic']",Onyx Coffee Lab,2024-12-24,2024-09-27,"['French Press', 'Pour Over', 'Drip']",True,268.0,Great for espresso and pour over.
|
||||||
|
01797a60-2bdf-4107-954d-f113afe955fa,Single Origin Guatemala,Honduras,Finca Esperanza,Juan Valdez,0aa366aa-4e45-443e-99b9-dfd53b8a8c28,1492,Natural,October-February,"['Spicy', 'Nutty', 'Chocolate']",High,Medium-Full,1,Medium-Light,91.8,10.2,Limited,"['Organic', 'Direct Trade', 'Rainforest Alliance']",Onyx Coffee Lab,2025-03-17,2024-12-07,"['Espresso', 'French Press', 'Pour Over']",True,171.5,Great for espresso and pour over.
|
||||||
|
15f6b982-a0f3-40bd-986e-d2278621d200,Single Origin Costa Rica,Honduras,Finca Esperanza,Juan Valdez,aea4bf64-96f9-4f48-8c00-0fa52eb259c9,1618,Washed,October-February,"['Nutty', 'Citrus', 'Berry']",Low,Medium-Full,1,Light,88.2,12.7,Limited,"['Direct Trade', 'Organic', 'Fair Trade']",Onyx Coffee Lab,2024-11-30,2024-08-15,"['French Press', 'Espresso', 'Drip']",True,378.5,Great for espresso and pour over.
|
||||||
|
9691703e-02fa-43b9-abbe-74cf12ccfe54,Single Origin Costa Rica,Brazil,Finca Esperanza,Juan Valdez,039a57ff-5e78-4942-a29d-297da50acfa9,1228,Natural,October-February,"['Caramel', 'Floral', 'Spicy']",High,Medium,1,Medium,93.0,28.1,Limited,"['Organic', 'Fair Trade', 'Rainforest Alliance']",Onyx Coffee Lab,2025-02-10,2025-06-01,"['Espresso', 'French Press', 'Drip']",True,320.5,Great for espresso and pour over.
|
||||||
|
d51e1935-bc10-4a1f-9d51-afc99ed85400,Single Origin Ethiopia,Ethiopia,Finca Esperanza,Juan Valdez,988705f9-8b17-459a-ba75-10bedaa5176c,1519,Washed,October-February,"['Nutty', 'Floral', 'Berry']",Medium-High,Light,7,Medium-Light,85.0,26.6,Available,"['Rainforest Alliance', 'Organic', 'Fair Trade']",Onyx Coffee Lab,2025-03-18,2025-06-07,"['Pour Over', 'Espresso', 'Drip']",False,305.5,Great for espresso and pour over.
|
||||||
|
51fbe353-964b-4f59-af25-05b7f6cd6ee1,Single Origin Brazil,Guatemala,Finca Esperanza,Juan Valdez,d0b12210-ee55-4805-a2af-1826e9c00c50,1495,Semi-washed,October-February,"['Berry', 'Floral', 'Chocolate']",Medium-High,Medium,7,Medium,82.5,28.0,Sold Out,"['Direct Trade', 'Organic', 'Rainforest Alliance']",Onyx Coffee Lab,2024-11-18,2024-09-05,"['Pour Over', 'Espresso', 'Drip']",False,261.8,Great for espresso and pour over.
|
||||||
|
2a209c31-fdd3-49ca-97b0-77d92bbc8d23,Single Origin Brazil,Ethiopia,Finca Esperanza,Juan Valdez,846ec766-cbbf-449c-bfc9-275bed60d45d,1421,Wet-hulled,October-February,"['Caramel', 'Fruity', 'Floral']",High,Medium-Full,2,Light,89.2,18.6,Available,"['Direct Trade', 'Organic', 'Fair Trade']",Onyx Coffee Lab,2024-10-03,2025-01-17,"['Drip', 'Pour Over', 'Espresso']",False,67.7,Great for espresso and pour over.
|
||||||
|
162db465-308b-4b76-b120-05b616be8fcb,Single Origin Yemen,Kenya,Finca Esperanza,Juan Valdez,45b7966b-5c8e-4ff7-a4b7-206cc42388fa,1868,Natural,October-February,"['Berry', 'Spicy', 'Fruity']",High,Light,8,Light,81.6,11.2,Available,"['Organic', 'Fair Trade', 'Direct Trade']",Onyx Coffee Lab,2025-01-02,2025-05-24,"['Pour Over', 'Espresso', 'Drip']",True,151.1,Great for espresso and pour over.
|
||||||
|
46307722-bada-4b6b-93f8-cc89e55583ea,Single Origin Jamaica,Guatemala,Finca Esperanza,Juan Valdez,a6f896d8-c7e5-40d2-876d-9b711488ced2,1740,Natural,October-February,"['Spicy', 'Nutty', 'Chocolate']",Medium,Full,1,Dark,91.7,16.9,Available,"['Rainforest Alliance', 'Direct Trade', 'Fair Trade']",Onyx Coffee Lab,2024-11-17,2025-06-16,"['Espresso', 'French Press', 'Pour Over']",False,295.1,Great for espresso and pour over.
|
||||||
|
87716b81-baa4-4727-bb21-d2aca8cbc08b,Single Origin Guatemala,Kenya,Finca Esperanza,Juan Valdez,1ec6883d-857b-4a7b-8175-cff999023659,1862,Wet-hulled,October-February,"['Caramel', 'Floral', 'Chocolate']",High,Light,9,Dark,80.5,14.1,Sold Out,"['Direct Trade', 'Organic', 'Fair Trade']",Onyx Coffee Lab,2024-12-17,2025-03-25,"['Pour Over', 'Espresso', 'Drip']",True,76.3,Great for espresso and pour over.
|
||||||
|
8f52dd60-8b5c-4cce-a939-7699d6a86cff,Single Origin Kenya,Kenya,Finca Esperanza,Juan Valdez,a7cff0fc-c150-4860-ba2a-7afcc72cad2d,1803,Wet-hulled,October-February,"['Caramel', 'Berry', 'Floral']",Medium-High,Medium-Light,9,Medium-Light,80.1,11.7,Seasonal,"['Direct Trade', 'Rainforest Alliance', 'Fair Trade']",Onyx Coffee Lab,2024-10-03,2024-07-16,"['Drip', 'French Press', 'Pour Over']",False,433.5,Great for espresso and pour over.
|
||||||
|
17f15fb4-4b5d-4d9d-8344-113cfab461ee,Single Origin Jamaica,Jamaica,Finca Esperanza,Juan Valdez,72cc1c60-4137-41e0-8cf2-79dbb2102016,1788,Washed,October-February,"['Chocolate', 'Spicy', 'Nutty']",Low,Full,3,Light,82.5,16.3,Seasonal,"['Direct Trade', 'Organic', 'Fair Trade']",Onyx Coffee Lab,2025-01-27,2025-05-12,"['French Press', 'Pour Over', 'Espresso']",False,495.8,Great for espresso and pour over.
|
||||||
|
64065c4d-8d01-4a0a-b4ed-249c1b7fcad4,Single Origin Jamaica,Guatemala,Finca Esperanza,Juan Valdez,35fc6bcc-ba65-4ac7-bfa4-5518a15f14b8,1430,Semi-washed,October-February,"['Floral', 'Caramel', 'Fruity']",Medium-Low,Medium,10,Dark,81.3,28.6,Available,"['Organic', 'Rainforest Alliance', 'Direct Trade']",Onyx Coffee Lab,2025-01-02,2025-04-03,"['Pour Over', 'Drip', 'Espresso']",False,207.7,Great for espresso and pour over.
|
||||||
|
82c0f214-4a33-4f4d-a86e-beff1170a887,Single Origin Jamaica,Yemen,Finca Esperanza,Juan Valdez,11492b6f-d649-4a6f-8efd-c62b66f45258,1391,Natural,October-February,"['Floral', 'Fruity', 'Caramel']",Low,Medium,4,Medium,88.2,15.2,Limited,"['Fair Trade', 'Organic', 'Direct Trade']",Onyx Coffee Lab,2025-03-08,2025-03-22,"['Drip', 'Espresso', 'French Press']",False,257.5,Great for espresso and pour over.
|
||||||
|
f42664e3-0e7a-48b7-bba6-0ea8c63e29db,Single Origin Colombia,Panama,Finca Esperanza,Juan Valdez,1dd3c6c7-99a3-467a-8e86-05b9b57340cf,1215,Natural,October-February,"['Caramel', 'Nutty', 'Citrus']",Medium,Medium-Light,8,Medium-Dark,94.4,23.7,Available,"['Direct Trade', 'Fair Trade', 'Rainforest Alliance']",Onyx Coffee Lab,2025-04-22,2025-05-04,"['Pour Over', 'Espresso', 'Drip']",False,205.8,Great for espresso and pour over.
|
||||||
|
b7eedb8b-e4f0-4b8c-b560-4f01627016aa,Single Origin Brazil,Honduras,Finca Esperanza,Juan Valdez,205ae039-3f8c-47e8-a5d9-2e8f5e01babe,1280,Wet-hulled,October-February,"['Nutty', 'Citrus', 'Caramel']",Low,Medium-Light,1,Light,89.4,25.7,Available,"['Rainforest Alliance', 'Organic', 'Fair Trade']",Onyx Coffee Lab,2024-12-19,2024-07-07,"['Espresso', 'Pour Over', 'Drip']",False,457.9,Great for espresso and pour over.
|
||||||
|
51
csv_backups/backup_20250704_103534/Coffee_Machines.csv
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
id,manufacturer,year,model,type,steamWand,details,isOwned,rating,popularity,portafilters,specifications
|
||||||
|
9f68fe90-a40c-4de9-bae5-cbd8350f15d9,La Marzocco,2017,Model 584,Drip,True,"Stainless steel body, user-friendly controls.",False,2.4,100,"[{'id': '2369181c-df26-4b1e-98cb-e2215b0a5871', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
96fbb2fc-c139-43b8-b285-6d8c70408696,Rocket,2021,Model 811,E61,False,"Stainless steel body, user-friendly controls.",False,3.0,100,"[{'id': '9135f83b-138a-45be-b56e-b029f9658434', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
07657e06-2b49-4c53-9d8e-5c0298eed45a,Gaggia,2016,Model 425,E61,True,"Stainless steel body, user-friendly controls.",False,3.4,45,"[{'id': '933851e9-3147-4e86-a5bb-111398063395', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
4e1be4f7-7611-4585-bd86-cab2736722a1,Rocket,2020,Model 922,Espresso Pod,False,"Stainless steel body, user-friendly controls.",True,4.4,16,"[{'id': 'c149dc17-e931-4142-8043-3629a191b767', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
da5ce31d-1b34-4747-bc7f-20d8a7a266b3,Rancilio,2022,Model 877,French Press,True,"Stainless steel body, user-friendly controls.",False,4.7,76,"[{'id': '5ace346d-8a77-4e77-ad39-84095472d306', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
31aed043-2d87-40f0-a824-90bc863e49b4,Gaggia,2020,Model 971,Espresso Pod,False,"Stainless steel body, user-friendly controls.",False,2.6,48,"[{'id': '3ea2bb4f-56b8-4e98-8ede-e75ab7e8f56a', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
7e933369-4160-4c10-bfdd-ab2624dac1d0,La Marzocco,2020,Model 941,Pod,False,"Stainless steel body, user-friendly controls.",False,4.5,45,"[{'id': 'f83cffe2-11f8-44b5-84cb-e52f916b73c8', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
06051aee-61bf-4a59-97db-e4799beef357,Gaggia,2019,Model 901,Grinder,False,"Stainless steel body, user-friendly controls.",True,3.2,75,"[{'id': 'f9c154cd-2d25-4134-a3ea-425cf8851dfe', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
3f394f15-fef5-41d7-8b0a-8c6780be5a69,Rocket,2020,Model 195,Grinder,False,"Stainless steel body, user-friendly controls.",False,2.8,30,"[{'id': 'ba198010-6987-47d0-be75-550f979ab542', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
f7a28b47-4484-47e9-b55b-3040edb53b6c,DeLonghi,2022,Model 147,Espresso,False,"Stainless steel body, user-friendly controls.",False,2.9,48,"[{'id': '78fc9726-b016-4510-9d1b-d5f20c7137a3', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
272055c6-42bf-447b-bf3b-fc49517a1a9f,Rocket,2021,Model 614,Espresso Pod,True,"Stainless steel body, user-friendly controls.",True,1.5,69,"[{'id': '9272d327-1c7c-4215-9d48-4fd04cecd137', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
2495a0e6-4cd9-43e1-8e73-a6a058de9177,Gaggia,2022,Model 294,Cold Brew,False,"Stainless steel body, user-friendly controls.",False,1.5,19,"[{'id': 'b119011f-d54c-46b3-b818-7427e1ab03e8', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
f19bd621-fbf9-4397-a94f-7e0568bf3b73,La Marzocco,2015,Model 415,Grinder,False,"Stainless steel body, user-friendly controls.",False,3.0,54,"[{'id': 'edc74dab-9502-42a3-a5b2-92bea4d2dfef', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
c0efb75b-c072-4dc4-9b67-01c06ef5e7a5,DeLonghi,2018,Model 632,Drip,False,"Stainless steel body, user-friendly controls.",False,1.3,41,"[{'id': 'bc216970-410e-47c6-ad2f-bb1422d3f9ec', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
ecc282ec-2857-48a7-bb0b-cd3eb3990f20,Breville,2018,Model 606,Cold Brew,False,"Stainless steel body, user-friendly controls.",False,2.9,46,"[{'id': 'c60bdad5-d5d0-4abb-b34c-ace1abb868a5', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
145eead8-b9c4-4d2e-a031-e7c488525eaa,Breville,2020,Model 180,Grinder,True,"Stainless steel body, user-friendly controls.",True,4.3,28,"[{'id': 'fb42d62e-2310-4c80-a721-5e2ebbf97ec2', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
b9a9f96c-1ba2-4bff-8de1-9230bc467d43,Rancilio,2016,Model 414,Drip,False,"Stainless steel body, user-friendly controls.",False,1.3,87,"[{'id': 'ee9cf949-b7fa-4afd-908a-e9e2167a718e', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
fa4b4ade-4fab-4428-b392-87fc331b86d0,Breville,2017,Model 794,Espresso,True,"Stainless steel body, user-friendly controls.",False,4.9,13,"[{'id': '2a9da80b-e6db-44cd-86ab-6d92ea3fc99b', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
4e1d352e-677d-4578-8adb-44c92245bcfb,DeLonghi,2021,Model 349,Percolation,False,"Stainless steel body, user-friendly controls.",True,4.4,57,"[{'id': '925d6f92-2455-41c1-8c1c-46c4112bcffc', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
eb5362c0-7131-4731-b4b7-e0f3df3c26b9,Breville,2022,Model 914,Espresso Pod,False,"Stainless steel body, user-friendly controls.",False,1.3,6,"[{'id': 'a22e10b8-4ec2-47ed-9702-2528f9fb803a', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
2d0bbbf5-7457-40f3-9836-4b81db57ed74,Rancilio,2019,Model 162,Percolation,False,"Stainless steel body, user-friendly controls.",False,1.0,66,"[{'id': '798a042e-3926-4f9d-b38c-511644be3a01', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
949bd3b3-599e-4f65-90db-d41e76556c46,Breville,2021,Model 195,Cold Brew,False,"Stainless steel body, user-friendly controls.",True,2.6,77,"[{'id': 'ba057949-6dbe-486f-ab42-1e073e5e5d76', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
f553e7e7-26c5-4032-b21c-40443892ca4f,Gaggia,2023,Model 706,Grinder,True,"Stainless steel body, user-friendly controls.",True,2.1,30,"[{'id': '1e47fae0-76e1-46c4-b8d7-f867cfb97330', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
1ea08a62-97aa-47be-8ad1-1d97d21daaec,DeLonghi,2022,Model 780,French Press,False,"Stainless steel body, user-friendly controls.",True,4.3,98,"[{'id': '54b912e2-479e-4d48-be3c-1b2b849b5ea0', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
7ec14dd4-a4eb-400b-9c1c-de6eb9b75a45,Gaggia,2022,Model 941,Percolation,False,"Stainless steel body, user-friendly controls.",False,4.3,99,"[{'id': '2ceb3f6a-1c94-4eca-a1a1-ad89993a4699', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
f911ef99-f96e-44c0-8551-0b176e78423e,Breville,2019,Model 574,Cold Brew,False,"Stainless steel body, user-friendly controls.",True,4.6,92,"[{'id': 'ac7d283f-d88e-49d0-a08c-52f176470cb1', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
01525f66-e9da-46ab-ae12-4d2e40b26cf1,Gaggia,2024,Model 286,Pod,False,"Stainless steel body, user-friendly controls.",False,1.9,5,"[{'id': 'bec2434e-f646-4de9-be3a-a40d9f5501e2', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
5ff13ae7-9591-407a-a0fb-cdcd0cc5127d,Rocket,2020,Model 264,French Press,False,"Stainless steel body, user-friendly controls.",False,1.9,95,"[{'id': '5ed0348c-a8d5-49ab-9a77-2576f193fd1b', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
8cffb75c-dd8c-43e5-94d9-27b311ca41db,Rancilio,2021,Model 286,Grinder,True,"Stainless steel body, user-friendly controls.",True,1.6,26,"[{'id': 'ba6aa266-d108-4a2f-af8e-04d743c30b92', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
61ec0959-f657-4dfc-9a8d-12a2e8ac9cfe,Gaggia,2016,Model 905,Percolation,False,"Stainless steel body, user-friendly controls.",True,2.3,25,"[{'id': 'f9b69c15-4a32-4326-979a-bc0b4ba08241', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
9ee2b394-7111-4972-905d-eceebeea0b8f,La Marzocco,2019,Model 777,Cold Brew,False,"Stainless steel body, user-friendly controls.",False,4.1,7,"[{'id': '6fb7b281-d587-4b89-b23d-7b38b0afae4e', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
38fe46de-0197-4792-8f3d-d54f555dc919,Rancilio,2016,Model 984,Espresso Pod,False,"Stainless steel body, user-friendly controls.",False,1.4,83,"[{'id': '1f3a9f0b-0d4b-4ddd-b4cb-c077c793be9a', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
ef219dde-f302-47db-9e06-7ee91921d7cf,La Marzocco,2024,Model 713,Espresso Pod,False,"Stainless steel body, user-friendly controls.",False,4.7,25,"[{'id': 'be456aa4-1592-4ed4-8617-dc75b1df8568', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
8ea4811c-8af1-4a3f-bf0a-997f21ed9243,La Marzocco,2024,Model 847,Drip,False,"Stainless steel body, user-friendly controls.",True,2.6,42,"[{'id': '57caf05f-4286-4c69-bbe3-f09a8e20aede', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
a97e27b5-d343-4b17-b7c9-2ac034610e55,Gaggia,2018,Model 121,E61,False,"Stainless steel body, user-friendly controls.",False,3.2,77,"[{'id': 'cf9f4f4d-821e-43a3-b2b7-4d767966c4c9', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
060090b0-90b3-4ce2-bad3-e5b7ef8f3a72,Rocket,2024,Model 918,Cold Brew,False,"Stainless steel body, user-friendly controls.",True,1.3,41,"[{'id': '48f69361-2c66-4c1f-8fa4-c5cd55ceb391', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
f01d9d29-3b15-4be1-8ec5-5cb6b6ae96a3,DeLonghi,2024,Model 268,Pod,False,"Stainless steel body, user-friendly controls.",True,4.4,13,"[{'id': '829f8d62-73f0-4299-b27f-41d3f752ecf7', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
7f519fd6-c0ba-4436-8a54-ca7b969686e6,Gaggia,2022,Model 727,Espresso Pod,True,"Stainless steel body, user-friendly controls.",True,4.9,87,"[{'id': '1feebc79-674d-4686-af82-492c8a208570', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
399f934a-8318-4532-a2ca-d9d1db9a5b45,Rocket,2019,Model 404,Percolation,True,"Stainless steel body, user-friendly controls.",False,4.2,89,"[{'id': 'e5bae35b-7bbf-4c25-8755-fcfdcde641e5', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
3d55b160-1a94-47bf-9089-922f1d7b3ae5,La Marzocco,2016,Model 862,Espresso,True,"Stainless steel body, user-friendly controls.",False,1.4,33,"[{'id': '36965d5e-7eac-4874-8fb5-2a0ba7f01c2f', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
67a4b7f7-3490-4a0b-9ded-8d0141ac0a1b,Rocket,2021,Model 442,Espresso,True,"Stainless steel body, user-friendly controls.",True,3.7,88,"[{'id': '484d395b-0fb2-46ea-9a8d-08dcb082d66e', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
7f349782-0cef-436a-b6ce-75d23c32e52f,Rocket,2018,Model 919,Cold Brew,True,"Stainless steel body, user-friendly controls.",True,4.6,71,"[{'id': '217922b0-d25a-4724-b89f-ef9c8be13917', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
45c797fe-411f-46bc-be27-68efcef94699,Breville,2019,Model 228,Pod,True,"Stainless steel body, user-friendly controls.",True,4.6,16,"[{'id': '871068f2-f516-4a41-899d-d38aed9753cf', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
ad6521e3-c588-4b77-9a68-4d42e9c3f40f,DeLonghi,2018,Model 801,Percolation,True,"Stainless steel body, user-friendly controls.",True,4.4,48,"[{'id': 'ded0a526-283f-4e9e-903d-0d4525ec74c8', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
1403c4e9-fe0b-4980-a0a0-399cb0e643bd,DeLonghi,2023,Model 583,Cold Brew,False,"Stainless steel body, user-friendly controls.",True,2.8,19,"[{'id': '74d185af-87cb-463b-8414-727b28721fb6', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
33d49b46-81e5-45ae-958c-bfc58ca7304f,Rocket,2024,Model 204,French Press,False,"Stainless steel body, user-friendly controls.",True,2.9,64,"[{'id': 'efdebdd5-46e4-4133-aa15-b706a2526c23', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
986f82bd-7999-42f9-9600-b3e24f038c3a,Breville,2021,Model 780,Espresso Pod,True,"Stainless steel body, user-friendly controls.",False,4.1,94,"[{'id': '666192ba-4003-421a-84df-60e31d5c37aa', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
b042f05d-1fb6-42aa-8792-9fe3f2c3465e,Rancilio,2016,Model 935,French Press,True,"Stainless steel body, user-friendly controls.",False,2.2,81,"[{'id': '37e24303-8f02-4571-b9b3-96435bb02204', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
ec7eac0e-799f-49e9-a2e4-e2162e22960f,Rocket,2024,Model 211,Cold Brew,True,"Stainless steel body, user-friendly controls.",True,1.6,8,"[{'id': '8a8856e7-5c6b-450e-9d11-7414b12cf739', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
8a17a266-916f-4b41-bcb0-74b7e5ecfeab,Breville,2020,Model 428,Cold Brew,False,"Stainless steel body, user-friendly controls.",True,3.1,0,"[{'id': '597a5c4c-2d59-4e98-8304-07cc56f81801', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
51
csv_backups/backup_20250704_103534/Origin_Countries.csv
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
id,name,continent,regions,altitudeRange,harvestSeason,commonVarietals,processingMethods,flavorProfile,characteristics,climate,soilType,rating,coffeeCulture,exportVolume,mainPorts,certifications,averagePrice,seasonality
|
||||||
|
44150328-5ad7-4116-bfaa-8aa2463933e5,Yemen,Central America,"['Kirinyaga', 'Huila', 'Kayanza']",1200-2000m,October-March,"['Maragogipe', 'Typica', 'SL28']","['Washed', 'Wet-hulled', 'Honey']","['Berry', 'Caramel', 'Spicy']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.9,Coffee is an integral part of daily life and traditions.,57305,"['Mombasa', 'Addis Ababa', 'Panama City']","['Direct Trade', 'Organic', 'Rainforest Alliance']",4.9,"{'plantingMonths': ['March', 'May', 'April'], 'harvestMonths': ['December', 'October', 'November'], 'dryingMonths': ['December', 'November', 'January']}"
|
||||||
|
9ae3c3a9-7345-493a-b158-03df4aa4deae,Jamaica,South America,"['Antioquia', 'Yirgacheffe', 'Kirinyaga']",1200-2000m,October-March,"['Bourbon', 'Maragogipe', 'SL34']","['Washed', 'Natural', 'Honey']","['Spicy', 'Fruity', 'Berry']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.8,Coffee is an integral part of daily life and traditions.,19150,"['Mombasa', 'Cartagena', 'Santos']","['Fair Trade', 'Organic', 'Rainforest Alliance']",5.8,"{'plantingMonths': ['March', 'May', 'April'], 'harvestMonths': ['November', 'December', 'October'], 'dryingMonths': ['December', 'November', 'January']}"
|
||||||
|
5e8438e0-d78c-4022-9e91-21443853be3b,Guatemala,Asia,"['Kirinyaga', 'Boquete', 'Yirgacheffe']",1200-2000m,October-March,"['Geisha', 'Kent', 'SL28']","['Washed', 'Natural', 'Honey']","['Chocolate', 'Berry', 'Nutty']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.5,Coffee is an integral part of daily life and traditions.,99700,"['Cartagena', 'Santos', 'Addis Ababa']","['Rainforest Alliance', 'Fair Trade', 'Direct Trade']",7.7,"{'plantingMonths': ['May', 'March', 'April'], 'harvestMonths': ['October', 'November', 'December'], 'dryingMonths': ['January', 'December', 'November']}"
|
||||||
|
b4dbdfa5-1ab7-4f08-86c9-fd1cdde6e885,Costa Rica,Asia,"['Yirgacheffe', 'Boquete', 'Nyeri']",1200-2000m,October-March,"['Kent', 'Typica', 'Pacamara']","['Wet-hulled', 'Semi-washed', 'Washed']","['Citrus', 'Chocolate', 'Floral']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.6,Coffee is an integral part of daily life and traditions.,71638,"['Mombasa', 'Cartagena', 'Panama City']","['Rainforest Alliance', 'Organic', 'Fair Trade']",5.5,"{'plantingMonths': ['May', 'April', 'March'], 'harvestMonths': ['October', 'December', 'November'], 'dryingMonths': ['November', 'January', 'December']}"
|
||||||
|
1389e222-3439-4625-a455-c14bd011a8de,Guatemala,Africa,"['Boquete', 'Kayanza', 'Yirgacheffe']",1200-2000m,October-March,"['Pacamara', 'Maragogipe', 'Typica']","['Semi-washed', 'Wet-hulled', 'Natural']","['Caramel', 'Fruity', 'Berry']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.8,Coffee is an integral part of daily life and traditions.,45482,"['Santos', 'Addis Ababa', 'Mombasa']","['Fair Trade', 'Direct Trade', 'Rainforest Alliance']",7.4,"{'plantingMonths': ['March', 'May', 'April'], 'harvestMonths': ['October', 'December', 'November'], 'dryingMonths': ['January', 'November', 'December']}"
|
||||||
|
725f3c8a-ec1d-436f-bfd2-ce622d9decb0,Jamaica,Asia,"['Yirgacheffe', 'Antioquia', 'Kirinyaga']",1200-2000m,October-March,"['Bourbon', 'Catuai', 'Maragogipe']","['Natural', 'Semi-washed', 'Washed']","['Floral', 'Chocolate', 'Spicy']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.9,Coffee is an integral part of daily life and traditions.,96008,"['Mombasa', 'Santos', 'Cartagena']","['Rainforest Alliance', 'Organic', 'Direct Trade']",4.6,"{'plantingMonths': ['May', 'March', 'April'], 'harvestMonths': ['December', 'November', 'October'], 'dryingMonths': ['January', 'November', 'December']}"
|
||||||
|
d747e836-73d6-4fb5-bd74-9711f1953a9c,Colombia,South America,"['Huila', 'Nyeri', 'Kayanza']",1200-2000m,October-March,"['Catuai', 'SL34', 'Kent']","['Semi-washed', 'Natural', 'Washed']","['Fruity', 'Floral', 'Berry']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.4,Coffee is an integral part of daily life and traditions.,57003,"['Cartagena', 'Addis Ababa', 'Mombasa']","['Direct Trade', 'Organic', 'Fair Trade']",5.3,"{'plantingMonths': ['March', 'May', 'April'], 'harvestMonths': ['November', 'October', 'December'], 'dryingMonths': ['December', 'January', 'November']}"
|
||||||
|
db3c3b15-6309-46b3-bf77-a8a9b1cb49a4,Colombia,Africa,"['Kirinyaga', 'Nyeri', 'Kayanza']",1200-2000m,October-March,"['SL28', 'Bourbon', 'Maragogipe']","['Honey', 'Wet-hulled', 'Washed']","['Berry', 'Floral', 'Chocolate']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.4,Coffee is an integral part of daily life and traditions.,43070,"['Mombasa', 'Santos', 'Cartagena']","['Rainforest Alliance', 'Organic', 'Fair Trade']",6.4,"{'plantingMonths': ['April', 'May', 'March'], 'harvestMonths': ['October', 'December', 'November'], 'dryingMonths': ['December', 'November', 'January']}"
|
||||||
|
1343dce5-0860-4474-89bf-3dec40b22354,Honduras,Africa,"['Kirinyaga', 'Yirgacheffe', 'Kayanza']",1200-2000m,October-March,"['Maragogipe', 'SL34', 'Catuai']","['Semi-washed', 'Wet-hulled', 'Washed']","['Citrus', 'Chocolate', 'Floral']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.8,Coffee is an integral part of daily life and traditions.,60891,"['Panama City', 'Santos', 'Mombasa']","['Fair Trade', 'Direct Trade', 'Rainforest Alliance']",8.0,"{'plantingMonths': ['May', 'April', 'March'], 'harvestMonths': ['November', 'October', 'December'], 'dryingMonths': ['December', 'January', 'November']}"
|
||||||
|
1b018f62-74ee-4c7b-8ea9-c7722fedd0ef,Costa Rica,Africa,"['Antioquia', 'Kayanza', 'Nyeri']",1200-2000m,October-March,"['Pacamara', 'Geisha', 'Caturra']","['Semi-washed', 'Honey', 'Natural']","['Fruity', 'Citrus', 'Caramel']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.8,Coffee is an integral part of daily life and traditions.,17070,"['Mombasa', 'Addis Ababa', 'Santos']","['Direct Trade', 'Organic', 'Rainforest Alliance']",5.8,"{'plantingMonths': ['March', 'April', 'May'], 'harvestMonths': ['October', 'December', 'November'], 'dryingMonths': ['December', 'January', 'November']}"
|
||||||
|
a929fae6-c9fd-4ad1-89e6-0ecb0a75a251,Kenya,Central America,"['Antioquia', 'Kayanza', 'Sidamo']",1200-2000m,October-March,"['Typica', 'Geisha', 'Kent']","['Semi-washed', 'Washed', 'Honey']","['Citrus', 'Nutty', 'Chocolate']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.9,Coffee is an integral part of daily life and traditions.,62282,"['Cartagena', 'Santos', 'Addis Ababa']","['Organic', 'Direct Trade', 'Fair Trade']",4.5,"{'plantingMonths': ['April', 'March', 'May'], 'harvestMonths': ['October', 'December', 'November'], 'dryingMonths': ['November', 'December', 'January']}"
|
||||||
|
df407b18-90af-49d0-947a-802f32af361a,Brazil,Asia,"['Boquete', 'Tarrazú', 'Huila']",1200-2000m,October-March,"['Caturra', 'Kent', 'Geisha']","['Wet-hulled', 'Honey', 'Natural']","['Citrus', 'Spicy', 'Berry']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.8,Coffee is an integral part of daily life and traditions.,53054,"['Mombasa', 'Addis Ababa', 'Cartagena']","['Rainforest Alliance', 'Direct Trade', 'Organic']",8.0,"{'plantingMonths': ['May', 'April', 'March'], 'harvestMonths': ['November', 'December', 'October'], 'dryingMonths': ['November', 'December', 'January']}"
|
||||||
|
5b5a6051-a221-49e7-810b-a6860a634a34,Colombia,Asia,"['Antioquia', 'Huila', 'Nyeri']",1200-2000m,October-March,"['Maragogipe', 'Catuai', 'Pacamara']","['Washed', 'Wet-hulled', 'Natural']","['Fruity', 'Citrus', 'Floral']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.1,Coffee is an integral part of daily life and traditions.,98186,"['Addis Ababa', 'Cartagena', 'Mombasa']","['Fair Trade', 'Organic', 'Rainforest Alliance']",5.6,"{'plantingMonths': ['May', 'March', 'April'], 'harvestMonths': ['November', 'October', 'December'], 'dryingMonths': ['November', 'December', 'January']}"
|
||||||
|
18a5fd34-3b58-4467-9b59-30cf77fb9ea3,Ethiopia,Asia,"['Huila', 'Kirinyaga', 'Antioquia']",1200-2000m,October-March,"['Caturra', 'Typica', 'SL34']","['Natural', 'Washed', 'Semi-washed']","['Caramel', 'Chocolate', 'Nutty']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.5,Coffee is an integral part of daily life and traditions.,28246,"['Panama City', 'Addis Ababa', 'Santos']","['Direct Trade', 'Fair Trade', 'Rainforest Alliance']",7.6,"{'plantingMonths': ['April', 'March', 'May'], 'harvestMonths': ['December', 'October', 'November'], 'dryingMonths': ['January', 'December', 'November']}"
|
||||||
|
d19c986c-09b3-4dca-bd71-4ae6f22ec909,Honduras,Asia,"['Kirinyaga', 'Huila', 'Sidamo']",1200-2000m,October-March,"['Maragogipe', 'SL34', 'Typica']","['Washed', 'Wet-hulled', 'Semi-washed']","['Floral', 'Caramel', 'Nutty']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.6,Coffee is an integral part of daily life and traditions.,36994,"['Santos', 'Panama City', 'Cartagena']","['Rainforest Alliance', 'Organic', 'Direct Trade']",7.4,"{'plantingMonths': ['March', 'May', 'April'], 'harvestMonths': ['November', 'December', 'October'], 'dryingMonths': ['December', 'November', 'January']}"
|
||||||
|
6a5e4866-2f70-4e31-a653-47bda0a573cd,Brazil,South America,"['Nyeri', 'Boquete', 'Huila']",1200-2000m,October-March,"['Typica', 'SL28', 'Bourbon']","['Wet-hulled', 'Washed', 'Semi-washed']","['Citrus', 'Nutty', 'Spicy']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.7,Coffee is an integral part of daily life and traditions.,34082,"['Panama City', 'Santos', 'Addis Ababa']","['Fair Trade', 'Organic', 'Direct Trade']",4.7,"{'plantingMonths': ['March', 'May', 'April'], 'harvestMonths': ['October', 'December', 'November'], 'dryingMonths': ['January', 'December', 'November']}"
|
||||||
|
43e1ff98-7f8a-4113-b245-71da7966d03d,Honduras,South America,"['Tarrazú', 'Kayanza', 'Antioquia']",1200-2000m,October-March,"['Bourbon', 'Geisha', 'Kent']","['Honey', 'Wet-hulled', 'Washed']","['Nutty', 'Caramel', 'Spicy']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.3,Coffee is an integral part of daily life and traditions.,16337,"['Mombasa', 'Panama City', 'Addis Ababa']","['Rainforest Alliance', 'Organic', 'Direct Trade']",6.8,"{'plantingMonths': ['April', 'March', 'May'], 'harvestMonths': ['October', 'December', 'November'], 'dryingMonths': ['January', 'November', 'December']}"
|
||||||
|
81118dca-bf6f-4588-8975-190f1332bdb0,Ethiopia,South America,"['Tarrazú', 'Boquete', 'Kirinyaga']",1200-2000m,October-March,"['Bourbon', 'Typica', 'Caturra']","['Washed', 'Wet-hulled', 'Semi-washed']","['Caramel', 'Chocolate', 'Fruity']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.3,Coffee is an integral part of daily life and traditions.,48420,"['Cartagena', 'Mombasa', 'Addis Ababa']","['Direct Trade', 'Rainforest Alliance', 'Fair Trade']",7.5,"{'plantingMonths': ['April', 'March', 'May'], 'harvestMonths': ['October', 'November', 'December'], 'dryingMonths': ['December', 'January', 'November']}"
|
||||||
|
c2dece47-eab5-4f5d-8ce7-4e53804a9cbe,Brazil,Asia,"['Huila', 'Kayanza', 'Nyeri']",1200-2000m,October-March,"['Maragogipe', 'Bourbon', 'Caturra']","['Honey', 'Washed', 'Wet-hulled']","['Chocolate', 'Citrus', 'Nutty']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.2,Coffee is an integral part of daily life and traditions.,96436,"['Mombasa', 'Panama City', 'Addis Ababa']","['Organic', 'Rainforest Alliance', 'Fair Trade']",5.8,"{'plantingMonths': ['May', 'April', 'March'], 'harvestMonths': ['December', 'October', 'November'], 'dryingMonths': ['January', 'December', 'November']}"
|
||||||
|
7ca93f0a-e3f9-4223-8e20-ad1ad519d3ee,Yemen,Africa,"['Yirgacheffe', 'Huila', 'Sidamo']",1200-2000m,October-March,"['SL34', 'Maragogipe', 'Geisha']","['Natural', 'Washed', 'Wet-hulled']","['Citrus', 'Chocolate', 'Nutty']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.8,Coffee is an integral part of daily life and traditions.,69750,"['Santos', 'Addis Ababa', 'Panama City']","['Organic', 'Rainforest Alliance', 'Fair Trade']",4.6,"{'plantingMonths': ['March', 'April', 'May'], 'harvestMonths': ['November', 'October', 'December'], 'dryingMonths': ['January', 'November', 'December']}"
|
||||||
|
d1d64f50-2891-4d86-ae5a-ea4b97cef4cb,Guatemala,Central America,"['Sidamo', 'Antioquia', 'Nyeri']",1200-2000m,October-March,"['Caturra', 'Maragogipe', 'SL28']","['Semi-washed', 'Washed', 'Wet-hulled']","['Caramel', 'Nutty', 'Chocolate']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.0,Coffee is an integral part of daily life and traditions.,92702,"['Mombasa', 'Addis Ababa', 'Panama City']","['Direct Trade', 'Rainforest Alliance', 'Organic']",6.2,"{'plantingMonths': ['May', 'April', 'March'], 'harvestMonths': ['October', 'November', 'December'], 'dryingMonths': ['November', 'January', 'December']}"
|
||||||
|
5ab2e55a-de85-4fe0-a9fd-982cec0242d1,Jamaica,Central America,"['Kayanza', 'Kirinyaga', 'Yirgacheffe']",1200-2000m,October-March,"['Typica', 'SL28', 'Kent']","['Wet-hulled', 'Semi-washed', 'Honey']","['Chocolate', 'Caramel', 'Floral']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.9,Coffee is an integral part of daily life and traditions.,57720,"['Cartagena', 'Panama City', 'Mombasa']","['Organic', 'Direct Trade', 'Rainforest Alliance']",4.9,"{'plantingMonths': ['March', 'April', 'May'], 'harvestMonths': ['November', 'October', 'December'], 'dryingMonths': ['December', 'November', 'January']}"
|
||||||
|
1f9bd1f5-e64d-417e-b12e-c39fbe0814f9,Colombia,Central America,"['Nyeri', 'Yirgacheffe', 'Kayanza']",1200-2000m,October-March,"['Maragogipe', 'Caturra', 'Pacamara']","['Honey', 'Washed', 'Natural']","['Fruity', 'Caramel', 'Chocolate']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.5,Coffee is an integral part of daily life and traditions.,48630,"['Addis Ababa', 'Cartagena', 'Mombasa']","['Fair Trade', 'Direct Trade', 'Organic']",4.1,"{'plantingMonths': ['March', 'May', 'April'], 'harvestMonths': ['November', 'December', 'October'], 'dryingMonths': ['January', 'November', 'December']}"
|
||||||
|
ed53fba1-e84c-4efc-a696-836703fe9d29,Guatemala,Asia,"['Kirinyaga', 'Kayanza', 'Huila']",1200-2000m,October-March,"['Catuai', 'SL28', 'Caturra']","['Natural', 'Wet-hulled', 'Washed']","['Nutty', 'Berry', 'Caramel']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.9,Coffee is an integral part of daily life and traditions.,11272,"['Cartagena', 'Mombasa', 'Panama City']","['Direct Trade', 'Organic', 'Rainforest Alliance']",6.1,"{'plantingMonths': ['May', 'March', 'April'], 'harvestMonths': ['November', 'October', 'December'], 'dryingMonths': ['January', 'December', 'November']}"
|
||||||
|
bff16d2b-43b9-40e8-b595-cc3da9b0d71f,Brazil,Africa,"['Antioquia', 'Kirinyaga', 'Yirgacheffe']",1200-2000m,October-March,"['Kent', 'Pacamara', 'SL34']","['Natural', 'Honey', 'Washed']","['Spicy', 'Chocolate', 'Floral']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.6,Coffee is an integral part of daily life and traditions.,11776,"['Santos', 'Mombasa', 'Cartagena']","['Direct Trade', 'Fair Trade', 'Organic']",4.4,"{'plantingMonths': ['April', 'March', 'May'], 'harvestMonths': ['October', 'November', 'December'], 'dryingMonths': ['December', 'January', 'November']}"
|
||||||
|
0d4be036-e6ff-4735-b2f6-22b8b3c16511,Costa Rica,Central America,"['Yirgacheffe', 'Kayanza', 'Boquete']",1200-2000m,October-March,"['Typica', 'Kent', 'Caturra']","['Natural', 'Honey', 'Semi-washed']","['Fruity', 'Floral', 'Berry']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.0,Coffee is an integral part of daily life and traditions.,16623,"['Santos', 'Mombasa', 'Cartagena']","['Rainforest Alliance', 'Organic', 'Fair Trade']",7.2,"{'plantingMonths': ['March', 'April', 'May'], 'harvestMonths': ['November', 'October', 'December'], 'dryingMonths': ['December', 'November', 'January']}"
|
||||||
|
8874922e-2425-4053-a794-e01010c5fe88,Ethiopia,South America,"['Kirinyaga', 'Tarrazú', 'Nyeri']",1200-2000m,October-March,"['Geisha', 'Catuai', 'Caturra']","['Honey', 'Washed', 'Wet-hulled']","['Citrus', 'Caramel', 'Berry']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.4,Coffee is an integral part of daily life and traditions.,76738,"['Santos', 'Panama City', 'Cartagena']","['Fair Trade', 'Rainforest Alliance', 'Direct Trade']",6.7,"{'plantingMonths': ['March', 'April', 'May'], 'harvestMonths': ['October', 'November', 'December'], 'dryingMonths': ['November', 'December', 'January']}"
|
||||||
|
2468ffa6-b166-4e5c-8e12-9da13013bce9,Panama,South America,"['Kirinyaga', 'Boquete', 'Nyeri']",1200-2000m,October-March,"['SL34', 'Kent', 'SL28']","['Natural', 'Washed', 'Honey']","['Fruity', 'Citrus', 'Floral']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.0,Coffee is an integral part of daily life and traditions.,69747,"['Cartagena', 'Panama City', 'Santos']","['Fair Trade', 'Organic', 'Rainforest Alliance']",6.6,"{'plantingMonths': ['May', 'April', 'March'], 'harvestMonths': ['October', 'November', 'December'], 'dryingMonths': ['December', 'November', 'January']}"
|
||||||
|
60b8c45c-8651-4107-9541-d797063e3985,Panama,Central America,"['Kirinyaga', 'Boquete', 'Antioquia']",1200-2000m,October-March,"['SL34', 'Typica', 'Pacamara']","['Natural', 'Washed', 'Wet-hulled']","['Berry', 'Caramel', 'Floral']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.6,Coffee is an integral part of daily life and traditions.,99107,"['Addis Ababa', 'Mombasa', 'Panama City']","['Fair Trade', 'Organic', 'Rainforest Alliance']",6.9,"{'plantingMonths': ['March', 'April', 'May'], 'harvestMonths': ['November', 'October', 'December'], 'dryingMonths': ['November', 'January', 'December']}"
|
||||||
|
a5ab6483-b8ca-4e5b-8bc6-5d7b5bdf3430,Brazil,South America,"['Tarrazú', 'Boquete', 'Kirinyaga']",1200-2000m,October-March,"['Caturra', 'Maragogipe', 'Bourbon']","['Natural', 'Honey', 'Wet-hulled']","['Citrus', 'Floral', 'Spicy']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.5,Coffee is an integral part of daily life and traditions.,94418,"['Santos', 'Addis Ababa', 'Cartagena']","['Direct Trade', 'Fair Trade', 'Rainforest Alliance']",7.8,"{'plantingMonths': ['April', 'May', 'March'], 'harvestMonths': ['November', 'December', 'October'], 'dryingMonths': ['December', 'January', 'November']}"
|
||||||
|
7b0f4169-7275-40e9-98f2-ebf80de6c5f6,Panama,South America,"['Huila', 'Sidamo', 'Tarrazú']",1200-2000m,October-March,"['Catuai', 'Caturra', 'Geisha']","['Natural', 'Honey', 'Semi-washed']","['Spicy', 'Floral', 'Berry']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.9,Coffee is an integral part of daily life and traditions.,37283,"['Panama City', 'Mombasa', 'Cartagena']","['Direct Trade', 'Organic', 'Fair Trade']",6.7,"{'plantingMonths': ['March', 'April', 'May'], 'harvestMonths': ['December', 'October', 'November'], 'dryingMonths': ['November', 'January', 'December']}"
|
||||||
|
1790eceb-c9fa-4669-ae95-a95a46edf5df,Costa Rica,Africa,"['Yirgacheffe', 'Tarrazú', 'Huila']",1200-2000m,October-March,"['Geisha', 'Catuai', 'Kent']","['Washed', 'Natural', 'Semi-washed']","['Chocolate', 'Spicy', 'Fruity']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,5.0,Coffee is an integral part of daily life and traditions.,38599,"['Santos', 'Addis Ababa', 'Mombasa']","['Rainforest Alliance', 'Direct Trade', 'Organic']",7.9,"{'plantingMonths': ['May', 'March', 'April'], 'harvestMonths': ['October', 'November', 'December'], 'dryingMonths': ['December', 'January', 'November']}"
|
||||||
|
5537b852-1013-4527-aa60-11928c9d306b,Colombia,Central America,"['Nyeri', 'Yirgacheffe', 'Kirinyaga']",1200-2000m,October-March,"['Maragogipe', 'Geisha', 'SL34']","['Honey', 'Washed', 'Natural']","['Caramel', 'Berry', 'Nutty']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.8,Coffee is an integral part of daily life and traditions.,62416,"['Cartagena', 'Panama City', 'Addis Ababa']","['Fair Trade', 'Direct Trade', 'Organic']",7.3,"{'plantingMonths': ['April', 'May', 'March'], 'harvestMonths': ['October', 'November', 'December'], 'dryingMonths': ['December', 'January', 'November']}"
|
||||||
|
fd24ecae-d115-412c-8883-31e80681d419,Costa Rica,Central America,"['Huila', 'Yirgacheffe', 'Nyeri']",1200-2000m,October-March,"['Bourbon', 'Maragogipe', 'Catuai']","['Natural', 'Wet-hulled', 'Honey']","['Chocolate', 'Nutty', 'Citrus']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.7,Coffee is an integral part of daily life and traditions.,27006,"['Cartagena', 'Panama City', 'Addis Ababa']","['Organic', 'Fair Trade', 'Rainforest Alliance']",5.5,"{'plantingMonths': ['April', 'March', 'May'], 'harvestMonths': ['November', 'December', 'October'], 'dryingMonths': ['December', 'November', 'January']}"
|
||||||
|
fb4fbe42-01b5-444f-8150-97e29e7c7b97,Colombia,South America,"['Boquete', 'Sidamo', 'Tarrazú']",1200-2000m,October-March,"['Catuai', 'Typica', 'SL34']","['Wet-hulled', 'Washed', 'Natural']","['Floral', 'Nutty', 'Spicy']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.8,Coffee is an integral part of daily life and traditions.,24727,"['Panama City', 'Santos', 'Addis Ababa']","['Fair Trade', 'Organic', 'Direct Trade']",5.6,"{'plantingMonths': ['March', 'May', 'April'], 'harvestMonths': ['October', 'November', 'December'], 'dryingMonths': ['November', 'December', 'January']}"
|
||||||
|
2eae7bf9-474d-43cf-811a-3563a3e8b5d9,Costa Rica,Central America,"['Tarrazú', 'Boquete', 'Sidamo']",1200-2000m,October-March,"['Pacamara', 'SL28', 'Typica']","['Natural', 'Semi-washed', 'Wet-hulled']","['Caramel', 'Nutty', 'Spicy']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.1,Coffee is an integral part of daily life and traditions.,29113,"['Santos', 'Cartagena', 'Panama City']","['Organic', 'Direct Trade', 'Fair Trade']",4.6,"{'plantingMonths': ['March', 'April', 'May'], 'harvestMonths': ['November', 'October', 'December'], 'dryingMonths': ['November', 'December', 'January']}"
|
||||||
|
83168641-110c-4109-98d6-5ca4d3892896,Colombia,South America,"['Kirinyaga', 'Boquete', 'Kayanza']",1200-2000m,October-March,"['SL28', 'Catuai', 'SL34']","['Semi-washed', 'Wet-hulled', 'Washed']","['Caramel', 'Chocolate', 'Citrus']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.2,Coffee is an integral part of daily life and traditions.,47124,"['Panama City', 'Santos', 'Cartagena']","['Rainforest Alliance', 'Direct Trade', 'Organic']",4.3,"{'plantingMonths': ['April', 'March', 'May'], 'harvestMonths': ['December', 'October', 'November'], 'dryingMonths': ['November', 'January', 'December']}"
|
||||||
|
7a5b8322-7dfa-47c5-a44e-ad273cdfd259,Guatemala,Africa,"['Kayanza', 'Tarrazú', 'Huila']",1200-2000m,October-March,"['Pacamara', 'Caturra', 'Kent']","['Natural', 'Honey', 'Wet-hulled']","['Spicy', 'Fruity', 'Floral']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.9,Coffee is an integral part of daily life and traditions.,41869,"['Mombasa', 'Santos', 'Cartagena']","['Direct Trade', 'Organic', 'Rainforest Alliance']",6.7,"{'plantingMonths': ['April', 'May', 'March'], 'harvestMonths': ['October', 'December', 'November'], 'dryingMonths': ['November', 'January', 'December']}"
|
||||||
|
25bc8058-e94d-4345-ab72-a8ac27dfe926,Panama,South America,"['Tarrazú', 'Kirinyaga', 'Nyeri']",1200-2000m,October-March,"['SL34', 'Caturra', 'Catuai']","['Wet-hulled', 'Natural', 'Semi-washed']","['Berry', 'Floral', 'Spicy']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.6,Coffee is an integral part of daily life and traditions.,81107,"['Santos', 'Mombasa', 'Cartagena']","['Organic', 'Rainforest Alliance', 'Fair Trade']",6.5,"{'plantingMonths': ['March', 'April', 'May'], 'harvestMonths': ['December', 'November', 'October'], 'dryingMonths': ['January', 'November', 'December']}"
|
||||||
|
c6e6b9dc-9227-4d78-848a-a5699c942833,Yemen,South America,"['Yirgacheffe', 'Huila', 'Nyeri']",1200-2000m,October-March,"['SL34', 'Typica', 'Pacamara']","['Wet-hulled', 'Honey', 'Semi-washed']","['Citrus', 'Nutty', 'Floral']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.9,Coffee is an integral part of daily life and traditions.,12033,"['Mombasa', 'Santos', 'Panama City']","['Organic', 'Rainforest Alliance', 'Direct Trade']",6.5,"{'plantingMonths': ['March', 'April', 'May'], 'harvestMonths': ['October', 'November', 'December'], 'dryingMonths': ['December', 'November', 'January']}"
|
||||||
|
5117bf61-4223-4b41-8e2b-c3cd0ae0b023,Jamaica,Africa,"['Sidamo', 'Nyeri', 'Boquete']",1200-2000m,October-March,"['Maragogipe', 'Catuai', 'Typica']","['Natural', 'Honey', 'Semi-washed']","['Caramel', 'Citrus', 'Floral']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.4,Coffee is an integral part of daily life and traditions.,75938,"['Addis Ababa', 'Mombasa', 'Santos']","['Rainforest Alliance', 'Fair Trade', 'Organic']",7.5,"{'plantingMonths': ['March', 'May', 'April'], 'harvestMonths': ['November', 'December', 'October'], 'dryingMonths': ['November', 'January', 'December']}"
|
||||||
|
89e0e1f1-0b1a-4d63-bdd6-0a0674ad0ca5,Brazil,Africa,"['Antioquia', 'Sidamo', 'Huila']",1200-2000m,October-March,"['SL34', 'SL28', 'Maragogipe']","['Washed', 'Natural', 'Honey']","['Spicy', 'Fruity', 'Nutty']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.3,Coffee is an integral part of daily life and traditions.,59131,"['Santos', 'Cartagena', 'Addis Ababa']","['Fair Trade', 'Rainforest Alliance', 'Direct Trade']",6.7,"{'plantingMonths': ['April', 'May', 'March'], 'harvestMonths': ['October', 'November', 'December'], 'dryingMonths': ['January', 'November', 'December']}"
|
||||||
|
67e87737-9337-4295-a9cc-eee9636d5b94,Costa Rica,South America,"['Tarrazú', 'Huila', 'Sidamo']",1200-2000m,October-March,"['Kent', 'SL34', 'Maragogipe']","['Honey', 'Wet-hulled', 'Natural']","['Berry', 'Chocolate', 'Floral']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.9,Coffee is an integral part of daily life and traditions.,49146,"['Panama City', 'Addis Ababa', 'Mombasa']","['Rainforest Alliance', 'Fair Trade', 'Direct Trade']",7.4,"{'plantingMonths': ['March', 'April', 'May'], 'harvestMonths': ['December', 'November', 'October'], 'dryingMonths': ['November', 'December', 'January']}"
|
||||||
|
ef613af9-5d21-4c71-aaed-8eb80b7dbf75,Panama,South America,"['Kirinyaga', 'Kayanza', 'Antioquia']",1200-2000m,October-March,"['SL34', 'SL28', 'Pacamara']","['Semi-washed', 'Natural', 'Washed']","['Fruity', 'Nutty', 'Floral']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.8,Coffee is an integral part of daily life and traditions.,41664,"['Panama City', 'Addis Ababa', 'Santos']","['Direct Trade', 'Fair Trade', 'Rainforest Alliance']",4.6,"{'plantingMonths': ['May', 'March', 'April'], 'harvestMonths': ['November', 'December', 'October'], 'dryingMonths': ['January', 'November', 'December']}"
|
||||||
|
3b3f2e08-1094-4c40-9f78-e3fda9ebb858,Guatemala,Asia,"['Tarrazú', 'Antioquia', 'Kayanza']",1200-2000m,October-March,"['SL34', 'Maragogipe', 'Geisha']","['Natural', 'Semi-washed', 'Washed']","['Nutty', 'Floral', 'Caramel']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.5,Coffee is an integral part of daily life and traditions.,31299,"['Panama City', 'Mombasa', 'Addis Ababa']","['Organic', 'Direct Trade', 'Rainforest Alliance']",4.2,"{'plantingMonths': ['April', 'May', 'March'], 'harvestMonths': ['October', 'November', 'December'], 'dryingMonths': ['November', 'December', 'January']}"
|
||||||
|
31488cb5-1362-4f4e-b175-575bfda3ea46,Kenya,Asia,"['Boquete', 'Sidamo', 'Huila']",1200-2000m,October-March,"['SL34', 'Typica', 'Caturra']","['Washed', 'Honey', 'Natural']","['Caramel', 'Fruity', 'Chocolate']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.3,Coffee is an integral part of daily life and traditions.,18346,"['Santos', 'Addis Ababa', 'Cartagena']","['Direct Trade', 'Organic', 'Rainforest Alliance']",7.7,"{'plantingMonths': ['March', 'May', 'April'], 'harvestMonths': ['October', 'December', 'November'], 'dryingMonths': ['January', 'November', 'December']}"
|
||||||
|
92b63ae1-d2d0-45e7-9d58-e6e8094eccb7,Yemen,South America,"['Yirgacheffe', 'Tarrazú', 'Kayanza']",1200-2000m,October-March,"['Pacamara', 'SL28', 'Maragogipe']","['Washed', 'Wet-hulled', 'Natural']","['Caramel', 'Fruity', 'Berry']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.3,Coffee is an integral part of daily life and traditions.,28724,"['Cartagena', 'Mombasa', 'Addis Ababa']","['Rainforest Alliance', 'Organic', 'Fair Trade']",5.6,"{'plantingMonths': ['March', 'April', 'May'], 'harvestMonths': ['October', 'November', 'December'], 'dryingMonths': ['January', 'November', 'December']}"
|
||||||
|
54b5a624-a690-444a-87ab-a3df9c19f9d9,Brazil,Central America,"['Antioquia', 'Nyeri', 'Yirgacheffe']",1200-2000m,October-March,"['Geisha', 'Maragogipe', 'Caturra']","['Semi-washed', 'Washed', 'Wet-hulled']","['Caramel', 'Floral', 'Berry']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.1,Coffee is an integral part of daily life and traditions.,87062,"['Mombasa', 'Cartagena', 'Addis Ababa']","['Organic', 'Fair Trade', 'Rainforest Alliance']",4.4,"{'plantingMonths': ['May', 'March', 'April'], 'harvestMonths': ['November', 'December', 'October'], 'dryingMonths': ['January', 'November', 'December']}"
|
||||||
|
9fab2f87-4dec-49d6-a983-ea7d5e6d9afd,Honduras,South America,"['Kirinyaga', 'Sidamo', 'Tarrazú']",1200-2000m,October-March,"['SL28', 'Typica', 'Catuai']","['Wet-hulled', 'Honey', 'Washed']","['Citrus', 'Spicy', 'Chocolate']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.8,Coffee is an integral part of daily life and traditions.,50373,"['Santos', 'Cartagena', 'Mombasa']","['Organic', 'Fair Trade', 'Direct Trade']",6.4,"{'plantingMonths': ['March', 'May', 'April'], 'harvestMonths': ['November', 'October', 'December'], 'dryingMonths': ['January', 'November', 'December']}"
|
||||||
|
6eb93ee9-2fa9-4fe6-8f0d-34d62d97f216,Honduras,South America,"['Antioquia', 'Yirgacheffe', 'Boquete']",1200-2000m,October-March,"['Caturra', 'Maragogipe', 'SL28']","['Washed', 'Wet-hulled', 'Semi-washed']","['Citrus', 'Spicy', 'Fruity']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.6,Coffee is an integral part of daily life and traditions.,99073,"['Cartagena', 'Addis Ababa', 'Panama City']","['Fair Trade', 'Direct Trade', 'Rainforest Alliance']",6.2,"{'plantingMonths': ['March', 'May', 'April'], 'harvestMonths': ['December', 'October', 'November'], 'dryingMonths': ['January', 'November', 'December']}"
|
||||||
|
1402
csv_exports/Brew_Recipes.json
Normal file
1352
csv_exports/Coffee_Beans.json
Normal file
702
csv_exports/Coffee_Machines.json
Normal file
@ -0,0 +1,702 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "9f68fe90-a40c-4de9-bae5-cbd8350f15d9",
|
||||||
|
"manufacturer": "La Marzocco",
|
||||||
|
"year": "2017",
|
||||||
|
"model": "Model 584",
|
||||||
|
"type": "Drip",
|
||||||
|
"steamWand": "True",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "False",
|
||||||
|
"rating": "2.4",
|
||||||
|
"popularity": "100",
|
||||||
|
"portafilters": "[{'id': '2369181c-df26-4b1e-98cb-e2215b0a5871', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "96fbb2fc-c139-43b8-b285-6d8c70408696",
|
||||||
|
"manufacturer": "Rocket",
|
||||||
|
"year": "2021",
|
||||||
|
"model": "Model 811",
|
||||||
|
"type": "E61",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "False",
|
||||||
|
"rating": "3.0",
|
||||||
|
"popularity": "100",
|
||||||
|
"portafilters": "[{'id': '9135f83b-138a-45be-b56e-b029f9658434', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "07657e06-2b49-4c53-9d8e-5c0298eed45a",
|
||||||
|
"manufacturer": "Gaggia",
|
||||||
|
"year": "2016",
|
||||||
|
"model": "Model 425",
|
||||||
|
"type": "E61",
|
||||||
|
"steamWand": "True",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "False",
|
||||||
|
"rating": "3.4",
|
||||||
|
"popularity": "45",
|
||||||
|
"portafilters": "[{'id': '933851e9-3147-4e86-a5bb-111398063395', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4e1be4f7-7611-4585-bd86-cab2736722a1",
|
||||||
|
"manufacturer": "Rocket",
|
||||||
|
"year": "2020",
|
||||||
|
"model": "Model 922",
|
||||||
|
"type": "Espresso Pod",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "True",
|
||||||
|
"rating": "4.4",
|
||||||
|
"popularity": "16",
|
||||||
|
"portafilters": "[{'id': 'c149dc17-e931-4142-8043-3629a191b767', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "da5ce31d-1b34-4747-bc7f-20d8a7a266b3",
|
||||||
|
"manufacturer": "Rancilio",
|
||||||
|
"year": "2022",
|
||||||
|
"model": "Model 877",
|
||||||
|
"type": "French Press",
|
||||||
|
"steamWand": "True",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "False",
|
||||||
|
"rating": "4.7",
|
||||||
|
"popularity": "76",
|
||||||
|
"portafilters": "[{'id': '5ace346d-8a77-4e77-ad39-84095472d306', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "31aed043-2d87-40f0-a824-90bc863e49b4",
|
||||||
|
"manufacturer": "Gaggia",
|
||||||
|
"year": "2020",
|
||||||
|
"model": "Model 971",
|
||||||
|
"type": "Espresso Pod",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "False",
|
||||||
|
"rating": "2.6",
|
||||||
|
"popularity": "48",
|
||||||
|
"portafilters": "[{'id': '3ea2bb4f-56b8-4e98-8ede-e75ab7e8f56a', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "7e933369-4160-4c10-bfdd-ab2624dac1d0",
|
||||||
|
"manufacturer": "La Marzocco",
|
||||||
|
"year": "2020",
|
||||||
|
"model": "Model 941",
|
||||||
|
"type": "Pod",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "False",
|
||||||
|
"rating": "4.5",
|
||||||
|
"popularity": "45",
|
||||||
|
"portafilters": "[{'id': 'f83cffe2-11f8-44b5-84cb-e52f916b73c8', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "06051aee-61bf-4a59-97db-e4799beef357",
|
||||||
|
"manufacturer": "Gaggia",
|
||||||
|
"year": "2019",
|
||||||
|
"model": "Model 901",
|
||||||
|
"type": "Grinder",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "True",
|
||||||
|
"rating": "3.2",
|
||||||
|
"popularity": "75",
|
||||||
|
"portafilters": "[{'id': 'f9c154cd-2d25-4134-a3ea-425cf8851dfe', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3f394f15-fef5-41d7-8b0a-8c6780be5a69",
|
||||||
|
"manufacturer": "Rocket",
|
||||||
|
"year": "2020",
|
||||||
|
"model": "Model 195",
|
||||||
|
"type": "Grinder",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "False",
|
||||||
|
"rating": "2.8",
|
||||||
|
"popularity": "30",
|
||||||
|
"portafilters": "[{'id': 'ba198010-6987-47d0-be75-550f979ab542', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f7a28b47-4484-47e9-b55b-3040edb53b6c",
|
||||||
|
"manufacturer": "DeLonghi",
|
||||||
|
"year": "2022",
|
||||||
|
"model": "Model 147",
|
||||||
|
"type": "Espresso",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "False",
|
||||||
|
"rating": "2.9",
|
||||||
|
"popularity": "48",
|
||||||
|
"portafilters": "[{'id': '78fc9726-b016-4510-9d1b-d5f20c7137a3', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "272055c6-42bf-447b-bf3b-fc49517a1a9f",
|
||||||
|
"manufacturer": "Rocket",
|
||||||
|
"year": "2021",
|
||||||
|
"model": "Model 614",
|
||||||
|
"type": "Espresso Pod",
|
||||||
|
"steamWand": "True",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "True",
|
||||||
|
"rating": "1.5",
|
||||||
|
"popularity": "69",
|
||||||
|
"portafilters": "[{'id': '9272d327-1c7c-4215-9d48-4fd04cecd137', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2495a0e6-4cd9-43e1-8e73-a6a058de9177",
|
||||||
|
"manufacturer": "Gaggia",
|
||||||
|
"year": "2022",
|
||||||
|
"model": "Model 294",
|
||||||
|
"type": "Cold Brew",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "False",
|
||||||
|
"rating": "1.5",
|
||||||
|
"popularity": "19",
|
||||||
|
"portafilters": "[{'id': 'b119011f-d54c-46b3-b818-7427e1ab03e8', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f19bd621-fbf9-4397-a94f-7e0568bf3b73",
|
||||||
|
"manufacturer": "La Marzocco",
|
||||||
|
"year": "2015",
|
||||||
|
"model": "Model 415",
|
||||||
|
"type": "Grinder",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "False",
|
||||||
|
"rating": "3.0",
|
||||||
|
"popularity": "54",
|
||||||
|
"portafilters": "[{'id': 'edc74dab-9502-42a3-a5b2-92bea4d2dfef', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "c0efb75b-c072-4dc4-9b67-01c06ef5e7a5",
|
||||||
|
"manufacturer": "DeLonghi",
|
||||||
|
"year": "2018",
|
||||||
|
"model": "Model 632",
|
||||||
|
"type": "Drip",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "False",
|
||||||
|
"rating": "1.3",
|
||||||
|
"popularity": "41",
|
||||||
|
"portafilters": "[{'id': 'bc216970-410e-47c6-ad2f-bb1422d3f9ec', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ecc282ec-2857-48a7-bb0b-cd3eb3990f20",
|
||||||
|
"manufacturer": "Breville",
|
||||||
|
"year": "2018",
|
||||||
|
"model": "Model 606",
|
||||||
|
"type": "Cold Brew",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "False",
|
||||||
|
"rating": "2.9",
|
||||||
|
"popularity": "46",
|
||||||
|
"portafilters": "[{'id': 'c60bdad5-d5d0-4abb-b34c-ace1abb868a5', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "145eead8-b9c4-4d2e-a031-e7c488525eaa",
|
||||||
|
"manufacturer": "Breville",
|
||||||
|
"year": "2020",
|
||||||
|
"model": "Model 180",
|
||||||
|
"type": "Grinder",
|
||||||
|
"steamWand": "True",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "True",
|
||||||
|
"rating": "4.3",
|
||||||
|
"popularity": "28",
|
||||||
|
"portafilters": "[{'id': 'fb42d62e-2310-4c80-a721-5e2ebbf97ec2', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b9a9f96c-1ba2-4bff-8de1-9230bc467d43",
|
||||||
|
"manufacturer": "Rancilio",
|
||||||
|
"year": "2016",
|
||||||
|
"model": "Model 414",
|
||||||
|
"type": "Drip",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "False",
|
||||||
|
"rating": "1.3",
|
||||||
|
"popularity": "87",
|
||||||
|
"portafilters": "[{'id': 'ee9cf949-b7fa-4afd-908a-e9e2167a718e', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fa4b4ade-4fab-4428-b392-87fc331b86d0",
|
||||||
|
"manufacturer": "Breville",
|
||||||
|
"year": "2017",
|
||||||
|
"model": "Model 794",
|
||||||
|
"type": "Espresso",
|
||||||
|
"steamWand": "True",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "False",
|
||||||
|
"rating": "4.9",
|
||||||
|
"popularity": "13",
|
||||||
|
"portafilters": "[{'id': '2a9da80b-e6db-44cd-86ab-6d92ea3fc99b', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4e1d352e-677d-4578-8adb-44c92245bcfb",
|
||||||
|
"manufacturer": "DeLonghi",
|
||||||
|
"year": "2021",
|
||||||
|
"model": "Model 349",
|
||||||
|
"type": "Percolation",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "True",
|
||||||
|
"rating": "4.4",
|
||||||
|
"popularity": "57",
|
||||||
|
"portafilters": "[{'id': '925d6f92-2455-41c1-8c1c-46c4112bcffc', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "eb5362c0-7131-4731-b4b7-e0f3df3c26b9",
|
||||||
|
"manufacturer": "Breville",
|
||||||
|
"year": "2022",
|
||||||
|
"model": "Model 914",
|
||||||
|
"type": "Espresso Pod",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "False",
|
||||||
|
"rating": "1.3",
|
||||||
|
"popularity": "6",
|
||||||
|
"portafilters": "[{'id': 'a22e10b8-4ec2-47ed-9702-2528f9fb803a', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2d0bbbf5-7457-40f3-9836-4b81db57ed74",
|
||||||
|
"manufacturer": "Rancilio",
|
||||||
|
"year": "2019",
|
||||||
|
"model": "Model 162",
|
||||||
|
"type": "Percolation",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "False",
|
||||||
|
"rating": "1.0",
|
||||||
|
"popularity": "66",
|
||||||
|
"portafilters": "[{'id': '798a042e-3926-4f9d-b38c-511644be3a01', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "949bd3b3-599e-4f65-90db-d41e76556c46",
|
||||||
|
"manufacturer": "Breville",
|
||||||
|
"year": "2021",
|
||||||
|
"model": "Model 195",
|
||||||
|
"type": "Cold Brew",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "True",
|
||||||
|
"rating": "2.6",
|
||||||
|
"popularity": "77",
|
||||||
|
"portafilters": "[{'id': 'ba057949-6dbe-486f-ab42-1e073e5e5d76', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f553e7e7-26c5-4032-b21c-40443892ca4f",
|
||||||
|
"manufacturer": "Gaggia",
|
||||||
|
"year": "2023",
|
||||||
|
"model": "Model 706",
|
||||||
|
"type": "Grinder",
|
||||||
|
"steamWand": "True",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "True",
|
||||||
|
"rating": "2.1",
|
||||||
|
"popularity": "30",
|
||||||
|
"portafilters": "[{'id': '1e47fae0-76e1-46c4-b8d7-f867cfb97330', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1ea08a62-97aa-47be-8ad1-1d97d21daaec",
|
||||||
|
"manufacturer": "DeLonghi",
|
||||||
|
"year": "2022",
|
||||||
|
"model": "Model 780",
|
||||||
|
"type": "French Press",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "True",
|
||||||
|
"rating": "4.3",
|
||||||
|
"popularity": "98",
|
||||||
|
"portafilters": "[{'id': '54b912e2-479e-4d48-be3c-1b2b849b5ea0', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "7ec14dd4-a4eb-400b-9c1c-de6eb9b75a45",
|
||||||
|
"manufacturer": "Gaggia",
|
||||||
|
"year": "2022",
|
||||||
|
"model": "Model 941",
|
||||||
|
"type": "Percolation",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "False",
|
||||||
|
"rating": "4.3",
|
||||||
|
"popularity": "99",
|
||||||
|
"portafilters": "[{'id': '2ceb3f6a-1c94-4eca-a1a1-ad89993a4699', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f911ef99-f96e-44c0-8551-0b176e78423e",
|
||||||
|
"manufacturer": "Breville",
|
||||||
|
"year": "2019",
|
||||||
|
"model": "Model 574",
|
||||||
|
"type": "Cold Brew",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "True",
|
||||||
|
"rating": "4.6",
|
||||||
|
"popularity": "92",
|
||||||
|
"portafilters": "[{'id': 'ac7d283f-d88e-49d0-a08c-52f176470cb1', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "01525f66-e9da-46ab-ae12-4d2e40b26cf1",
|
||||||
|
"manufacturer": "Gaggia",
|
||||||
|
"year": "2024",
|
||||||
|
"model": "Model 286",
|
||||||
|
"type": "Pod",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "False",
|
||||||
|
"rating": "1.9",
|
||||||
|
"popularity": "5",
|
||||||
|
"portafilters": "[{'id': 'bec2434e-f646-4de9-be3a-a40d9f5501e2', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5ff13ae7-9591-407a-a0fb-cdcd0cc5127d",
|
||||||
|
"manufacturer": "Rocket",
|
||||||
|
"year": "2020",
|
||||||
|
"model": "Model 264",
|
||||||
|
"type": "French Press",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "False",
|
||||||
|
"rating": "1.9",
|
||||||
|
"popularity": "95",
|
||||||
|
"portafilters": "[{'id': '5ed0348c-a8d5-49ab-9a77-2576f193fd1b', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8cffb75c-dd8c-43e5-94d9-27b311ca41db",
|
||||||
|
"manufacturer": "Rancilio",
|
||||||
|
"year": "2021",
|
||||||
|
"model": "Model 286",
|
||||||
|
"type": "Grinder",
|
||||||
|
"steamWand": "True",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "True",
|
||||||
|
"rating": "1.6",
|
||||||
|
"popularity": "26",
|
||||||
|
"portafilters": "[{'id': 'ba6aa266-d108-4a2f-af8e-04d743c30b92', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "61ec0959-f657-4dfc-9a8d-12a2e8ac9cfe",
|
||||||
|
"manufacturer": "Gaggia",
|
||||||
|
"year": "2016",
|
||||||
|
"model": "Model 905",
|
||||||
|
"type": "Percolation",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "True",
|
||||||
|
"rating": "2.3",
|
||||||
|
"popularity": "25",
|
||||||
|
"portafilters": "[{'id': 'f9b69c15-4a32-4326-979a-bc0b4ba08241', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9ee2b394-7111-4972-905d-eceebeea0b8f",
|
||||||
|
"manufacturer": "La Marzocco",
|
||||||
|
"year": "2019",
|
||||||
|
"model": "Model 777",
|
||||||
|
"type": "Cold Brew",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "False",
|
||||||
|
"rating": "4.1",
|
||||||
|
"popularity": "7",
|
||||||
|
"portafilters": "[{'id': '6fb7b281-d587-4b89-b23d-7b38b0afae4e', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "38fe46de-0197-4792-8f3d-d54f555dc919",
|
||||||
|
"manufacturer": "Rancilio",
|
||||||
|
"year": "2016",
|
||||||
|
"model": "Model 984",
|
||||||
|
"type": "Espresso Pod",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "False",
|
||||||
|
"rating": "1.4",
|
||||||
|
"popularity": "83",
|
||||||
|
"portafilters": "[{'id': '1f3a9f0b-0d4b-4ddd-b4cb-c077c793be9a', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ef219dde-f302-47db-9e06-7ee91921d7cf",
|
||||||
|
"manufacturer": "La Marzocco",
|
||||||
|
"year": "2024",
|
||||||
|
"model": "Model 713",
|
||||||
|
"type": "Espresso Pod",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "False",
|
||||||
|
"rating": "4.7",
|
||||||
|
"popularity": "25",
|
||||||
|
"portafilters": "[{'id': 'be456aa4-1592-4ed4-8617-dc75b1df8568', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8ea4811c-8af1-4a3f-bf0a-997f21ed9243",
|
||||||
|
"manufacturer": "La Marzocco",
|
||||||
|
"year": "2024",
|
||||||
|
"model": "Model 847",
|
||||||
|
"type": "Drip",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "True",
|
||||||
|
"rating": "2.6",
|
||||||
|
"popularity": "42",
|
||||||
|
"portafilters": "[{'id': '57caf05f-4286-4c69-bbe3-f09a8e20aede', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a97e27b5-d343-4b17-b7c9-2ac034610e55",
|
||||||
|
"manufacturer": "Gaggia",
|
||||||
|
"year": "2018",
|
||||||
|
"model": "Model 121",
|
||||||
|
"type": "E61",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "False",
|
||||||
|
"rating": "3.2",
|
||||||
|
"popularity": "77",
|
||||||
|
"portafilters": "[{'id': 'cf9f4f4d-821e-43a3-b2b7-4d767966c4c9', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "060090b0-90b3-4ce2-bad3-e5b7ef8f3a72",
|
||||||
|
"manufacturer": "Rocket",
|
||||||
|
"year": "2024",
|
||||||
|
"model": "Model 918",
|
||||||
|
"type": "Cold Brew",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "True",
|
||||||
|
"rating": "1.3",
|
||||||
|
"popularity": "41",
|
||||||
|
"portafilters": "[{'id': '48f69361-2c66-4c1f-8fa4-c5cd55ceb391', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f01d9d29-3b15-4be1-8ec5-5cb6b6ae96a3",
|
||||||
|
"manufacturer": "DeLonghi",
|
||||||
|
"year": "2024",
|
||||||
|
"model": "Model 268",
|
||||||
|
"type": "Pod",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "True",
|
||||||
|
"rating": "4.4",
|
||||||
|
"popularity": "13",
|
||||||
|
"portafilters": "[{'id': '829f8d62-73f0-4299-b27f-41d3f752ecf7', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "7f519fd6-c0ba-4436-8a54-ca7b969686e6",
|
||||||
|
"manufacturer": "Gaggia",
|
||||||
|
"year": "2022",
|
||||||
|
"model": "Model 727",
|
||||||
|
"type": "Espresso Pod",
|
||||||
|
"steamWand": "True",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "True",
|
||||||
|
"rating": "4.9",
|
||||||
|
"popularity": "87",
|
||||||
|
"portafilters": "[{'id': '1feebc79-674d-4686-af82-492c8a208570', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "399f934a-8318-4532-a2ca-d9d1db9a5b45",
|
||||||
|
"manufacturer": "Rocket",
|
||||||
|
"year": "2019",
|
||||||
|
"model": "Model 404",
|
||||||
|
"type": "Percolation",
|
||||||
|
"steamWand": "True",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "False",
|
||||||
|
"rating": "4.2",
|
||||||
|
"popularity": "89",
|
||||||
|
"portafilters": "[{'id': 'e5bae35b-7bbf-4c25-8755-fcfdcde641e5', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3d55b160-1a94-47bf-9089-922f1d7b3ae5",
|
||||||
|
"manufacturer": "La Marzocco",
|
||||||
|
"year": "2016",
|
||||||
|
"model": "Model 862",
|
||||||
|
"type": "Espresso",
|
||||||
|
"steamWand": "True",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "False",
|
||||||
|
"rating": "1.4",
|
||||||
|
"popularity": "33",
|
||||||
|
"portafilters": "[{'id': '36965d5e-7eac-4874-8fb5-2a0ba7f01c2f', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "67a4b7f7-3490-4a0b-9ded-8d0141ac0a1b",
|
||||||
|
"manufacturer": "Rocket",
|
||||||
|
"year": "2021",
|
||||||
|
"model": "Model 442",
|
||||||
|
"type": "Espresso",
|
||||||
|
"steamWand": "True",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "True",
|
||||||
|
"rating": "3.7",
|
||||||
|
"popularity": "88",
|
||||||
|
"portafilters": "[{'id': '484d395b-0fb2-46ea-9a8d-08dcb082d66e', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "7f349782-0cef-436a-b6ce-75d23c32e52f",
|
||||||
|
"manufacturer": "Rocket",
|
||||||
|
"year": "2018",
|
||||||
|
"model": "Model 919",
|
||||||
|
"type": "Cold Brew",
|
||||||
|
"steamWand": "True",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "True",
|
||||||
|
"rating": "4.6",
|
||||||
|
"popularity": "71",
|
||||||
|
"portafilters": "[{'id': '217922b0-d25a-4724-b89f-ef9c8be13917', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "45c797fe-411f-46bc-be27-68efcef94699",
|
||||||
|
"manufacturer": "Breville",
|
||||||
|
"year": "2019",
|
||||||
|
"model": "Model 228",
|
||||||
|
"type": "Pod",
|
||||||
|
"steamWand": "True",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "True",
|
||||||
|
"rating": "4.6",
|
||||||
|
"popularity": "16",
|
||||||
|
"portafilters": "[{'id': '871068f2-f516-4a41-899d-d38aed9753cf', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ad6521e3-c588-4b77-9a68-4d42e9c3f40f",
|
||||||
|
"manufacturer": "DeLonghi",
|
||||||
|
"year": "2018",
|
||||||
|
"model": "Model 801",
|
||||||
|
"type": "Percolation",
|
||||||
|
"steamWand": "True",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "True",
|
||||||
|
"rating": "4.4",
|
||||||
|
"popularity": "48",
|
||||||
|
"portafilters": "[{'id': 'ded0a526-283f-4e9e-903d-0d4525ec74c8', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1403c4e9-fe0b-4980-a0a0-399cb0e643bd",
|
||||||
|
"manufacturer": "DeLonghi",
|
||||||
|
"year": "2023",
|
||||||
|
"model": "Model 583",
|
||||||
|
"type": "Cold Brew",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "True",
|
||||||
|
"rating": "2.8",
|
||||||
|
"popularity": "19",
|
||||||
|
"portafilters": "[{'id': '74d185af-87cb-463b-8414-727b28721fb6', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "33d49b46-81e5-45ae-958c-bfc58ca7304f",
|
||||||
|
"manufacturer": "Rocket",
|
||||||
|
"year": "2024",
|
||||||
|
"model": "Model 204",
|
||||||
|
"type": "French Press",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "True",
|
||||||
|
"rating": "2.9",
|
||||||
|
"popularity": "64",
|
||||||
|
"portafilters": "[{'id': 'efdebdd5-46e4-4133-aa15-b706a2526c23', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "986f82bd-7999-42f9-9600-b3e24f038c3a",
|
||||||
|
"manufacturer": "Breville",
|
||||||
|
"year": "2021",
|
||||||
|
"model": "Model 780",
|
||||||
|
"type": "Espresso Pod",
|
||||||
|
"steamWand": "True",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "False",
|
||||||
|
"rating": "4.1",
|
||||||
|
"popularity": "94",
|
||||||
|
"portafilters": "[{'id': '666192ba-4003-421a-84df-60e31d5c37aa', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b042f05d-1fb6-42aa-8792-9fe3f2c3465e",
|
||||||
|
"manufacturer": "Rancilio",
|
||||||
|
"year": "2016",
|
||||||
|
"model": "Model 935",
|
||||||
|
"type": "French Press",
|
||||||
|
"steamWand": "True",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "False",
|
||||||
|
"rating": "2.2",
|
||||||
|
"popularity": "81",
|
||||||
|
"portafilters": "[{'id': '37e24303-8f02-4571-b9b3-96435bb02204', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ec7eac0e-799f-49e9-a2e4-e2162e22960f",
|
||||||
|
"manufacturer": "Rocket",
|
||||||
|
"year": "2024",
|
||||||
|
"model": "Model 211",
|
||||||
|
"type": "Cold Brew",
|
||||||
|
"steamWand": "True",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "True",
|
||||||
|
"rating": "1.6",
|
||||||
|
"popularity": "8",
|
||||||
|
"portafilters": "[{'id': '8a8856e7-5c6b-450e-9d11-7414b12cf739', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8a17a266-916f-4b41-bcb0-74b7e5ecfeab",
|
||||||
|
"manufacturer": "Breville",
|
||||||
|
"year": "2020",
|
||||||
|
"model": "Model 428",
|
||||||
|
"type": "Cold Brew",
|
||||||
|
"steamWand": "False",
|
||||||
|
"details": "Stainless steel body, user-friendly controls.",
|
||||||
|
"isOwned": "True",
|
||||||
|
"rating": "3.1",
|
||||||
|
"popularity": "0",
|
||||||
|
"portafilters": "[{'id': '597a5c4c-2d59-4e98-8304-07cc56f81801', 'size': '58mm', 'material': 'Stainless Steel'}]",
|
||||||
|
"specifications": "{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
}
|
||||||
|
]
|
||||||
1052
csv_exports/Origin_Countries.json
Normal file
52
docs/CSV_CRUD_Guide.md
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# CSV Data Management Guide
|
||||||
|
|
||||||
|
1. **Master Catalog** (CSV files) - Coffee beans, machines, and recipes that come with the app
|
||||||
|
2. **User Collections** - Your personal saved items from the catalog
|
||||||
|
|
||||||
|
## For Users
|
||||||
|
|
||||||
|
### Adding Items to Your Collection
|
||||||
|
1. Open any screen (Beans, Machines, or Recipes)
|
||||||
|
2. Tap the "+" button to browse the catalog
|
||||||
|
3. Select items you want to add to your personal collection
|
||||||
|
|
||||||
|
### Managing Your Collection
|
||||||
|
- **View**: See only your saved items on each screen
|
||||||
|
- **Remove**: Swipe to delete items from your collection
|
||||||
|
- **Edit**: Tap to modify details (only for custom items you create)
|
||||||
|
|
||||||
|
## For Developers
|
||||||
|
|
||||||
|
### Adding New Catalog Items
|
||||||
|
Use the Python script to safely add items to the master catalog:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Add a new bean to the catalog
|
||||||
|
python scripts/csv_manager.py add_bean '{"id":"bean_new","name":"New Bean","origin":"Colombia","varietal":"Arabica","roastLevel":"Medium"}'
|
||||||
|
|
||||||
|
# Backup CSV files before making changes
|
||||||
|
python scripts/csv_manager.py backup
|
||||||
|
|
||||||
|
# Check if CSV files are valid
|
||||||
|
python scripts/csv_manager.py validate
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Usage
|
||||||
|
```dart
|
||||||
|
// Get user's collection
|
||||||
|
final userBeans = await storageService.getUserBeans();
|
||||||
|
|
||||||
|
// Browse full catalog
|
||||||
|
final catalogBeans = await storageService.getCatalogBeans();
|
||||||
|
|
||||||
|
// Add to user collection
|
||||||
|
await storageService.saveUserBean(selectedBean);
|
||||||
|
```
|
||||||
|
|
||||||
|
## File Locations
|
||||||
|
|
||||||
|
- **CSV Files**: `lib/database/Coffee_Beans.csv`, etc.
|
||||||
|
- **Management Script**: `scripts/csv_manager.py`
|
||||||
|
- **Backups**: Created in `csv_backups/` folder
|
||||||
|
|
||||||
|
This setup with automatically manage data and storage.
|
||||||
34
ios/.gitignore
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
**/dgph
|
||||||
|
*.mode1v3
|
||||||
|
*.mode2v3
|
||||||
|
*.moved-aside
|
||||||
|
*.pbxuser
|
||||||
|
*.perspectivev3
|
||||||
|
**/*sync/
|
||||||
|
.sconsign.dblite
|
||||||
|
.tags*
|
||||||
|
**/.vagrant/
|
||||||
|
**/DerivedData/
|
||||||
|
Icon?
|
||||||
|
**/Pods/
|
||||||
|
**/.symlinks/
|
||||||
|
profile
|
||||||
|
xcuserdata
|
||||||
|
**/.generated/
|
||||||
|
Flutter/App.framework
|
||||||
|
Flutter/Flutter.framework
|
||||||
|
Flutter/Flutter.podspec
|
||||||
|
Flutter/Generated.xcconfig
|
||||||
|
Flutter/ephemeral/
|
||||||
|
Flutter/app.flx
|
||||||
|
Flutter/app.zip
|
||||||
|
Flutter/flutter_assets/
|
||||||
|
Flutter/flutter_export_environment.sh
|
||||||
|
ServiceDefinitions.json
|
||||||
|
Runner/GeneratedPluginRegistrant.*
|
||||||
|
|
||||||
|
# Exceptions to above rules.
|
||||||
|
!default.mode1v3
|
||||||
|
!default.mode2v3
|
||||||
|
!default.pbxuser
|
||||||
|
!default.perspectivev3
|
||||||
26
ios/Flutter/AppFrameworkInfo.plist
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>io.flutter.flutter.app</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>FMWK</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>MinimumOSVersion</key>
|
||||||
|
<string>12.0</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
1
ios/Flutter/Debug.xcconfig
Normal file
@ -0,0 +1 @@
|
|||||||
|
#include "Generated.xcconfig"
|
||||||
1
ios/Flutter/Release.xcconfig
Normal file
@ -0,0 +1 @@
|
|||||||
|
#include "Generated.xcconfig"
|
||||||
616
ios/Runner.xcodeproj/project.pbxproj
Normal file
@ -0,0 +1,616 @@
|
|||||||
|
// !$*UTF8*$!
|
||||||
|
{
|
||||||
|
archiveVersion = 1;
|
||||||
|
classes = {
|
||||||
|
};
|
||||||
|
objectVersion = 54;
|
||||||
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||||
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||||
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||||
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||||
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
|
||||||
|
remoteInfo = Runner;
|
||||||
|
};
|
||||||
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "";
|
||||||
|
dstSubfolderSpec = 10;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
name = "Embed Frameworks";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXFileReference section */
|
||||||
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
|
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||||
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||||
|
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXGroup section */
|
||||||
|
331C8082294A63A400263BE5 /* RunnerTests */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
331C807B294A618700263BE5 /* RunnerTests.swift */,
|
||||||
|
);
|
||||||
|
path = RunnerTests;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||||
|
);
|
||||||
|
name = Flutter;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146E51CF9000F007C117D = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */,
|
||||||
|
97C146F01CF9000F007C117D /* Runner */,
|
||||||
|
97C146EF1CF9000F007C117D /* Products */,
|
||||||
|
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||||
|
);
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146EF1CF9000F007C117D /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||||
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146F01CF9000F007C117D /* Runner */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||||
|
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */,
|
||||||
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||||
|
);
|
||||||
|
path = Runner;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXNativeTarget section */
|
||||||
|
331C8080294A63A400263BE5 /* RunnerTests */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||||
|
buildPhases = (
|
||||||
|
331C807D294A63A400263BE5 /* Sources */,
|
||||||
|
331C807F294A63A400263BE5 /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
331C8086294A63A400263BE5 /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
name = RunnerTests;
|
||||||
|
productName = RunnerTests;
|
||||||
|
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
|
||||||
|
productType = "com.apple.product-type.bundle.unit-test";
|
||||||
|
};
|
||||||
|
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
|
buildPhases = (
|
||||||
|
9740EEB61CF901F6004384FC /* Run Script */,
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */,
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */,
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = Runner;
|
||||||
|
productName = Runner;
|
||||||
|
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||||
|
productType = "com.apple.product-type.application";
|
||||||
|
};
|
||||||
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXProject section */
|
||||||
|
97C146E61CF9000F007C117D /* Project object */ = {
|
||||||
|
isa = PBXProject;
|
||||||
|
attributes = {
|
||||||
|
BuildIndependentTargetsInParallel = YES;
|
||||||
|
LastUpgradeCheck = 1510;
|
||||||
|
ORGANIZATIONNAME = "";
|
||||||
|
TargetAttributes = {
|
||||||
|
331C8080294A63A400263BE5 = {
|
||||||
|
CreatedOnToolsVersion = 14.0;
|
||||||
|
TestTargetID = 97C146ED1CF9000F007C117D;
|
||||||
|
};
|
||||||
|
97C146ED1CF9000F007C117D = {
|
||||||
|
CreatedOnToolsVersion = 7.3.1;
|
||||||
|
LastSwiftMigration = 1100;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||||
|
compatibilityVersion = "Xcode 9.3";
|
||||||
|
developmentRegion = en;
|
||||||
|
hasScannedForEncodings = 0;
|
||||||
|
knownRegions = (
|
||||||
|
en,
|
||||||
|
Base,
|
||||||
|
);
|
||||||
|
mainGroup = 97C146E51CF9000F007C117D;
|
||||||
|
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||||
|
projectDirPath = "";
|
||||||
|
projectRoot = "";
|
||||||
|
targets = (
|
||||||
|
97C146ED1CF9000F007C117D /* Runner */,
|
||||||
|
331C8080294A63A400263BE5 /* RunnerTests */,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
331C807F294A63A400263BE5 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||||
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||||
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||||
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
||||||
|
);
|
||||||
|
name = "Thin Binary";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||||
|
};
|
||||||
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "Run Script";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||||
|
};
|
||||||
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
331C807D294A63A400263BE5 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXTargetDependency section */
|
||||||
|
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 97C146ED1CF9000F007C117D /* Runner */;
|
||||||
|
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
|
/* Begin PBXVariantGroup section */
|
||||||
|
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C146FB1CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = Main.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C147001CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = LaunchScreen.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
|
/* Begin XCBuildConfiguration section */
|
||||||
|
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.coffeeAtHome;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
331C8088294A63A400263BE5 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.coffeeAtHome.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
331C8089294A63A400263BE5 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.coffeeAtHome.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
331C808A294A63A400263BE5 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.coffeeAtHome.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
97C147031CF9000F007C117D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_TESTABILITY = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
|
"DEBUG=1",
|
||||||
|
"$(inherited)",
|
||||||
|
);
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
97C147041CF9000F007C117D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
97C147061CF9000F007C117D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.coffeeAtHome;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
97C147071CF9000F007C117D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.coffeeAtHome;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
|
/* Begin XCConfigurationList section */
|
||||||
|
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
331C8088294A63A400263BE5 /* Debug */,
|
||||||
|
331C8089294A63A400263BE5 /* Release */,
|
||||||
|
331C808A294A63A400263BE5 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
97C147031CF9000F007C117D /* Debug */,
|
||||||
|
97C147041CF9000F007C117D /* Release */,
|
||||||
|
249021D3217E4FDB00AE95B9 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
97C147061CF9000F007C117D /* Debug */,
|
||||||
|
97C147071CF9000F007C117D /* Release */,
|
||||||
|
249021D4217E4FDB00AE95B9 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
/* End XCConfigurationList section */
|
||||||
|
};
|
||||||
|
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
}
|
||||||
7
ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
101
ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1510"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<Testables>
|
||||||
|
<TestableReference
|
||||||
|
skipped = "NO"
|
||||||
|
parallelizable = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "331C8080294A63A400263BE5"
|
||||||
|
BuildableName = "RunnerTests.xctest"
|
||||||
|
BlueprintName = "RunnerTests"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</TestableReference>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
enableGPUValidationMode = "1"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Profile"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
7
ios/Runner.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "group:Runner.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
13
ios/Runner/AppDelegate.swift
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@main
|
||||||
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
|
override func application(
|
||||||
|
_ application: UIApplication,
|
||||||
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
|
) -> Bool {
|
||||||
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
122
ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-20x20@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-20x20@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-40x40@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-40x40@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-60x60@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-60x60@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-20x20@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-20x20@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-29x29@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-29x29@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-40x40@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-40x40@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "76x76",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-76x76@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "76x76",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-76x76@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "83.5x83.5",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "1024x1024",
|
||||||
|
"idiom" : "ios-marketing",
|
||||||
|
"filename" : "Icon-App-1024x1024@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 295 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 450 B |
|
After Width: | Height: | Size: 282 B |
|
After Width: | Height: | Size: 462 B |
|
After Width: | Height: | Size: 704 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 586 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 762 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
23
ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
5
ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Launch Screen Assets
|
||||||
|
|
||||||
|
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||||
|
|
||||||
|
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
|
||||||
37
ios/Runner/Base.lproj/LaunchScreen.storyboard
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--View Controller-->
|
||||||
|
<scene sceneID="EHf-IW-A2E">
|
||||||
|
<objects>
|
||||||
|
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||||
|
<layoutGuides>
|
||||||
|
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||||
|
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||||
|
</layoutGuides>
|
||||||
|
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||||
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="53" y="375"/>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
<resources>
|
||||||
|
<image name="LaunchImage" width="168" height="185"/>
|
||||||
|
</resources>
|
||||||
|
</document>
|
||||||
26
ios/Runner/Base.lproj/Main.storyboard
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--Flutter View Controller-->
|
||||||
|
<scene sceneID="tne-QT-ifu">
|
||||||
|
<objects>
|
||||||
|
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||||
|
<layoutGuides>
|
||||||
|
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||||
|
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||||
|
</layoutGuides>
|
||||||
|
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
</document>
|
||||||
49
ios/Runner/Info.plist
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>Coffee At Home</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>coffee_at_home</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
<true/>
|
||||||
|
<key>UILaunchStoryboardName</key>
|
||||||
|
<string>LaunchScreen</string>
|
||||||
|
<key>UIMainStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true/>
|
||||||
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
1
ios/Runner/Runner-Bridging-Header.h
Normal file
@ -0,0 +1 @@
|
|||||||
|
#import "GeneratedPluginRegistrant.h"
|
||||||
12
ios/RunnerTests/RunnerTests.swift
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
class RunnerTests: XCTestCase {
|
||||||
|
|
||||||
|
func testExample() {
|
||||||
|
// If you add code to the Runner application, consider adding tests here.
|
||||||
|
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
473
lib/components/bean_dialog.dart
Normal file
@ -0,0 +1,473 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import '../models/bean.dart';
|
||||||
|
import '../providers/app_state.dart';
|
||||||
|
|
||||||
|
class BeanDialog extends StatefulWidget {
|
||||||
|
final Bean? bean; // null for add, non-null for edit
|
||||||
|
|
||||||
|
const BeanDialog({super.key, this.bean});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BeanDialog> createState() => _BeanDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BeanDialogState extends State<BeanDialog> {
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
late final TextEditingController _nameController;
|
||||||
|
late final TextEditingController _varietalController;
|
||||||
|
late final TextEditingController _processingMethodController;
|
||||||
|
late final TextEditingController _originCountryController;
|
||||||
|
late final TextEditingController _roasterNameController;
|
||||||
|
late final TextEditingController _roasterLocationController;
|
||||||
|
|
||||||
|
late RoastLevel _selectedRoastLevel;
|
||||||
|
late DateTime _roastedDate;
|
||||||
|
late bool _isPreferred;
|
||||||
|
late List<TastingNotes> _selectedTastingNotes;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
// Initialize controllers and values
|
||||||
|
final bean = widget.bean;
|
||||||
|
_nameController = TextEditingController(text: bean?.name ?? '');
|
||||||
|
_varietalController = TextEditingController(text: bean?.varietal ?? '');
|
||||||
|
_processingMethodController = TextEditingController(text: bean?.processingMethod ?? '');
|
||||||
|
_originCountryController = TextEditingController(text: bean?.origin ?? '');
|
||||||
|
_roasterNameController = TextEditingController(text: bean?.roaster ?? '');
|
||||||
|
_roasterLocationController = TextEditingController(text: bean?.farm ?? '');
|
||||||
|
|
||||||
|
_selectedRoastLevel = bean?.roastLevel ?? RoastLevel.medium;
|
||||||
|
_roastedDate = bean?.roastDate ?? DateTime.now();
|
||||||
|
_isPreferred = bean?.isOwned ?? false;
|
||||||
|
_selectedTastingNotes = List.from(bean?.flavorNotes ?? []);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_nameController.dispose();
|
||||||
|
_varietalController.dispose();
|
||||||
|
_processingMethodController.dispose();
|
||||||
|
_originCountryController.dispose();
|
||||||
|
_roasterNameController.dispose();
|
||||||
|
_roasterLocationController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Dialog(
|
||||||
|
child: Container(
|
||||||
|
width: MediaQuery.of(context).size.width > 600 ? 600 : double.infinity,
|
||||||
|
height: MediaQuery.of(context).size.height * 0.9,
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// Header
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.coffee,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
size: 28,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
widget.bean == null ? 'Add New Bean' : 'Edit Bean',
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
// Form
|
||||||
|
Expanded(
|
||||||
|
child: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildBasicInfoSection(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
_buildRoastInfoSection(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
_buildOriginSection(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
_buildRoasterSection(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
_buildTastingNotesSection(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
_buildPreferencesSection(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Actions
|
||||||
|
const Divider(),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: _saveBean,
|
||||||
|
child: Text(widget.bean == null ? 'Add Bean' : 'Update Bean'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildBasicInfoSection() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Basic Information',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _nameController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Bean Name *',
|
||||||
|
hintText: 'e.g., Ethiopian Yirgacheffe',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.trim().isEmpty) {
|
||||||
|
return 'Bean name is required';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _varietalController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Varietal *',
|
||||||
|
hintText: 'e.g., Arabica, Bourbon, Typica',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.trim().isEmpty) {
|
||||||
|
return 'Varietal is required';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _processingMethodController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Processing Method *',
|
||||||
|
hintText: 'e.g., Washed, Natural, Honey',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.trim().isEmpty) {
|
||||||
|
return 'Processing method is required';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildRoastInfoSection() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Roast Information',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
DropdownButtonFormField<RoastLevel>(
|
||||||
|
value: _selectedRoastLevel,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Roast Level',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
items: RoastLevel.values.map((level) {
|
||||||
|
return DropdownMenuItem(
|
||||||
|
value: level,
|
||||||
|
child: Text(_formatRoastLevel(level)),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
setState(() {
|
||||||
|
_selectedRoastLevel = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
InkWell(
|
||||||
|
onTap: _selectRoastedDate,
|
||||||
|
child: InputDecorator(
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Roasted Date',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
suffixIcon: Icon(Icons.calendar_today),
|
||||||
|
),
|
||||||
|
child: Text(_formatDate(_roastedDate)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildOriginSection() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Origin Information',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _originCountryController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Origin Country/Region',
|
||||||
|
hintText: 'e.g., Ethiopia, Colombia, Guatemala',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildRoasterSection() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Roaster Information',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _roasterNameController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Roaster Name',
|
||||||
|
hintText: 'e.g., Blue Bottle, Intelligentsia',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _roasterLocationController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Roaster Location',
|
||||||
|
hintText: 'e.g., San Francisco, CA',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTastingNotesSection() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Tasting Notes',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Wrap(
|
||||||
|
spacing: 8,
|
||||||
|
runSpacing: 8,
|
||||||
|
children: TastingNotes.values.map((note) {
|
||||||
|
final isSelected = _selectedTastingNotes.contains(note);
|
||||||
|
return FilterChip(
|
||||||
|
label: Text(_formatTastingNote(note)),
|
||||||
|
selected: isSelected,
|
||||||
|
onSelected: (selected) {
|
||||||
|
setState(() {
|
||||||
|
if (selected) {
|
||||||
|
_selectedTastingNotes.add(note);
|
||||||
|
} else {
|
||||||
|
_selectedTastingNotes.remove(note);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
selectedColor: Theme.of(context).colorScheme.primary.withAlpha(51),
|
||||||
|
checkmarkColor: Theme.of(context).colorScheme.primary,
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildPreferencesSection() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Preferences',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
SwitchListTile(
|
||||||
|
title: const Text('Mark as Preferred'),
|
||||||
|
subtitle: const Text('Add this bean to your favorites'),
|
||||||
|
value: _isPreferred,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_isPreferred = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatRoastLevel(RoastLevel level) {
|
||||||
|
switch (level) {
|
||||||
|
case RoastLevel.light:
|
||||||
|
return 'Light Roast';
|
||||||
|
case RoastLevel.medium:
|
||||||
|
return 'Medium Roast';
|
||||||
|
case RoastLevel.mediumLight:
|
||||||
|
return 'Medium-Light Roast';
|
||||||
|
case RoastLevel.mediumDark:
|
||||||
|
return 'Medium-Dark Roast';
|
||||||
|
case RoastLevel.dark:
|
||||||
|
return 'Dark Roast';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatTastingNote(TastingNotes note) {
|
||||||
|
switch (note) {
|
||||||
|
case TastingNotes.stoneFruit:
|
||||||
|
return 'Stone Fruit';
|
||||||
|
case TastingNotes.tropical:
|
||||||
|
return 'Tropical Fruit';
|
||||||
|
case TastingNotes.driedFruit:
|
||||||
|
return 'Dried Fruit';
|
||||||
|
case TastingNotes.brownSugar:
|
||||||
|
return 'Brown Sugar';
|
||||||
|
default:
|
||||||
|
return note.name.substring(0, 1).toUpperCase() + note.name.substring(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatDate(DateTime date) {
|
||||||
|
return '${date.day}/${date.month}/${date.year}';
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _selectRoastedDate() async {
|
||||||
|
final picked = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: _roastedDate,
|
||||||
|
firstDate: DateTime.now().subtract(const Duration(days: 365)),
|
||||||
|
lastDate: DateTime.now(),
|
||||||
|
);
|
||||||
|
if (picked != null) {
|
||||||
|
setState(() {
|
||||||
|
_roastedDate = picked;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _saveBean() async {
|
||||||
|
if (!_formKey.currentState!.validate()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final bean = Bean(
|
||||||
|
id: widget.bean?.id ?? DateTime.now().millisecondsSinceEpoch.toString(),
|
||||||
|
name: _nameController.text.trim(),
|
||||||
|
origin: _originCountryController.text.trim().isEmpty ? 'Unknown' : _originCountryController.text.trim(),
|
||||||
|
farm: _roasterLocationController.text.trim().isEmpty ? 'Unknown Farm' : _roasterLocationController.text.trim(),
|
||||||
|
producer: 'Unknown Producer',
|
||||||
|
varietal: _varietalController.text.trim().isEmpty ? 'Unknown' : _varietalController.text.trim(),
|
||||||
|
altitude: 1500,
|
||||||
|
processingMethod: _processingMethodController.text.trim().isEmpty ? 'Unknown' : _processingMethodController.text.trim(),
|
||||||
|
harvestSeason: 'Unknown',
|
||||||
|
flavorNotes: _selectedTastingNotes,
|
||||||
|
acidity: Acidity.medium,
|
||||||
|
body: Body.medium,
|
||||||
|
sweetness: 5,
|
||||||
|
roastLevel: _selectedRoastLevel,
|
||||||
|
cupScore: 85.0,
|
||||||
|
price: 15.0,
|
||||||
|
availability: Availability.available,
|
||||||
|
certifications: [],
|
||||||
|
roaster: _roasterNameController.text.trim().isEmpty ? 'Unknown Roaster' : _roasterNameController.text.trim(),
|
||||||
|
roastDate: _roastedDate,
|
||||||
|
bestByDate: _roastedDate.add(Duration(days: 365)),
|
||||||
|
brewingMethods: ['Drip', 'Espresso'],
|
||||||
|
isOwned: _isPreferred,
|
||||||
|
quantity: 250.0,
|
||||||
|
notes: 'User created bean',
|
||||||
|
);
|
||||||
|
|
||||||
|
final appState = Provider.of<AppState>(context, listen: false);
|
||||||
|
|
||||||
|
if (widget.bean == null) {
|
||||||
|
await appState.addBean(bean);
|
||||||
|
} else {
|
||||||
|
await appState.updateBean(bean);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(widget.bean == null
|
||||||
|
? 'Bean added successfully!'
|
||||||
|
: 'Bean updated successfully!'),
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Error saving bean: $e'),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
455
lib/components/global_search.dart
Normal file
@ -0,0 +1,455 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import '../providers/app_state.dart';
|
||||||
|
|
||||||
|
class SearchResult {
|
||||||
|
final String type;
|
||||||
|
final String id;
|
||||||
|
final String title;
|
||||||
|
final String subtitle;
|
||||||
|
final IconData icon;
|
||||||
|
final dynamic data;
|
||||||
|
|
||||||
|
SearchResult({
|
||||||
|
required this.type,
|
||||||
|
required this.id,
|
||||||
|
required this.title,
|
||||||
|
required this.subtitle,
|
||||||
|
required this.icon,
|
||||||
|
required this.data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class GlobalSearchWidget extends StatefulWidget {
|
||||||
|
final bool isOpen;
|
||||||
|
final VoidCallback onClose;
|
||||||
|
final Function(SearchResult) onResultSelected;
|
||||||
|
|
||||||
|
const GlobalSearchWidget({
|
||||||
|
super.key,
|
||||||
|
required this.isOpen,
|
||||||
|
required this.onClose,
|
||||||
|
required this.onResultSelected,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<GlobalSearchWidget> createState() => _GlobalSearchWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GlobalSearchWidgetState extends State<GlobalSearchWidget> {
|
||||||
|
final TextEditingController _searchController = TextEditingController();
|
||||||
|
List<SearchResult> _searchResults = [];
|
||||||
|
bool _isSearching = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_searchController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _performSearch(String query) {
|
||||||
|
if (query.isEmpty) {
|
||||||
|
setState(() {
|
||||||
|
_searchResults = [];
|
||||||
|
_isSearching = false;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_isSearching = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
final appState = Provider.of<AppState>(context, listen: false);
|
||||||
|
final results = <SearchResult>[];
|
||||||
|
|
||||||
|
// Search beans
|
||||||
|
for (final bean in appState.beans) {
|
||||||
|
if (_matchesQuery(bean.name, query) ||
|
||||||
|
_matchesQuery(bean.varietal, query) ||
|
||||||
|
_matchesQuery(bean.originCountry?.id ?? '', query) ||
|
||||||
|
_matchesQuery(bean.roastLevel.name, query)) {
|
||||||
|
results.add(SearchResult(
|
||||||
|
type: 'Bean',
|
||||||
|
id: bean.id,
|
||||||
|
title: bean.name,
|
||||||
|
subtitle: '${bean.varietal} • ${bean.originCountry?.id ?? 'Unknown'}',
|
||||||
|
icon: Icons.coffee,
|
||||||
|
data: bean,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search machines
|
||||||
|
for (final machine in appState.machines) {
|
||||||
|
if (_matchesQuery(machine.model, query) ||
|
||||||
|
_matchesQuery(machine.type.name, query) ||
|
||||||
|
_matchesQuery(machine.manufacturer, query)) {
|
||||||
|
results.add(SearchResult(
|
||||||
|
type: 'Machine',
|
||||||
|
id: machine.id,
|
||||||
|
title: machine.model,
|
||||||
|
subtitle: '${machine.manufacturer} • ${machine.type.name}',
|
||||||
|
icon: Icons.kitchen,
|
||||||
|
data: machine,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search recipes
|
||||||
|
for (final recipe in appState.recipes) {
|
||||||
|
if (_matchesQuery(recipe.name, query) ||
|
||||||
|
_matchesQuery(recipe.brewMethod.name, query) ||
|
||||||
|
_matchesQuery(recipe.instructions, query) ||
|
||||||
|
_matchesQuery(recipe.notes ?? '', query)) {
|
||||||
|
results.add(SearchResult(
|
||||||
|
type: 'Recipe',
|
||||||
|
id: recipe.id,
|
||||||
|
title: recipe.name,
|
||||||
|
subtitle: '${recipe.brewMethod.name} • ${recipe.grindSize}',
|
||||||
|
icon: Icons.menu_book,
|
||||||
|
data: recipe,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search journal entries
|
||||||
|
for (final entry in appState.journalEntries) {
|
||||||
|
if (_matchesQuery(entry.drink.name, query) ||
|
||||||
|
_matchesQuery(entry.notes ?? '', query) ||
|
||||||
|
_matchesQuery(entry.mood ?? '', query) ||
|
||||||
|
_matchesQuery(entry.weather ?? '', query)) {
|
||||||
|
results.add(SearchResult(
|
||||||
|
type: 'Journal',
|
||||||
|
id: entry.id,
|
||||||
|
title: entry.drink.name,
|
||||||
|
subtitle: 'Rating: ${entry.drink.rating}/5 • ${_formatDate(entry.date)}',
|
||||||
|
icon: Icons.book,
|
||||||
|
data: entry,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort results by relevance (exact matches first)
|
||||||
|
results.sort((a, b) {
|
||||||
|
final aExact = a.title.toLowerCase() == query.toLowerCase();
|
||||||
|
final bExact = b.title.toLowerCase() == query.toLowerCase();
|
||||||
|
if (aExact && !bExact) return -1;
|
||||||
|
if (!aExact && bExact) return 1;
|
||||||
|
return a.title.compareTo(b.title);
|
||||||
|
});
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_searchResults = results;
|
||||||
|
_isSearching = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _matchesQuery(String text, String query) {
|
||||||
|
return text.toLowerCase().contains(query.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatDate(DateTime date) {
|
||||||
|
return '${date.day}/${date.month}/${date.year}';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (!widget.isOpen) return const SizedBox.shrink();
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
color: Colors.black.withAlpha((0.6 * 255).toInt()),
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
width: MediaQuery.of(context).size.width > 600
|
||||||
|
? 600
|
||||||
|
: MediaQuery.of(context).size.width - 32,
|
||||||
|
height: MediaQuery.of(context).size.height > 600
|
||||||
|
? 600
|
||||||
|
: MediaQuery.of(context).size.height - 100,
|
||||||
|
margin: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFF2D2D2D),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(color: const Color(0xFF3A3A3A)),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withAlpha((0.5 * 255).toInt()),
|
||||||
|
blurRadius: 20,
|
||||||
|
offset: const Offset(0, 10),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// Header
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(color: Color(0xFF3A3A3A)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.search,
|
||||||
|
color: Color(0xFFD4A574),
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
const Expanded(
|
||||||
|
child: Text(
|
||||||
|
'Global Search',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xFFF5F5DC),
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.close,
|
||||||
|
color: Color(0xFFF5F5DC),
|
||||||
|
),
|
||||||
|
onPressed: widget.onClose,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Search input
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: TextField(
|
||||||
|
controller: _searchController,
|
||||||
|
autofocus: true,
|
||||||
|
style: const TextStyle(color: Color(0xFFF5F5DC)),
|
||||||
|
onChanged: _performSearch,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Search beans, machines, recipes, journal entries...',
|
||||||
|
hintStyle: const TextStyle(color: Color(0xFFD2B48C)),
|
||||||
|
prefixIcon: const Icon(
|
||||||
|
Icons.search,
|
||||||
|
color: Color(0xFFD4A574),
|
||||||
|
),
|
||||||
|
suffixIcon: _searchController.text.isNotEmpty
|
||||||
|
? IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.clear,
|
||||||
|
color: Color(0xFFD2B48C),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
_searchController.clear();
|
||||||
|
_performSearch('');
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
borderSide: const BorderSide(color: Color(0xFF3A3A3A)),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
borderSide: const BorderSide(color: Color(0xFFD4A574)),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
borderSide: const BorderSide(color: Color(0xFF3A3A3A)),
|
||||||
|
),
|
||||||
|
filled: true,
|
||||||
|
fillColor: const Color(0xFF1A1A1A),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Results
|
||||||
|
Expanded(
|
||||||
|
child: _buildSearchResults(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSearchResults() {
|
||||||
|
if (_isSearching) {
|
||||||
|
return const Center(
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
color: Color(0xFFD4A574),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_searchController.text.isEmpty) {
|
||||||
|
return const Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.search,
|
||||||
|
size: 64,
|
||||||
|
color: Color(0xFF3A3A3A),
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'Start typing to search...',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xFFD2B48C),
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'Search across beans, machines, recipes, and journal entries',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xFF3A3A3A),
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_searchResults.isEmpty) {
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.search_off,
|
||||||
|
size: 64,
|
||||||
|
color: Color(0xFF3A3A3A),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'No results found for "${_searchController.text}"',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Color(0xFFD2B48C),
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
const Text(
|
||||||
|
'Try different keywords or check your spelling',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xFF3A3A3A),
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListView.builder(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
itemCount: _searchResults.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final result = _searchResults[index];
|
||||||
|
return _buildSearchResultItem(result);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSearchResultItem(SearchResult result) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
widget.onResultSelected(result);
|
||||||
|
widget.onClose();
|
||||||
|
},
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(color: const Color(0xFF3A3A3A)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFD4A574).withAlpha((0.2 * 255).toInt()),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
result.icon,
|
||||||
|
color: const Color(0xFFD4A574),
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 6,
|
||||||
|
vertical: 2,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFF6F4E37),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
result.type,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Color(0xFFF5F5DC),
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
result.title,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Color(0xFFF5F5DC),
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
result.subtitle,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Color(0xFFD2B48C),
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Icon(
|
||||||
|
Icons.arrow_forward_ios,
|
||||||
|
color: Color(0xFF3A3A3A),
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
624
lib/components/journal_entry_dialog.dart
Normal file
@ -0,0 +1,624 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import '../models/journal_entry.dart';
|
||||||
|
import '../models/drink.dart';
|
||||||
|
import '../models/bean.dart';
|
||||||
|
import '../models/machine.dart';
|
||||||
|
import '../models/recipe.dart';
|
||||||
|
import '../providers/app_state.dart';
|
||||||
|
import 'searchable_selection.dart';
|
||||||
|
import 'bean_dialog.dart';
|
||||||
|
import 'machine_dialog.dart';
|
||||||
|
import 'recipe_dialog.dart';
|
||||||
|
|
||||||
|
class JournalEntryDialog extends StatefulWidget {
|
||||||
|
final JournalEntry? journalEntry; // null for add, non-null for edit
|
||||||
|
|
||||||
|
const JournalEntryDialog({super.key, this.journalEntry});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<JournalEntryDialog> createState() => _JournalEntryDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _JournalEntryDialogState extends State<JournalEntryDialog> {
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
late final TextEditingController _drinkNameController;
|
||||||
|
late final TextEditingController _drinkDetailsController;
|
||||||
|
late final TextEditingController _drinkNotesController;
|
||||||
|
late final TextEditingController _drinkSizeController;
|
||||||
|
late final TextEditingController _journalNotesController;
|
||||||
|
late final TextEditingController _moodController;
|
||||||
|
late final TextEditingController _weatherController;
|
||||||
|
|
||||||
|
late DateTime _selectedDate;
|
||||||
|
late double _rating;
|
||||||
|
late bool _isPreferred;
|
||||||
|
Bean? _selectedBean;
|
||||||
|
Machine? _selectedMachine;
|
||||||
|
Recipe? _selectedRecipe;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
final journalEntry = widget.journalEntry;
|
||||||
|
final drink = journalEntry?.drink;
|
||||||
|
|
||||||
|
_drinkNameController = TextEditingController(text: drink?.name ?? '');
|
||||||
|
_drinkDetailsController = TextEditingController(text: drink?.details ?? '');
|
||||||
|
_drinkNotesController = TextEditingController(text: drink?.notes ?? '');
|
||||||
|
_drinkSizeController = TextEditingController(text: drink?.size ?? '');
|
||||||
|
_journalNotesController = TextEditingController(text: journalEntry?.notes ?? '');
|
||||||
|
_moodController = TextEditingController(text: journalEntry?.mood ?? '');
|
||||||
|
_weatherController = TextEditingController(text: journalEntry?.weather ?? '');
|
||||||
|
|
||||||
|
_selectedDate = journalEntry?.date ?? DateTime.now();
|
||||||
|
_rating = drink?.rating ?? 3.0;
|
||||||
|
_isPreferred = drink?.preferred ?? false;
|
||||||
|
_selectedBean = drink?.bean;
|
||||||
|
_selectedMachine = drink?.machine;
|
||||||
|
_selectedRecipe = drink?.recipe;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_drinkNameController.dispose();
|
||||||
|
_drinkDetailsController.dispose();
|
||||||
|
_drinkNotesController.dispose();
|
||||||
|
_drinkSizeController.dispose();
|
||||||
|
_journalNotesController.dispose();
|
||||||
|
_moodController.dispose();
|
||||||
|
_weatherController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Dialog(
|
||||||
|
child: Container(
|
||||||
|
width: MediaQuery.of(context).size.width > 600 ? 600 : double.infinity,
|
||||||
|
height: MediaQuery.of(context).size.height * 0.9,
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// Header
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.book,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
size: 28,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
widget.journalEntry == null ? 'Add Journal Entry' : 'Edit Journal Entry',
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
// Form
|
||||||
|
Expanded(
|
||||||
|
child: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildDateSection(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
_buildDrinkInfoSection(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
_buildRatingSection(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
_buildReferencesSection(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
_buildJournalSection(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Actions
|
||||||
|
const Divider(),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: _saveJournalEntry,
|
||||||
|
child: Text(widget.journalEntry == null ? 'Add Entry' : 'Update Entry'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDateSection() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Entry Date',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
InkWell(
|
||||||
|
onTap: _selectDate,
|
||||||
|
child: InputDecorator(
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Date',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
suffixIcon: Icon(Icons.calendar_today),
|
||||||
|
),
|
||||||
|
child: Text(_formatDate(_selectedDate)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDrinkInfoSection() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Drink Information',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _drinkNameController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Drink Name *',
|
||||||
|
hintText: 'e.g., Morning Latte, Afternoon Espresso',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.trim().isEmpty) {
|
||||||
|
return 'Drink name is required';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
controller: _drinkSizeController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Size',
|
||||||
|
hintText: 'e.g., 8oz, 12oz, Large',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: SwitchListTile(
|
||||||
|
title: const Text('Preferred'),
|
||||||
|
value: _isPreferred,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_isPreferred = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _drinkDetailsController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Details',
|
||||||
|
hintText: 'Additional details about the drink',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
maxLines: 2,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _drinkNotesController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Drink Notes',
|
||||||
|
hintText: 'Notes about the drink preparation or taste',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
maxLines: 2,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildRatingSection() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Rating',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Text('Rating:'),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Slider(
|
||||||
|
value: _rating,
|
||||||
|
min: 1.0,
|
||||||
|
max: 5.0,
|
||||||
|
divisions: 8,
|
||||||
|
label: '${_rating.toStringAsFixed(1)} stars',
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_rating = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Row(
|
||||||
|
children: List.generate(5, (index) {
|
||||||
|
return Icon(
|
||||||
|
index < _rating.floor()
|
||||||
|
? Icons.star
|
||||||
|
: index < _rating
|
||||||
|
? Icons.star_half
|
||||||
|
: Icons.star_border,
|
||||||
|
color: Colors.amber,
|
||||||
|
size: 20,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildReferencesSection() {
|
||||||
|
return Consumer<AppState>(
|
||||||
|
builder: (context, appState, child) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'References (Optional)',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
// Bean Selection
|
||||||
|
_buildSelectionField(
|
||||||
|
label: 'Bean Used',
|
||||||
|
value: _selectedBean?.name ?? 'No bean selected',
|
||||||
|
onTap: () => _selectBean(context),
|
||||||
|
icon: Icons.coffee_outlined,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
// Machine Selection
|
||||||
|
_buildSelectionField(
|
||||||
|
label: 'Machine Used',
|
||||||
|
value: _selectedMachine != null
|
||||||
|
? '${_selectedMachine!.manufacturer} ${_selectedMachine!.model}'
|
||||||
|
: 'No machine selected',
|
||||||
|
onTap: () => _selectMachine(context),
|
||||||
|
icon: Icons.coffee_maker,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
// Recipe Selection
|
||||||
|
_buildSelectionField(
|
||||||
|
label: 'Recipe Used',
|
||||||
|
value: _selectedRecipe?.name ?? 'No recipe selected',
|
||||||
|
onTap: () => _selectRecipe(context),
|
||||||
|
icon: Icons.menu_book,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildJournalSection() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Journal Notes',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _journalNotesController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Journal Notes',
|
||||||
|
hintText: 'Your thoughts, feelings, or observations about this coffee experience...',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
alignLabelWithHint: true,
|
||||||
|
),
|
||||||
|
maxLines: 4,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
controller: _moodController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Mood',
|
||||||
|
hintText: 'e.g., Energetic, Relaxed',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
controller: _weatherController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Weather',
|
||||||
|
hintText: 'e.g., Sunny, Rainy',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatDate(DateTime date) {
|
||||||
|
return '${date.day}/${date.month}/${date.year}';
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _selectDate() async {
|
||||||
|
final picked = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: _selectedDate,
|
||||||
|
firstDate: DateTime.now().subtract(const Duration(days: 365 * 2)),
|
||||||
|
lastDate: DateTime.now(),
|
||||||
|
);
|
||||||
|
if (picked != null) {
|
||||||
|
setState(() {
|
||||||
|
_selectedDate = picked;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _saveJournalEntry() async {
|
||||||
|
if (!_formKey.currentState!.validate()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create the drink
|
||||||
|
final drink = Drink(
|
||||||
|
id: widget.journalEntry?.drink.id ?? DateTime.now().millisecondsSinceEpoch.toString(),
|
||||||
|
name: _drinkNameController.text.trim(),
|
||||||
|
details: _drinkDetailsController.text.trim(),
|
||||||
|
notes: _drinkNotesController.text.trim(),
|
||||||
|
preferred: _isPreferred,
|
||||||
|
rating: _rating,
|
||||||
|
size: _drinkSizeController.text.trim(),
|
||||||
|
bean: _selectedBean,
|
||||||
|
machine: _selectedMachine,
|
||||||
|
recipe: _selectedRecipe,
|
||||||
|
dateCreated: _selectedDate,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create the journal entry
|
||||||
|
final journalEntry = JournalEntry(
|
||||||
|
id: widget.journalEntry?.id ?? DateTime.now().millisecondsSinceEpoch.toString(),
|
||||||
|
date: _selectedDate,
|
||||||
|
drink: drink,
|
||||||
|
notes: _journalNotesController.text.trim().isEmpty ? null : _journalNotesController.text.trim(),
|
||||||
|
mood: _moodController.text.trim().isEmpty ? null : _moodController.text.trim(),
|
||||||
|
weather: _weatherController.text.trim().isEmpty ? null : _weatherController.text.trim(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final appState = Provider.of<AppState>(context, listen: false);
|
||||||
|
|
||||||
|
// Save or update the drink first
|
||||||
|
if (widget.journalEntry == null) {
|
||||||
|
await appState.addDrink(drink);
|
||||||
|
await appState.addJournalEntry(journalEntry);
|
||||||
|
} else {
|
||||||
|
await appState.updateDrink(drink);
|
||||||
|
await appState.updateJournalEntry(journalEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(widget.journalEntry == null
|
||||||
|
? 'Journal entry added successfully!'
|
||||||
|
: 'Journal entry updated successfully!'),
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Error saving journal entry: $e'),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSelectionField({
|
||||||
|
required String label,
|
||||||
|
required String value,
|
||||||
|
required VoidCallback onTap,
|
||||||
|
required IconData icon,
|
||||||
|
}) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: Colors.grey),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(icon, size: 20),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.grey[600],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: const TextStyle(fontSize: 16),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Icon(Icons.arrow_forward_ios, size: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _selectBean(BuildContext context) {
|
||||||
|
final appState = Provider.of<AppState>(context, listen: false);
|
||||||
|
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => SearchableSelection<Bean>(
|
||||||
|
items: appState.beans,
|
||||||
|
title: 'Select Bean',
|
||||||
|
searchHint: 'Search beans...',
|
||||||
|
displayText: (bean) => bean.name,
|
||||||
|
onItemSelected: (bean) {
|
||||||
|
setState(() {
|
||||||
|
_selectedBean = bean;
|
||||||
|
});
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
onAddCustom: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => const BeanDialog(),
|
||||||
|
).then((_) {
|
||||||
|
// Refresh the state after adding a new bean
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _selectMachine(BuildContext context) {
|
||||||
|
final appState = Provider.of<AppState>(context, listen: false);
|
||||||
|
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => SearchableSelection<Machine>(
|
||||||
|
items: appState.machines,
|
||||||
|
title: 'Select Machine',
|
||||||
|
searchHint: 'Search machines...',
|
||||||
|
displayText: (machine) => '${machine.manufacturer} ${machine.model}',
|
||||||
|
onItemSelected: (machine) {
|
||||||
|
setState(() {
|
||||||
|
_selectedMachine = machine;
|
||||||
|
});
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
onAddCustom: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => const MachineDialog(),
|
||||||
|
).then((_) {
|
||||||
|
// Refresh the state after adding a new machine
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _selectRecipe(BuildContext context) {
|
||||||
|
final appState = Provider.of<AppState>(context, listen: false);
|
||||||
|
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => SearchableSelection<Recipe>(
|
||||||
|
items: appState.recipes,
|
||||||
|
title: 'Select Recipe',
|
||||||
|
searchHint: 'Search recipes...',
|
||||||
|
displayText: (recipe) => recipe.name,
|
||||||
|
onItemSelected: (recipe) {
|
||||||
|
setState(() {
|
||||||
|
_selectedRecipe = recipe;
|
||||||
|
});
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
onAddCustom: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => const RecipeDialog(),
|
||||||
|
).then((_) {
|
||||||
|
// Refresh the state after adding a new recipe
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
328
lib/components/machine_dialog.dart
Normal file
@ -0,0 +1,328 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import '../models/machine.dart';
|
||||||
|
import '../providers/app_state.dart';
|
||||||
|
|
||||||
|
class MachineDialog extends StatefulWidget {
|
||||||
|
final Machine? machine; // null for add, non-null for edit
|
||||||
|
|
||||||
|
const MachineDialog({super.key, this.machine});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MachineDialog> createState() => _MachineDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MachineDialogState extends State<MachineDialog> {
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
late final TextEditingController _modelController;
|
||||||
|
late final TextEditingController _manufacturerController;
|
||||||
|
late final TextEditingController _yearController;
|
||||||
|
late final TextEditingController _detailsController;
|
||||||
|
|
||||||
|
late MachineType _selectedType;
|
||||||
|
late bool _hasSteamWand;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
final machine = widget.machine;
|
||||||
|
_modelController = TextEditingController(text: machine?.model ?? '');
|
||||||
|
_manufacturerController = TextEditingController(text: machine?.manufacturer ?? '');
|
||||||
|
_yearController = TextEditingController(text: machine?.year.toString() ?? '');
|
||||||
|
_detailsController = TextEditingController(text: machine?.details ?? '');
|
||||||
|
|
||||||
|
_selectedType = machine?.type ?? MachineType.espresso;
|
||||||
|
_hasSteamWand = machine?.steamWand ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_modelController.dispose();
|
||||||
|
_manufacturerController.dispose();
|
||||||
|
_yearController.dispose();
|
||||||
|
_detailsController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Dialog(
|
||||||
|
child: Container(
|
||||||
|
width: MediaQuery.of(context).size.width > 600 ? 600 : double.infinity,
|
||||||
|
height: MediaQuery.of(context).size.height * 0.8,
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// Header
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.kitchen,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
size: 28,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
widget.machine == null ? 'Add New Machine' : 'Edit Machine',
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
// Form
|
||||||
|
Expanded(
|
||||||
|
child: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildBasicInfoSection(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
_buildSpecificationSection(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
_buildFeaturesSection(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Actions
|
||||||
|
const Divider(),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: _saveMachine,
|
||||||
|
child: Text(widget.machine == null ? 'Add Machine' : 'Update Machine'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildBasicInfoSection() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Basic Information',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _modelController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Model Name *',
|
||||||
|
hintText: 'e.g., Breville Barista Express',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.trim().isEmpty) {
|
||||||
|
return 'Model name is required';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _manufacturerController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Manufacturer *',
|
||||||
|
hintText: 'e.g., Breville, De\'Longhi, Gaggia',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.trim().isEmpty) {
|
||||||
|
return 'Manufacturer is required';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _yearController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Year',
|
||||||
|
hintText: 'e.g., 2023',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
validator: (value) {
|
||||||
|
if (value != null && value.isNotEmpty) {
|
||||||
|
final year = int.tryParse(value);
|
||||||
|
if (year == null || year < 1900 || year > DateTime.now().year + 1) {
|
||||||
|
return 'Please enter a valid year';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSpecificationSection() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Specifications',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
DropdownButtonFormField<MachineType>(
|
||||||
|
value: _selectedType,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Machine Type',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
items: MachineType.values.map((type) {
|
||||||
|
return DropdownMenuItem(
|
||||||
|
value: type,
|
||||||
|
child: Text(_formatMachineType(type)),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
setState(() {
|
||||||
|
_selectedType = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _detailsController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Details & Notes',
|
||||||
|
hintText: 'Additional information about the machine',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
maxLines: 3,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildFeaturesSection() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Features',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
SwitchListTile(
|
||||||
|
title: const Text('Steam Wand'),
|
||||||
|
subtitle: const Text('Does this machine have a steam wand for milk?'),
|
||||||
|
value: _hasSteamWand,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_hasSteamWand = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatMachineType(MachineType type) {
|
||||||
|
switch (type) {
|
||||||
|
case MachineType.espresso:
|
||||||
|
return 'Espresso Machine';
|
||||||
|
case MachineType.drip:
|
||||||
|
return 'Drip Coffee Maker';
|
||||||
|
case MachineType.percolation:
|
||||||
|
return 'Percolation';
|
||||||
|
case MachineType.frenchPress:
|
||||||
|
return 'French Press';
|
||||||
|
case MachineType.coldBrew:
|
||||||
|
return 'Cold Brew Maker';
|
||||||
|
case MachineType.e61:
|
||||||
|
return 'E61 Group Head';
|
||||||
|
case MachineType.pod:
|
||||||
|
return 'Pod Machine';
|
||||||
|
case MachineType.espressoPod:
|
||||||
|
return 'Espresso Pod Machine';
|
||||||
|
case MachineType.grinder:
|
||||||
|
return 'Coffee Grinder';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _saveMachine() async {
|
||||||
|
if (!_formKey.currentState!.validate()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final machine = Machine(
|
||||||
|
id: widget.machine?.id ?? DateTime.now().millisecondsSinceEpoch.toString(),
|
||||||
|
model: _modelController.text.trim(),
|
||||||
|
manufacturer: _manufacturerController.text.trim(),
|
||||||
|
year: int.tryParse(_yearController.text.trim()) ?? DateTime.now().year,
|
||||||
|
type: _selectedType,
|
||||||
|
steamWand: _hasSteamWand,
|
||||||
|
details: _detailsController.text.trim(),
|
||||||
|
isOwned: true,
|
||||||
|
rating: 4.0,
|
||||||
|
popularity: 50,
|
||||||
|
portafilters: [],
|
||||||
|
specifications: {},
|
||||||
|
);
|
||||||
|
|
||||||
|
final appState = Provider.of<AppState>(context, listen: false);
|
||||||
|
|
||||||
|
if (widget.machine == null) {
|
||||||
|
await appState.addMachine(machine);
|
||||||
|
} else {
|
||||||
|
await appState.updateMachine(machine);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(widget.machine == null
|
||||||
|
? 'Machine added successfully!'
|
||||||
|
: 'Machine updated successfully!'),
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Error saving machine: $e'),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
485
lib/components/recipe_dialog.dart
Normal file
@ -0,0 +1,485 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import '../models/recipe.dart';
|
||||||
|
import '../providers/app_state.dart';
|
||||||
|
|
||||||
|
class RecipeDialog extends StatefulWidget {
|
||||||
|
final Recipe? recipe; // null for add, non-null for edit
|
||||||
|
|
||||||
|
const RecipeDialog({super.key, this.recipe});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<RecipeDialog> createState() => _RecipeDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RecipeDialogState extends State<RecipeDialog> {
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
late final TextEditingController _nameController;
|
||||||
|
late final TextEditingController _grindSizeController;
|
||||||
|
late final TextEditingController _coffeeAmountController;
|
||||||
|
late final TextEditingController _waterAmountController;
|
||||||
|
late final TextEditingController _brewTimeController;
|
||||||
|
late final TextEditingController _instructionsController;
|
||||||
|
late final TextEditingController _notesController;
|
||||||
|
|
||||||
|
late ServingTemp _selectedServingTemp;
|
||||||
|
late MilkType? _selectedMilkType;
|
||||||
|
late BrewMethod _selectedBrewMethod;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
final recipe = widget.recipe;
|
||||||
|
_nameController = TextEditingController(text: recipe?.name ?? '');
|
||||||
|
_grindSizeController = TextEditingController(text: recipe?.grindSize.toString() ?? '');
|
||||||
|
_coffeeAmountController = TextEditingController(text: recipe?.coffeeAmount.toString() ?? '');
|
||||||
|
_waterAmountController = TextEditingController(text: recipe?.waterAmount.toString() ?? '');
|
||||||
|
_brewTimeController = TextEditingController(text: recipe?.brewTime.toString() ?? '');
|
||||||
|
_instructionsController = TextEditingController(text: recipe?.instructions ?? '');
|
||||||
|
_notesController = TextEditingController(text: recipe?.notes ?? '');
|
||||||
|
|
||||||
|
_selectedServingTemp = recipe?.servingTemp ?? ServingTemp.hot;
|
||||||
|
_selectedMilkType = recipe?.milkType;
|
||||||
|
_selectedBrewMethod = recipe?.brewMethod ?? BrewMethod.espresso;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_nameController.dispose();
|
||||||
|
_grindSizeController.dispose();
|
||||||
|
_coffeeAmountController.dispose();
|
||||||
|
_waterAmountController.dispose();
|
||||||
|
_brewTimeController.dispose();
|
||||||
|
_instructionsController.dispose();
|
||||||
|
_notesController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Dialog(
|
||||||
|
child: Container(
|
||||||
|
width: MediaQuery.of(context).size.width > 600 ? 600 : double.infinity,
|
||||||
|
height: MediaQuery.of(context).size.height * 0.9,
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// Header
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.menu_book,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
size: 28,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
widget.recipe == null ? 'Add New Recipe' : 'Edit Recipe',
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
// Form
|
||||||
|
Expanded(
|
||||||
|
child: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildBasicInfoSection(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
_buildBrewingParametersSection(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
_buildInstructionsSection(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Actions
|
||||||
|
const Divider(),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: _saveRecipe,
|
||||||
|
child: Text(widget.recipe == null ? 'Add Recipe' : 'Update Recipe'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildBasicInfoSection() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Basic Information',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _nameController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Recipe Name *',
|
||||||
|
hintText: 'e.g., Morning Espresso, Chemex Pour Over',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.trim().isEmpty) {
|
||||||
|
return 'Recipe name is required';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: DropdownButtonFormField<BrewMethod>(
|
||||||
|
value: _selectedBrewMethod,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Brew Method',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
items: BrewMethod.values.map((method) {
|
||||||
|
return DropdownMenuItem(
|
||||||
|
value: method,
|
||||||
|
child: Text(_formatBrewMethod(method)),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
setState(() {
|
||||||
|
_selectedBrewMethod = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: DropdownButtonFormField<ServingTemp>(
|
||||||
|
value: _selectedServingTemp,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Serving Temperature',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
items: ServingTemp.values.map((temp) {
|
||||||
|
return DropdownMenuItem(
|
||||||
|
value: temp,
|
||||||
|
child: Text(_formatServingTemp(temp)),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
setState(() {
|
||||||
|
_selectedServingTemp = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
DropdownButtonFormField<MilkType?>(
|
||||||
|
value: _selectedMilkType,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Milk Type (Optional)',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
items: [
|
||||||
|
const DropdownMenuItem<MilkType?>(
|
||||||
|
value: null,
|
||||||
|
child: Text('No Milk'),
|
||||||
|
),
|
||||||
|
...MilkType.values.map((milk) {
|
||||||
|
return DropdownMenuItem(
|
||||||
|
value: milk,
|
||||||
|
child: Text(_formatMilkType(milk)),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_selectedMilkType = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildBrewingParametersSection() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Brewing Parameters',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
controller: _coffeeAmountController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Coffee Amount (g) *',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.trim().isEmpty) {
|
||||||
|
return 'Coffee amount is required';
|
||||||
|
}
|
||||||
|
if (double.tryParse(value) == null) {
|
||||||
|
return 'Please enter a valid number';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
controller: _waterAmountController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Water Amount (ml) *',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.trim().isEmpty) {
|
||||||
|
return 'Water amount is required';
|
||||||
|
}
|
||||||
|
if (double.tryParse(value) == null) {
|
||||||
|
return 'Please enter a valid number';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
controller: _grindSizeController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Grind Size *',
|
||||||
|
hintText: 'e.g., Fine, Medium, Coarse',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.trim().isEmpty) {
|
||||||
|
return 'Grind size is required';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
controller: _brewTimeController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Brew Time (seconds) *',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.trim().isEmpty) {
|
||||||
|
return 'Brew time is required';
|
||||||
|
}
|
||||||
|
if (int.tryParse(value) == null) {
|
||||||
|
return 'Please enter a valid number';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildInstructionsSection() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Instructions & Notes',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _instructionsController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Brewing Instructions *',
|
||||||
|
hintText: 'Step-by-step brewing instructions...',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
alignLabelWithHint: true,
|
||||||
|
),
|
||||||
|
maxLines: 4,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.trim().isEmpty) {
|
||||||
|
return 'Instructions are required';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _notesController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Additional Notes',
|
||||||
|
hintText: 'Tips, variations, or other notes...',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
alignLabelWithHint: true,
|
||||||
|
),
|
||||||
|
maxLines: 3,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatBrewMethod(BrewMethod method) {
|
||||||
|
switch (method) {
|
||||||
|
case BrewMethod.drip:
|
||||||
|
return 'Drip Coffee';
|
||||||
|
case BrewMethod.frenchPress:
|
||||||
|
return 'French Press';
|
||||||
|
case BrewMethod.pourOver:
|
||||||
|
return 'Pour Over';
|
||||||
|
case BrewMethod.espresso:
|
||||||
|
return 'Espresso';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatServingTemp(ServingTemp temp) {
|
||||||
|
switch (temp) {
|
||||||
|
case ServingTemp.hot:
|
||||||
|
return 'Hot';
|
||||||
|
case ServingTemp.cold:
|
||||||
|
return 'Cold';
|
||||||
|
case ServingTemp.iced:
|
||||||
|
return 'Iced';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatMilkType(MilkType milk) {
|
||||||
|
switch (milk) {
|
||||||
|
case MilkType.whole:
|
||||||
|
return 'Whole Milk';
|
||||||
|
case MilkType.skim:
|
||||||
|
return 'Skim Milk';
|
||||||
|
case MilkType.soy:
|
||||||
|
return 'Soy Milk';
|
||||||
|
case MilkType.almond:
|
||||||
|
return 'Almond Milk';
|
||||||
|
case MilkType.coconut:
|
||||||
|
return 'Coconut Milk';
|
||||||
|
case MilkType.oat:
|
||||||
|
return 'Oat Milk';
|
||||||
|
case MilkType.pistachio:
|
||||||
|
return 'Pistachio Milk';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _saveRecipe() async {
|
||||||
|
if (!_formKey.currentState!.validate()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final recipe = Recipe(
|
||||||
|
id: widget.recipe?.id ?? DateTime.now().millisecondsSinceEpoch.toString(),
|
||||||
|
name: _nameController.text.trim(),
|
||||||
|
servingTemp: _selectedServingTemp,
|
||||||
|
milkType: _selectedMilkType,
|
||||||
|
brewMethod: _selectedBrewMethod,
|
||||||
|
grindSize: GrindSize.medium, // Parse from _grindSizeController if needed
|
||||||
|
coffeeAmount: double.parse(_coffeeAmountController.text.trim()),
|
||||||
|
waterAmount: double.parse(_waterAmountController.text.trim()),
|
||||||
|
brewTime: int.parse(_brewTimeController.text.trim()),
|
||||||
|
instructions: _instructionsController.text.trim(),
|
||||||
|
notes: _notesController.text.trim().isEmpty ? null : _notesController.text.trim(),
|
||||||
|
difficulty: Difficulty.intermediate,
|
||||||
|
equipmentNeeded: ['Grinder', 'Scale'],
|
||||||
|
yieldAmount: double.parse(_waterAmountController.text.trim()),
|
||||||
|
caffeinePer100ml: 50.0,
|
||||||
|
waterTemperature: 93,
|
||||||
|
bloomTime: 30,
|
||||||
|
totalExtractionTime: int.parse(_brewTimeController.text.trim()),
|
||||||
|
grindToWaterRatio: '1:16',
|
||||||
|
tags: [],
|
||||||
|
origin: 'User Created',
|
||||||
|
rating: 4.0,
|
||||||
|
popularity: 50,
|
||||||
|
createdBy: 'User',
|
||||||
|
isPublic: false,
|
||||||
|
lastModified: DateTime.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final appState = Provider.of<AppState>(context, listen: false);
|
||||||
|
|
||||||
|
if (widget.recipe == null) {
|
||||||
|
await appState.addRecipe(recipe);
|
||||||
|
} else {
|
||||||
|
await appState.updateRecipe(recipe);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(widget.recipe == null
|
||||||
|
? 'Recipe added successfully!'
|
||||||
|
: 'Recipe updated successfully!'),
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Error saving recipe: $e'),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
370
lib/components/searchable_selection.dart
Normal file
@ -0,0 +1,370 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../models/bean.dart';
|
||||||
|
import '../models/machine.dart';
|
||||||
|
import '../models/recipe.dart';
|
||||||
|
|
||||||
|
class SearchableSelection<T> extends StatefulWidget {
|
||||||
|
final List<T> items;
|
||||||
|
final String title;
|
||||||
|
final String Function(T) displayText;
|
||||||
|
final void Function(T) onItemSelected;
|
||||||
|
final VoidCallback onAddCustom;
|
||||||
|
final String? searchHint;
|
||||||
|
|
||||||
|
const SearchableSelection({
|
||||||
|
super.key,
|
||||||
|
required this.items,
|
||||||
|
required this.title,
|
||||||
|
required this.displayText,
|
||||||
|
required this.onItemSelected,
|
||||||
|
required this.onAddCustom,
|
||||||
|
this.searchHint,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SearchableSelection<T>> createState() => _SearchableSelectionState<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SearchableSelectionState<T> extends State<SearchableSelection<T>> {
|
||||||
|
final TextEditingController _searchController = TextEditingController();
|
||||||
|
List<T> _filteredItems = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_filteredItems = widget.items;
|
||||||
|
_searchController.addListener(_filterItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_searchController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _filterItems() {
|
||||||
|
final query = _searchController.text.toLowerCase();
|
||||||
|
setState(() {
|
||||||
|
if (query.isEmpty) {
|
||||||
|
_filteredItems = widget.items;
|
||||||
|
} else {
|
||||||
|
_filteredItems = widget.items.where((item) {
|
||||||
|
final displayText = widget.displayText(item).toLowerCase();
|
||||||
|
return displayText.contains(query);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildItemTile(T item) {
|
||||||
|
if (item is Bean) {
|
||||||
|
return _buildBeanTile(item as Bean);
|
||||||
|
} else if (item is Machine) {
|
||||||
|
return _buildMachineTile(item as Machine);
|
||||||
|
} else if (item is Recipe) {
|
||||||
|
return _buildRecipeTile(item as Recipe);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListTile(
|
||||||
|
title: Text(widget.displayText(item)),
|
||||||
|
onTap: () => widget.onItemSelected(item),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildBeanTile(Bean bean) {
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
||||||
|
child: ListTile(
|
||||||
|
leading: CircleAvatar(
|
||||||
|
backgroundColor: _getRoastColor(bean.roastLevel),
|
||||||
|
child: Text(
|
||||||
|
bean.roastLevel.name[0].toUpperCase(),
|
||||||
|
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
bean.name,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
subtitle: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('${bean.varietal} • ${bean.processingMethod}'),
|
||||||
|
Text(
|
||||||
|
'Origin: ${bean.originCountry?.continent ?? 'Unknown'}',
|
||||||
|
style: TextStyle(color: Colors.grey[600]),
|
||||||
|
),
|
||||||
|
if (bean.tastingNotes.isNotEmpty)
|
||||||
|
Wrap(
|
||||||
|
spacing: 4,
|
||||||
|
children: bean.tastingNotes.take(3).map((note) => Chip(
|
||||||
|
label: Text(
|
||||||
|
_formatTastingNote(note),
|
||||||
|
style: const TextStyle(fontSize: 10),
|
||||||
|
),
|
||||||
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
)).toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
trailing: bean.preferred ? const Icon(Icons.star, color: Colors.amber) : null,
|
||||||
|
onTap: () => widget.onItemSelected(bean as T),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMachineTile(Machine machine) {
|
||||||
|
IconData icon;
|
||||||
|
switch (machine.type) {
|
||||||
|
case MachineType.espresso:
|
||||||
|
icon = Icons.coffee_maker;
|
||||||
|
break;
|
||||||
|
case MachineType.frenchPress:
|
||||||
|
icon = Icons.coffee;
|
||||||
|
break;
|
||||||
|
case MachineType.grinder:
|
||||||
|
icon = Icons.settings;
|
||||||
|
break;
|
||||||
|
case MachineType.drip:
|
||||||
|
icon = Icons.water_drop;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
icon = Icons.coffee_maker;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
||||||
|
child: ListTile(
|
||||||
|
leading: CircleAvatar(
|
||||||
|
backgroundColor: Theme.of(context).primaryColor,
|
||||||
|
child: Icon(icon, color: Colors.white),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
'${machine.manufacturer} ${machine.model}',
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
subtitle: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('Type: ${_formatMachineType(machine.type)}'),
|
||||||
|
Text('Year: ${machine.year}'),
|
||||||
|
if (machine.details.isNotEmpty)
|
||||||
|
Text(
|
||||||
|
machine.details,
|
||||||
|
style: TextStyle(color: Colors.grey[600]),
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () => widget.onItemSelected(machine as T),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildRecipeTile(Recipe recipe) {
|
||||||
|
IconData icon;
|
||||||
|
switch (recipe.brewMethod) {
|
||||||
|
case BrewMethod.espresso:
|
||||||
|
icon = Icons.coffee_maker;
|
||||||
|
break;
|
||||||
|
case BrewMethod.pourOver:
|
||||||
|
icon = Icons.water_drop;
|
||||||
|
break;
|
||||||
|
case BrewMethod.frenchPress:
|
||||||
|
icon = Icons.coffee;
|
||||||
|
break;
|
||||||
|
case BrewMethod.drip:
|
||||||
|
icon = Icons.opacity;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
||||||
|
child: ListTile(
|
||||||
|
leading: CircleAvatar(
|
||||||
|
backgroundColor: _getServingTempColor(recipe.servingTemp),
|
||||||
|
child: Icon(icon, color: Colors.white),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
recipe.name,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
subtitle: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('Method: ${_formatBrewMethod(recipe.brewMethod)}'),
|
||||||
|
Text('Ratio: ${recipe.coffeeAmount}g coffee : ${recipe.waterAmount}ml water'),
|
||||||
|
Text('Brew time: ${_formatBrewTime(recipe.brewTime)}'),
|
||||||
|
if (recipe.milkType != null)
|
||||||
|
Text('Milk: ${_formatMilkType(recipe.milkType!)}'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () => widget.onItemSelected(recipe as T),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color _getRoastColor(RoastLevel level) {
|
||||||
|
switch (level) {
|
||||||
|
case RoastLevel.light:
|
||||||
|
return Colors.brown[300]!;
|
||||||
|
case RoastLevel.medium:
|
||||||
|
return Colors.brown[600]!;
|
||||||
|
case RoastLevel.mediumLight:
|
||||||
|
return Colors.brown[400]!;
|
||||||
|
case RoastLevel.mediumDark:
|
||||||
|
return Colors.brown[700]!;
|
||||||
|
case RoastLevel.dark:
|
||||||
|
return Colors.brown[900]!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Color _getServingTempColor(ServingTemp temp) {
|
||||||
|
switch (temp) {
|
||||||
|
case ServingTemp.hot:
|
||||||
|
return Colors.red[400]!;
|
||||||
|
case ServingTemp.cold:
|
||||||
|
return Colors.blue[400]!;
|
||||||
|
case ServingTemp.iced:
|
||||||
|
return Colors.lightBlue[300]!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatTastingNote(TastingNotes note) {
|
||||||
|
switch (note) {
|
||||||
|
case TastingNotes.stoneFruit:
|
||||||
|
return 'Stone Fruit';
|
||||||
|
case TastingNotes.driedFruit:
|
||||||
|
return 'Dried Fruit';
|
||||||
|
case TastingNotes.tropical:
|
||||||
|
return 'Tropical';
|
||||||
|
case TastingNotes.brownSugar:
|
||||||
|
return 'Brown Sugar';
|
||||||
|
default:
|
||||||
|
return note.name[0].toUpperCase() + note.name.substring(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatMachineType(MachineType type) {
|
||||||
|
switch (type) {
|
||||||
|
case MachineType.frenchPress:
|
||||||
|
return 'French Press';
|
||||||
|
case MachineType.coldBrew:
|
||||||
|
return 'Cold Brew';
|
||||||
|
case MachineType.espressoPod:
|
||||||
|
return 'Espresso Pod';
|
||||||
|
default:
|
||||||
|
return type.name[0].toUpperCase() + type.name.substring(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatBrewMethod(BrewMethod method) {
|
||||||
|
switch (method) {
|
||||||
|
case BrewMethod.frenchPress:
|
||||||
|
return 'French Press';
|
||||||
|
case BrewMethod.pourOver:
|
||||||
|
return 'Pour Over';
|
||||||
|
default:
|
||||||
|
return method.name[0].toUpperCase() + method.name.substring(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatMilkType(MilkType type) {
|
||||||
|
return type.name[0].toUpperCase() + type.name.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatBrewTime(int seconds) {
|
||||||
|
final minutes = seconds ~/ 60;
|
||||||
|
final remainingSeconds = seconds % 60;
|
||||||
|
if (minutes > 0) {
|
||||||
|
return '${minutes}m ${remainingSeconds}s';
|
||||||
|
}
|
||||||
|
return '${seconds}s';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(widget.title),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
onPressed: widget.onAddCustom,
|
||||||
|
tooltip: 'Add Custom',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: TextField(
|
||||||
|
controller: _searchController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: widget.searchHint ?? 'Search ${widget.title.toLowerCase()}...',
|
||||||
|
prefixIcon: const Icon(Icons.search),
|
||||||
|
suffixIcon: _searchController.text.isNotEmpty
|
||||||
|
? IconButton(
|
||||||
|
icon: const Icon(Icons.clear),
|
||||||
|
onPressed: () {
|
||||||
|
_searchController.clear();
|
||||||
|
_filterItems();
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_filteredItems.isEmpty)
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.search_off,
|
||||||
|
size: 64,
|
||||||
|
color: Colors.grey[400],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'No items found',
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||||
|
color: Colors.grey[600],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'Try adjusting your search or add a custom item',
|
||||||
|
style: TextStyle(color: Colors.grey[500]),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: widget.onAddCustom,
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: const Text('Add Custom'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: _filteredItems.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return _buildItemTile(_filteredItems[index]);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
51
lib/database/Achievements.csv
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
id,title,description,category,points,icon,difficulty,requirements,isHidden,prerequisiteAchievements,rewardType,rewardDescription
|
||||||
|
ea0251d9-79e8-4b16-89b3-0998bee9b965,Machine Whisperer,Achieved by completing a major coffee milestone.,Brewing,76,icon_trophy,Medium,"{'type': 'beans_collected', 'target': 38, 'timeframe': 'none'}",True,[],Badge,Unlocked a special badge.
|
||||||
|
8cd70029-2b4e-48dd-ab36-e807826014fa,Machine Whisperer,Achieved by completing a major coffee milestone.,Equipment,56,icon_trophy,Expert,"{'type': 'machines_logged', 'target': 11, 'timeframe': '30 days'}",False,[],Feature,Unlocked a special badge.
|
||||||
|
37cae5b6-4590-41c2-9ffc-748df4f9c2e8,Bean Collector,Achieved by completing a major coffee milestone.,Learning,25,icon_trophy,Hard,"{'type': 'machines_logged', 'target': 30, 'timeframe': 'none'}",True,[],Badge,Unlocked a special badge.
|
||||||
|
a560014d-4e2f-42aa-b0e4-38a3a7187c32,Bean Collector,Achieved by completing a major coffee milestone.,Beans,20,icon_trophy,Easy,"{'type': 'machines_logged', 'target': 3, 'timeframe': 'none'}",False,[],Badge,Unlocked a special badge.
|
||||||
|
8d482e29-1620-44ce-9d69-cdf19ea21880,Brew Master,Achieved by completing a major coffee milestone.,Beans,65,icon_trophy,Easy,"{'type': 'machines_logged', 'target': 2, 'timeframe': 'none'}",False,[],Title,Unlocked a special badge.
|
||||||
|
c175c0a3-8e28-4834-9afe-4cfaf53849fd,Machine Whisperer,Achieved by completing a major coffee milestone.,Beans,50,icon_trophy,Easy,"{'type': 'beans_collected', 'target': 30, 'timeframe': '7 days'}",True,[],Feature,Unlocked a special badge.
|
||||||
|
8f60bd29-d5ba-4816-9044-a08855d1dc09,Bean Collector,Achieved by completing a major coffee milestone.,Equipment,67,icon_trophy,Easy,"{'type': 'brew_count', 'target': 43, 'timeframe': '7 days'}",False,[],Feature,Unlocked a special badge.
|
||||||
|
dc428fe6-1184-4af2-bf70-46341ce00e37,Tasting Tour,Achieved by completing a major coffee milestone.,Equipment,14,icon_trophy,Hard,"{'type': 'brew_count', 'target': 27, 'timeframe': '30 days'}",True,[],Title,Unlocked a special badge.
|
||||||
|
94797a1e-4a4e-495d-84db-8d0d941df58c,Brew Master,Achieved by completing a major coffee milestone.,Learning,24,icon_trophy,Medium,"{'type': 'brew_count', 'target': 37, 'timeframe': '30 days'}",False,[],Badge,Unlocked a special badge.
|
||||||
|
71a8eed0-ec5c-4c9c-b319-2f1b0a03aa5d,Tasting Tour,Achieved by completing a major coffee milestone.,Learning,95,icon_trophy,Expert,"{'type': 'brew_count', 'target': 14, 'timeframe': 'none'}",True,[],Points,Unlocked a special badge.
|
||||||
|
5fa58920-3656-46cc-bbb4-3ba56e137290,Machine Whisperer,Achieved by completing a major coffee milestone.,Social,91,icon_trophy,Easy,"{'type': 'brew_count', 'target': 35, 'timeframe': 'none'}",True,[],Points,Unlocked a special badge.
|
||||||
|
d05df8c0-bd48-4ab7-a1d0-bc6377d9ed85,Tasting Tour,Achieved by completing a major coffee milestone.,Beans,72,icon_trophy,Expert,"{'type': 'beans_collected', 'target': 20, 'timeframe': 'none'}",True,[],Points,Unlocked a special badge.
|
||||||
|
feca2cc0-a962-431a-b674-f36138368b7b,Brew Master,Achieved by completing a major coffee milestone.,Learning,70,icon_trophy,Easy,"{'type': 'machines_logged', 'target': 35, 'timeframe': '7 days'}",True,[],Points,Unlocked a special badge.
|
||||||
|
7ffadac0-b894-4b3c-8701-10caf9f49dc8,Tasting Tour,Achieved by completing a major coffee milestone.,Brewing,84,icon_trophy,Medium,"{'type': 'beans_collected', 'target': 24, 'timeframe': '7 days'}",False,[],Title,Unlocked a special badge.
|
||||||
|
4c1469ec-865a-49a8-9738-f1f11c43d40e,Machine Whisperer,Achieved by completing a major coffee milestone.,Equipment,63,icon_trophy,Medium,"{'type': 'beans_collected', 'target': 3, 'timeframe': '30 days'}",False,[],Points,Unlocked a special badge.
|
||||||
|
0d9a4e53-8298-4303-9306-c35fa9f926eb,Machine Whisperer,Achieved by completing a major coffee milestone.,Equipment,62,icon_trophy,Easy,"{'type': 'beans_collected', 'target': 18, 'timeframe': '7 days'}",False,[],Feature,Unlocked a special badge.
|
||||||
|
e50225ac-946c-4a67-99f9-1f5195c6b761,Brew Master,Achieved by completing a major coffee milestone.,Equipment,14,icon_trophy,Easy,"{'type': 'beans_collected', 'target': 37, 'timeframe': '30 days'}",False,[],Title,Unlocked a special badge.
|
||||||
|
aff62459-e4ce-4056-8908-f20bfe6ff9a7,Brew Master,Achieved by completing a major coffee milestone.,Learning,23,icon_trophy,Medium,"{'type': 'brew_count', 'target': 18, 'timeframe': '30 days'}",True,[],Feature,Unlocked a special badge.
|
||||||
|
1398bbba-8828-422b-a6f2-0212fc15d9fc,Machine Whisperer,Achieved by completing a major coffee milestone.,Learning,94,icon_trophy,Medium,"{'type': 'brew_count', 'target': 10, 'timeframe': '30 days'}",True,[],Title,Unlocked a special badge.
|
||||||
|
9cb9e34c-96e4-4cd5-a0cf-60799e704e41,Tasting Tour,Achieved by completing a major coffee milestone.,Equipment,87,icon_trophy,Medium,"{'type': 'beans_collected', 'target': 47, 'timeframe': '30 days'}",False,[],Feature,Unlocked a special badge.
|
||||||
|
cca06c32-014a-43fa-9b46-92d96b066084,Bean Collector,Achieved by completing a major coffee milestone.,Brewing,83,icon_trophy,Easy,"{'type': 'brew_count', 'target': 32, 'timeframe': '7 days'}",False,[],Title,Unlocked a special badge.
|
||||||
|
af02d729-c741-4592-83d9-8934caf52e66,Bean Collector,Achieved by completing a major coffee milestone.,Learning,71,icon_trophy,Medium,"{'type': 'brew_count', 'target': 39, 'timeframe': 'none'}",False,[],Feature,Unlocked a special badge.
|
||||||
|
f6f9d0a9-c8a7-42a1-8934-3679831049f7,Tasting Tour,Achieved by completing a major coffee milestone.,Brewing,36,icon_trophy,Expert,"{'type': 'brew_count', 'target': 47, 'timeframe': '30 days'}",False,[],Badge,Unlocked a special badge.
|
||||||
|
ac21b06c-3bad-4f69-a9c5-0db77f87e5c4,Machine Whisperer,Achieved by completing a major coffee milestone.,Brewing,94,icon_trophy,Easy,"{'type': 'machines_logged', 'target': 12, 'timeframe': '7 days'}",False,[],Points,Unlocked a special badge.
|
||||||
|
b0c7be6e-ebd1-4d21-b4c0-8c46f81b610d,Brew Master,Achieved by completing a major coffee milestone.,Brewing,99,icon_trophy,Expert,"{'type': 'beans_collected', 'target': 47, 'timeframe': '7 days'}",False,[],Badge,Unlocked a special badge.
|
||||||
|
44dd23c6-1069-48b0-98b3-fe24eaf04bda,Brew Master,Achieved by completing a major coffee milestone.,Beans,14,icon_trophy,Easy,"{'type': 'brew_count', 'target': 10, 'timeframe': 'none'}",False,[],Feature,Unlocked a special badge.
|
||||||
|
b15593a6-6f53-48ee-82a8-f092a0c4c626,Machine Whisperer,Achieved by completing a major coffee milestone.,Brewing,34,icon_trophy,Hard,"{'type': 'beans_collected', 'target': 9, 'timeframe': 'none'}",True,[],Badge,Unlocked a special badge.
|
||||||
|
b811c457-f177-4a27-9f04-211fc86892a5,Bean Collector,Achieved by completing a major coffee milestone.,Beans,84,icon_trophy,Easy,"{'type': 'brew_count', 'target': 2, 'timeframe': '30 days'}",True,[],Feature,Unlocked a special badge.
|
||||||
|
e46d1512-7d2a-4a98-8e74-23767fcbe7cd,Brew Master,Achieved by completing a major coffee milestone.,Equipment,41,icon_trophy,Easy,"{'type': 'machines_logged', 'target': 12, 'timeframe': 'none'}",True,[],Feature,Unlocked a special badge.
|
||||||
|
ee2505ba-b99b-4b7c-a589-34aaa62f7d2a,Bean Collector,Achieved by completing a major coffee milestone.,Brewing,30,icon_trophy,Medium,"{'type': 'machines_logged', 'target': 3, 'timeframe': '7 days'}",False,[],Feature,Unlocked a special badge.
|
||||||
|
aaa772b4-ded5-47a1-8bae-98fe3d551f4a,Bean Collector,Achieved by completing a major coffee milestone.,Brewing,12,icon_trophy,Easy,"{'type': 'machines_logged', 'target': 47, 'timeframe': '30 days'}",False,[],Points,Unlocked a special badge.
|
||||||
|
db46aa9e-0fe9-409d-b4f7-7c0a1c09f85c,Bean Collector,Achieved by completing a major coffee milestone.,Beans,37,icon_trophy,Expert,"{'type': 'beans_collected', 'target': 16, 'timeframe': 'none'}",False,[],Title,Unlocked a special badge.
|
||||||
|
ba291f5f-df74-4def-a964-be7a54bd3898,Tasting Tour,Achieved by completing a major coffee milestone.,Beans,66,icon_trophy,Expert,"{'type': 'beans_collected', 'target': 35, 'timeframe': '30 days'}",False,[],Title,Unlocked a special badge.
|
||||||
|
266ef458-d1ff-40b2-b7ea-103324b5a57c,Tasting Tour,Achieved by completing a major coffee milestone.,Social,88,icon_trophy,Hard,"{'type': 'machines_logged', 'target': 46, 'timeframe': '30 days'}",False,[],Feature,Unlocked a special badge.
|
||||||
|
7f8b10b8-f098-4af6-a23c-0570e467e611,Machine Whisperer,Achieved by completing a major coffee milestone.,Social,14,icon_trophy,Hard,"{'type': 'machines_logged', 'target': 17, 'timeframe': '7 days'}",False,[],Badge,Unlocked a special badge.
|
||||||
|
564c988d-cbae-4e56-a068-eedebe2705b7,Tasting Tour,Achieved by completing a major coffee milestone.,Beans,71,icon_trophy,Hard,"{'type': 'beans_collected', 'target': 40, 'timeframe': 'none'}",False,[],Title,Unlocked a special badge.
|
||||||
|
94072fe2-7a00-424b-b4c7-0689bf06ac52,Bean Collector,Achieved by completing a major coffee milestone.,Equipment,55,icon_trophy,Easy,"{'type': 'machines_logged', 'target': 12, 'timeframe': '7 days'}",False,[],Badge,Unlocked a special badge.
|
||||||
|
3959652e-d1b3-4a78-be82-c886f1d07da4,Bean Collector,Achieved by completing a major coffee milestone.,Beans,44,icon_trophy,Expert,"{'type': 'machines_logged', 'target': 10, 'timeframe': '30 days'}",True,[],Points,Unlocked a special badge.
|
||||||
|
e2693d39-23d8-4fb8-a413-d638b5bb7aac,Tasting Tour,Achieved by completing a major coffee milestone.,Brewing,14,icon_trophy,Expert,"{'type': 'machines_logged', 'target': 23, 'timeframe': '7 days'}",True,[],Feature,Unlocked a special badge.
|
||||||
|
d5e3edd4-d25a-4b77-9f1a-a029e126d783,Machine Whisperer,Achieved by completing a major coffee milestone.,Social,62,icon_trophy,Medium,"{'type': 'beans_collected', 'target': 14, 'timeframe': 'none'}",False,[],Title,Unlocked a special badge.
|
||||||
|
735322e2-b89d-403c-a7b6-afb4d211062f,Machine Whisperer,Achieved by completing a major coffee milestone.,Brewing,84,icon_trophy,Hard,"{'type': 'brew_count', 'target': 17, 'timeframe': 'none'}",False,[],Points,Unlocked a special badge.
|
||||||
|
5a07dad5-bc56-4bd0-aa08-b24bb7f4dc1b,Tasting Tour,Achieved by completing a major coffee milestone.,Brewing,25,icon_trophy,Expert,"{'type': 'beans_collected', 'target': 18, 'timeframe': '30 days'}",False,[],Title,Unlocked a special badge.
|
||||||
|
c07551b0-5a69-4f45-b047-0d5b8ca3898b,Bean Collector,Achieved by completing a major coffee milestone.,Social,72,icon_trophy,Expert,"{'type': 'brew_count', 'target': 3, 'timeframe': 'none'}",False,[],Title,Unlocked a special badge.
|
||||||
|
48fd73c6-2964-44e8-b1b2-2dea75dccf54,Bean Collector,Achieved by completing a major coffee milestone.,Brewing,62,icon_trophy,Hard,"{'type': 'machines_logged', 'target': 31, 'timeframe': 'none'}",False,[],Badge,Unlocked a special badge.
|
||||||
|
c0b82740-0101-419a-90a9-d921a8c2ddb6,Machine Whisperer,Achieved by completing a major coffee milestone.,Social,12,icon_trophy,Easy,"{'type': 'machines_logged', 'target': 29, 'timeframe': '7 days'}",False,[],Title,Unlocked a special badge.
|
||||||
|
8032ba5c-6ff9-4247-bef0-9f04ea34a4d7,Machine Whisperer,Achieved by completing a major coffee milestone.,Brewing,20,icon_trophy,Medium,"{'type': 'machines_logged', 'target': 38, 'timeframe': 'none'}",True,[],Title,Unlocked a special badge.
|
||||||
|
e6607eeb-9b53-42f7-ad5b-7625b7a9e681,Brew Master,Achieved by completing a major coffee milestone.,Learning,54,icon_trophy,Medium,"{'type': 'brew_count', 'target': 1, 'timeframe': 'none'}",False,[],Points,Unlocked a special badge.
|
||||||
|
fc813453-c281-441e-8ba0-8714212a16e6,Tasting Tour,Achieved by completing a major coffee milestone.,Brewing,59,icon_trophy,Easy,"{'type': 'brew_count', 'target': 36, 'timeframe': 'none'}",True,[],Feature,Unlocked a special badge.
|
||||||
|
e24cadd0-eac8-4a34-98de-e6d72b3a0579,Brew Master,Achieved by completing a major coffee milestone.,Learning,50,icon_trophy,Hard,"{'type': 'brew_count', 'target': 9, 'timeframe': 'none'}",False,[],Feature,Unlocked a special badge.
|
||||||
|
b0752bb5-5d7d-40f6-b31c-aaf1a338c8d2,Brew Master,Achieved by completing a major coffee milestone.,Learning,39,icon_trophy,Hard,"{'type': 'brew_count', 'target': 22, 'timeframe': '7 days'}",False,[],Points,Unlocked a special badge.
|
||||||
|
51
lib/database/Bean_Varietals.csv
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
id,name,description,commonOrigins,flavorProfile,characteristics,genetics,altitude,yield,diseaseResistance,cupScore,processingMethods
|
||||||
|
4cb8c887-67a0-4fcd-a0a2-56e42b1b44b2,Caturra,Classic varietal with distinct flavor profile.,"['Colombia', 'Yemen', 'Jamaica']","['Nutty', 'Floral', 'Spicy']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,83.8,"['Semi-washed', 'Wet-hulled', 'Honey']"
|
||||||
|
defaec07-2383-42b2-9cb7-b7d1534adc36,Maragogipe,Classic varietal with distinct flavor profile.,"['Ethiopia', 'Costa Rica', 'Panama']","['Berry', 'Nutty', 'Chocolate']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,89.0,"['Natural', 'Honey', 'Semi-washed']"
|
||||||
|
cb9ffb87-251f-42e1-a64e-eb8fbb1883a5,Maragogipe,Classic varietal with distinct flavor profile.,"['Costa Rica', 'Yemen', 'Jamaica']","['Caramel', 'Spicy', 'Floral']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,84.2,"['Wet-hulled', 'Natural', 'Honey']"
|
||||||
|
dc90c31d-5184-4f81-9fa9-e53a219366f1,Caturra,Classic varietal with distinct flavor profile.,"['Colombia', 'Panama', 'Kenya']","['Caramel', 'Citrus', 'Spicy']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,94.9,"['Natural', 'Wet-hulled', 'Washed']"
|
||||||
|
1db22ac4-4abe-4608-ba28-91de51598a8b,Pacamara,Classic varietal with distinct flavor profile.,"['Guatemala', 'Brazil', 'Costa Rica']","['Caramel', 'Fruity', 'Spicy']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,93.1,"['Natural', 'Semi-washed', 'Washed']"
|
||||||
|
b5098c67-16b0-4e25-8569-98b2df912df4,Bourbon,Classic varietal with distinct flavor profile.,"['Honduras', 'Brazil', 'Ethiopia']","['Nutty', 'Spicy', 'Caramel']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,90.3,"['Wet-hulled', 'Washed', 'Natural']"
|
||||||
|
25864ddb-5277-4be3-9c1b-e84f8c42697a,Maragogipe,Classic varietal with distinct flavor profile.,"['Panama', 'Costa Rica', 'Jamaica']","['Caramel', 'Berry', 'Floral']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,90.1,"['Semi-washed', 'Natural', 'Wet-hulled']"
|
||||||
|
1bf9739d-ac22-40ac-844f-fe7e7db39e76,Kent,Classic varietal with distinct flavor profile.,"['Costa Rica', 'Brazil', 'Colombia']","['Spicy', 'Berry', 'Caramel']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,89.9,"['Wet-hulled', 'Honey', 'Washed']"
|
||||||
|
1e1811a3-b567-47c0-b8ce-a720010cd280,SL34,Classic varietal with distinct flavor profile.,"['Yemen', 'Guatemala', 'Colombia']","['Floral', 'Chocolate', 'Fruity']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,91.7,"['Wet-hulled', 'Semi-washed', 'Natural']"
|
||||||
|
35aa7bc2-2ebc-4136-b028-90c6e6eccbfd,Geisha,Classic varietal with distinct flavor profile.,"['Yemen', 'Kenya', 'Colombia']","['Chocolate', 'Spicy', 'Citrus']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,89.4,"['Honey', 'Natural', 'Washed']"
|
||||||
|
176ed0db-b8b0-4572-a5d3-f770170f78b1,Geisha,Classic varietal with distinct flavor profile.,"['Kenya', 'Jamaica', 'Honduras']","['Nutty', 'Spicy', 'Berry']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,86.5,"['Semi-washed', 'Washed', 'Honey']"
|
||||||
|
911a40dd-7f2f-4730-8089-b737c8a8f2de,SL34,Classic varietal with distinct flavor profile.,"['Guatemala', 'Ethiopia', 'Panama']","['Citrus', 'Floral', 'Fruity']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,81.6,"['Wet-hulled', 'Semi-washed', 'Honey']"
|
||||||
|
4912d9f9-cb2a-41ce-a4dc-879f347b9bb5,Maragogipe,Classic varietal with distinct flavor profile.,"['Brazil', 'Guatemala', 'Panama']","['Citrus', 'Spicy', 'Chocolate']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,94.0,"['Wet-hulled', 'Semi-washed', 'Natural']"
|
||||||
|
f72494df-f47e-4a47-86dd-2faf385c9200,Caturra,Classic varietal with distinct flavor profile.,"['Colombia', 'Costa Rica', 'Kenya']","['Fruity', 'Caramel', 'Citrus']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,88.3,"['Semi-washed', 'Honey', 'Washed']"
|
||||||
|
11a7e600-7861-49ea-9536-fa67c689f63a,Bourbon,Classic varietal with distinct flavor profile.,"['Brazil', 'Yemen', 'Honduras']","['Spicy', 'Caramel', 'Chocolate']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,86.9,"['Honey', 'Natural', 'Washed']"
|
||||||
|
c90b8225-53d1-4280-bba9-0c4133b4b975,Caturra,Classic varietal with distinct flavor profile.,"['Guatemala', 'Ethiopia', 'Jamaica']","['Citrus', 'Fruity', 'Nutty']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,94.2,"['Natural', 'Honey', 'Washed']"
|
||||||
|
5d6feb96-4633-43b9-b44f-6579418c5994,Maragogipe,Classic varietal with distinct flavor profile.,"['Kenya', 'Colombia', 'Panama']","['Floral', 'Berry', 'Caramel']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,93.8,"['Semi-washed', 'Honey', 'Washed']"
|
||||||
|
32cfc671-0ec5-49ac-b507-894776fd3f11,Pacamara,Classic varietal with distinct flavor profile.,"['Colombia', 'Panama', 'Costa Rica']","['Fruity', 'Floral', 'Spicy']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,88.7,"['Honey', 'Wet-hulled', 'Semi-washed']"
|
||||||
|
a692d28b-de2f-4a6e-b056-b34ffa8abedb,SL28,Classic varietal with distinct flavor profile.,"['Kenya', 'Panama', 'Yemen']","['Berry', 'Spicy', 'Nutty']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,89.6,"['Natural', 'Washed', 'Honey']"
|
||||||
|
d3858040-88a3-48dc-a5f9-a372619a1ef1,Pacamara,Classic varietal with distinct flavor profile.,"['Costa Rica', 'Kenya', 'Colombia']","['Floral', 'Nutty', 'Citrus']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,91.9,"['Semi-washed', 'Honey', 'Washed']"
|
||||||
|
8998ecd3-426e-4875-bd97-44cf0d16126c,Kent,Classic varietal with distinct flavor profile.,"['Colombia', 'Jamaica', 'Guatemala']","['Floral', 'Chocolate', 'Citrus']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,87.9,"['Washed', 'Natural', 'Semi-washed']"
|
||||||
|
c5986848-f4f5-4623-bcf0-1c8089cb59df,Catuai,Classic varietal with distinct flavor profile.,"['Brazil', 'Colombia', 'Yemen']","['Citrus', 'Fruity', 'Caramel']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,81.5,"['Wet-hulled', 'Semi-washed', 'Natural']"
|
||||||
|
b5fc0ec8-cb76-4a56-ae5d-a751374d7be9,Typica,Classic varietal with distinct flavor profile.,"['Panama', 'Ethiopia', 'Honduras']","['Nutty', 'Spicy', 'Chocolate']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,90.8,"['Washed', 'Wet-hulled', 'Natural']"
|
||||||
|
71d5b237-37b7-412a-86f9-8cf2120cc6c0,Typica,Classic varietal with distinct flavor profile.,"['Jamaica', 'Yemen', 'Ethiopia']","['Fruity', 'Caramel', 'Nutty']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,89.6,"['Washed', 'Honey', 'Natural']"
|
||||||
|
bf45f0d8-3ca7-41a7-a3b9-85cb33d827f0,Kent,Classic varietal with distinct flavor profile.,"['Honduras', 'Costa Rica', 'Brazil']","['Spicy', 'Floral', 'Fruity']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,83.3,"['Wet-hulled', 'Natural', 'Semi-washed']"
|
||||||
|
1f73a289-b3a0-4098-af8f-f34cb9af4149,Bourbon,Classic varietal with distinct flavor profile.,"['Panama', 'Colombia', 'Jamaica']","['Nutty', 'Floral', 'Fruity']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,83.1,"['Natural', 'Semi-washed', 'Wet-hulled']"
|
||||||
|
67274ddf-a2d8-4105-85b3-ea51e2c6fe5c,SL28,Classic varietal with distinct flavor profile.,"['Costa Rica', 'Brazil', 'Honduras']","['Spicy', 'Nutty', 'Fruity']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,81.5,"['Washed', 'Wet-hulled', 'Honey']"
|
||||||
|
cde7eef0-1cce-418f-a2b3-403e856573de,Catuai,Classic varietal with distinct flavor profile.,"['Jamaica', 'Yemen', 'Guatemala']","['Caramel', 'Fruity', 'Berry']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,94.8,"['Wet-hulled', 'Natural', 'Semi-washed']"
|
||||||
|
734335e8-9379-4b47-9490-5b7500c1bc15,Bourbon,Classic varietal with distinct flavor profile.,"['Brazil', 'Colombia', 'Panama']","['Spicy', 'Caramel', 'Berry']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,80.3,"['Washed', 'Honey', 'Natural']"
|
||||||
|
cadd51b2-8f05-4f8e-a8d9-0855a65a17cf,Kent,Classic varietal with distinct flavor profile.,"['Guatemala', 'Jamaica', 'Honduras']","['Berry', 'Chocolate', 'Nutty']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,86.7,"['Washed', 'Semi-washed', 'Natural']"
|
||||||
|
42ff6b25-902c-471d-a3f7-89475d6b8de4,SL28,Classic varietal with distinct flavor profile.,"['Brazil', 'Guatemala', 'Jamaica']","['Spicy', 'Floral', 'Nutty']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,86.5,"['Wet-hulled', 'Honey', 'Washed']"
|
||||||
|
acbf9271-9c75-457c-b69d-6e1eee47aec9,Kent,Classic varietal with distinct flavor profile.,"['Kenya', 'Guatemala', 'Yemen']","['Caramel', 'Citrus', 'Fruity']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,89.0,"['Wet-hulled', 'Washed', 'Honey']"
|
||||||
|
5669dad0-9a6f-469e-b00e-216cec82b9c9,Maragogipe,Classic varietal with distinct flavor profile.,"['Colombia', 'Ethiopia', 'Brazil']","['Floral', 'Fruity', 'Nutty']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,83.7,"['Semi-washed', 'Wet-hulled', 'Honey']"
|
||||||
|
fd9c9ed4-9c90-4606-98ec-7a12b1e6e546,Caturra,Classic varietal with distinct flavor profile.,"['Guatemala', 'Kenya', 'Honduras']","['Chocolate', 'Nutty', 'Fruity']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,82.4,"['Wet-hulled', 'Honey', 'Semi-washed']"
|
||||||
|
ee28a942-a5d2-4234-a448-e19832b20e41,Caturra,Classic varietal with distinct flavor profile.,"['Colombia', 'Jamaica', 'Panama']","['Floral', 'Spicy', 'Berry']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,90.6,"['Honey', 'Natural', 'Washed']"
|
||||||
|
0aa366aa-4e45-443e-99b9-dfd53b8a8c28,Pacamara,Classic varietal with distinct flavor profile.,"['Kenya', 'Yemen', 'Brazil']","['Nutty', 'Citrus', 'Fruity']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,91.1,"['Honey', 'Natural', 'Wet-hulled']"
|
||||||
|
aea4bf64-96f9-4f48-8c00-0fa52eb259c9,Bourbon,Classic varietal with distinct flavor profile.,"['Costa Rica', 'Kenya', 'Yemen']","['Floral', 'Spicy', 'Nutty']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,88.1,"['Washed', 'Semi-washed', 'Natural']"
|
||||||
|
039a57ff-5e78-4942-a29d-297da50acfa9,Typica,Classic varietal with distinct flavor profile.,"['Kenya', 'Panama', 'Yemen']","['Chocolate', 'Spicy', 'Citrus']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,87.6,"['Semi-washed', 'Wet-hulled', 'Honey']"
|
||||||
|
988705f9-8b17-459a-ba75-10bedaa5176c,SL34,Classic varietal with distinct flavor profile.,"['Kenya', 'Brazil', 'Ethiopia']","['Fruity', 'Citrus', 'Spicy']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,88.2,"['Wet-hulled', 'Honey', 'Washed']"
|
||||||
|
d0b12210-ee55-4805-a2af-1826e9c00c50,SL28,Classic varietal with distinct flavor profile.,"['Colombia', 'Ethiopia', 'Costa Rica']","['Chocolate', 'Berry', 'Fruity']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,91.4,"['Natural', 'Honey', 'Semi-washed']"
|
||||||
|
846ec766-cbbf-449c-bfc9-275bed60d45d,SL34,Classic varietal with distinct flavor profile.,"['Jamaica', 'Brazil', 'Panama']","['Fruity', 'Caramel', 'Citrus']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,92.8,"['Semi-washed', 'Natural', 'Washed']"
|
||||||
|
45b7966b-5c8e-4ff7-a4b7-206cc42388fa,Geisha,Classic varietal with distinct flavor profile.,"['Kenya', 'Colombia', 'Guatemala']","['Berry', 'Fruity', 'Caramel']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,87.1,"['Semi-washed', 'Honey', 'Wet-hulled']"
|
||||||
|
a6f896d8-c7e5-40d2-876d-9b711488ced2,Bourbon,Classic varietal with distinct flavor profile.,"['Honduras', 'Ethiopia', 'Guatemala']","['Fruity', 'Caramel', 'Citrus']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,89.4,"['Semi-washed', 'Honey', 'Wet-hulled']"
|
||||||
|
1ec6883d-857b-4a7b-8175-cff999023659,SL28,Classic varietal with distinct flavor profile.,"['Honduras', 'Costa Rica', 'Ethiopia']","['Floral', 'Chocolate', 'Citrus']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,83.9,"['Wet-hulled', 'Natural', 'Semi-washed']"
|
||||||
|
a7cff0fc-c150-4860-ba2a-7afcc72cad2d,Typica,Classic varietal with distinct flavor profile.,"['Honduras', 'Guatemala', 'Colombia']","['Fruity', 'Citrus', 'Nutty']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,83.0,"['Honey', 'Wet-hulled', 'Semi-washed']"
|
||||||
|
72cc1c60-4137-41e0-8cf2-79dbb2102016,Pacamara,Classic varietal with distinct flavor profile.,"['Yemen', 'Honduras', 'Panama']","['Berry', 'Chocolate', 'Spicy']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,85.5,"['Semi-washed', 'Natural', 'Honey']"
|
||||||
|
35fc6bcc-ba65-4ac7-bfa4-5518a15f14b8,Catuai,Classic varietal with distinct flavor profile.,"['Costa Rica', 'Brazil', 'Colombia']","['Floral', 'Spicy', 'Fruity']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,93.9,"['Wet-hulled', 'Honey', 'Washed']"
|
||||||
|
11492b6f-d649-4a6f-8efd-c62b66f45258,Pacamara,Classic varietal with distinct flavor profile.,"['Kenya', 'Ethiopia', 'Jamaica']","['Fruity', 'Caramel', 'Spicy']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,90.2,"['Natural', 'Washed', 'Wet-hulled']"
|
||||||
|
1dd3c6c7-99a3-467a-8e86-05b9b57340cf,Maragogipe,Classic varietal with distinct flavor profile.,"['Honduras', 'Yemen', 'Costa Rica']","['Chocolate', 'Spicy', 'Caramel']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,91.2,"['Semi-washed', 'Honey', 'Wet-hulled']"
|
||||||
|
205ae039-3f8c-47e8-a5d9-2e8f5e01babe,SL34,Classic varietal with distinct flavor profile.,"['Guatemala', 'Costa Rica', 'Brazil']","['Nutty', 'Chocolate', 'Berry']","Grows well at high altitudes, medium yield.",Bourbon x Typica,1200-2000m,Medium,Moderate resistance to rust,86.1,"['Wet-hulled', 'Honey', 'Semi-washed']"
|
||||||
|
51
lib/database/Brew_Recipes.csv
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
id,name,servingTemp,milkType,brewMethod,grindSize,coffeeAmount,waterAmount,brewTime,instructions,notes,difficulty,equipmentNeeded,yieldAmount,caffeinePer100ml,waterTemperature,bloomTime,totalExtractionTime,grindToWaterRatio,tags,origin,rating,popularity,createdBy,isPublic,lastModified
|
||||||
|
00ba5a1a-fa7f-4506-83f7-b83cb24f8205,Espresso Brew,Hot,Skim,Espresso,Fine,20.7,252.3,261,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Espresso Machine', 'Grinder', 'Scale']",203.8,45.5,92,47,270,1:16,"['Balanced', 'Rich', 'Fruity']",James Hoffmann,4.2,779,user123,True,2025-04-01
|
||||||
|
4a6f6b52-a51c-413d-a1ef-28c79389b9ea,Pour Over Brew,Cold,Skim,Pour Over,Coarse,24.5,276.0,198,Step-by-step brewing instructions.,Use fresh filtered water.,Intermediate,"['Grinder', 'Espresso Machine', 'Filter']",265.9,55.8,91,21,285,1:16,"['Balanced', 'Fruity', 'Rich']",James Hoffmann,1.9,433,user123,True,2024-09-11
|
||||||
|
dcfb878f-8fe6-4ab9-9cbb-2147b478fdc6,Pour Over Brew,Cold,Coconut,Drip,Medium,20.0,266.2,263,Step-by-step brewing instructions.,Use fresh filtered water.,Intermediate,"['Scale', 'Grinder', 'Kettle']",227.1,67.6,93,20,213,1:16,"['Rich', 'Balanced', 'Fruity']",James Hoffmann,4.1,422,user123,True,2025-04-17
|
||||||
|
758ceee8-b60f-4146-9bb1-698b33397fa2,French Press Brew,Iced,Soy,Pour Over,Fine,15.2,282.3,270,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Grinder', 'Espresso Machine', 'Scale']",283.5,68.3,95,47,240,1:16,"['Balanced', 'Rich', 'Fruity']",James Hoffmann,4.5,376,user123,True,2024-09-24
|
||||||
|
4cb440cf-0ad6-49ae-b5ae-597fac9a902f,Espresso Brew,Hot,Almond,Pour Over,Medium,21.0,205.8,198,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Kettle', 'Scale', 'Espresso Machine']",244.0,68.8,95,41,232,1:16,"['Rich', 'Balanced', 'Fruity']",James Hoffmann,4.2,756,user123,True,2024-07-09
|
||||||
|
86c81bad-9f3b-421c-8fd3-a804e50876d1,Drip Brew,Hot,Soy,Espresso,Coarse,15.6,224.2,234,Step-by-step brewing instructions.,Use fresh filtered water.,Intermediate,"['Kettle', 'Grinder', 'Filter']",273.0,48.1,96,37,222,1:16,"['Fruity', 'Rich', 'Balanced']",James Hoffmann,2.0,741,user123,True,2025-02-25
|
||||||
|
47b9fc53-5610-4b15-a607-f69c6e18d73e,French Press Brew,Iced,Soy,Drip,Medium,20.8,245.4,218,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Espresso Machine', 'Kettle', 'Filter']",235.6,75.6,96,39,283,1:16,"['Balanced', 'Rich', 'Fruity']",James Hoffmann,2.8,748,user123,True,2025-05-12
|
||||||
|
59047b27-9813-4f5b-bf18-2932ceb35123,Pour Over Brew,Iced,Whole,Pour Over,Medium,20.9,206.2,150,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Kettle', 'Filter', 'Espresso Machine']",208.2,51.4,94,27,194,1:16,"['Balanced', 'Fruity', 'Rich']",James Hoffmann,2.1,661,user123,True,2025-03-05
|
||||||
|
8f3da6c5-de6f-4201-b796-487486357d5d,Drip Brew,Cold,Almond,French Press,Medium,15.3,258.2,134,Step-by-step brewing instructions.,Use fresh filtered water.,Intermediate,"['Scale', 'Grinder', 'Filter']",287.3,53.6,95,41,207,1:16,"['Fruity', 'Rich', 'Balanced']",James Hoffmann,2.0,69,user123,True,2025-01-17
|
||||||
|
faafd1f3-dd01-4578-8a38-0b77e1b83202,Pour Over Brew,Hot,Soy,Pour Over,Fine,21.9,230.6,248,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Kettle', 'Espresso Machine', 'Filter']",231.7,50.0,95,36,266,1:16,"['Balanced', 'Fruity', 'Rich']",James Hoffmann,4.7,93,user123,True,2024-10-20
|
||||||
|
20f8d4b1-5ae5-49f1-96be-906fcf82e0e7,French Press Brew,Hot,Whole,Espresso,Medium,21.9,205.1,182,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Espresso Machine', 'Kettle', 'Filter']",272.8,72.7,94,25,290,1:16,"['Balanced', 'Fruity', 'Rich']",James Hoffmann,1.0,515,user123,True,2025-04-26
|
||||||
|
30f6b268-1ba3-4fd6-8eab-50c508fc68a5,French Press Brew,Cold,Soy,Pour Over,Medium,19.4,258.2,190,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Filter', 'Grinder', 'Scale']",210.6,44.7,90,50,242,1:16,"['Balanced', 'Fruity', 'Rich']",James Hoffmann,3.0,718,user123,True,2025-04-17
|
||||||
|
6aa0404a-9f89-4454-a75e-8cf14255b8b4,Pour Over Brew,Cold,Whole,Pour Over,Fine,16.0,245.1,221,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Filter', 'Grinder', 'Espresso Machine']",264.3,65.4,92,21,259,1:16,"['Fruity', 'Balanced', 'Rich']",James Hoffmann,2.9,747,user123,True,2025-04-30
|
||||||
|
8138f04a-eac7-4d24-8b44-c28ccfd883d1,Drip Brew,Hot,Oat,French Press,Medium,20.5,217.7,208,Step-by-step brewing instructions.,Use fresh filtered water.,Intermediate,"['Kettle', 'Scale', 'Filter']",233.5,76.5,94,22,213,1:16,"['Balanced', 'Fruity', 'Rich']",James Hoffmann,2.0,435,user123,True,2025-03-29
|
||||||
|
6ce67a9d-9c76-4763-8fec-e09dd45b917c,Espresso Brew,Hot,Almond,French Press,Coarse,21.2,216.2,144,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Espresso Machine', 'Scale', 'Grinder']",217.2,54.7,94,43,298,1:16,"['Rich', 'Balanced', 'Fruity']",James Hoffmann,4.3,799,user123,True,2025-03-27
|
||||||
|
ed2c15a0-f904-436a-8022-a35ea56adeca,Pour Over Brew,Hot,Skim,Pour Over,Coarse,16.7,294.3,278,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Scale', 'Grinder', 'Filter']",291.9,43.5,95,21,229,1:16,"['Balanced', 'Rich', 'Fruity']",James Hoffmann,4.0,144,user123,True,2025-02-25
|
||||||
|
8b780ba6-9af9-45fc-92f4-d3cf9518ffbc,Espresso Brew,Iced,Oat,Drip,Coarse,17.6,276.3,252,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Kettle', 'Filter', 'Espresso Machine']",242.2,53.6,95,39,206,1:16,"['Balanced', 'Rich', 'Fruity']",James Hoffmann,5.0,891,user123,True,2025-04-04
|
||||||
|
0f75e1c3-d5e1-4774-8f29-a26131ce4567,Drip Brew,Cold,Whole,Espresso,Coarse,19.0,299.8,171,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Espresso Machine', 'Filter', 'Scale']",221.1,55.2,95,59,203,1:16,"['Fruity', 'Balanced', 'Rich']",James Hoffmann,2.4,627,user123,True,2025-05-17
|
||||||
|
10568a68-d7cc-46d0-8ec5-0f9171d973cc,Pour Over Brew,Hot,Soy,Drip,Fine,16.0,276.4,252,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Espresso Machine', 'Grinder', 'Scale']",258.7,60.8,96,26,201,1:16,"['Fruity', 'Balanced', 'Rich']",James Hoffmann,1.0,480,user123,True,2024-12-13
|
||||||
|
e453617a-8d19-456d-8ee4-3170b77f0d46,Drip Brew,Cold,Whole,French Press,Coarse,15.3,215.5,132,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Scale', 'Filter', 'Espresso Machine']",250.7,68.2,91,55,245,1:16,"['Fruity', 'Balanced', 'Rich']",James Hoffmann,3.2,16,user123,True,2024-12-14
|
||||||
|
273fb235-2fe8-4e85-93e2-34bfab3327de,French Press Brew,Hot,Soy,French Press,Medium,20.5,255.2,275,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Filter', 'Scale', 'Espresso Machine']",284.7,57.0,96,20,270,1:16,"['Fruity', 'Balanced', 'Rich']",James Hoffmann,4.6,963,user123,True,2024-07-10
|
||||||
|
23157240-472d-40b0-9412-3e527f06ea96,Pour Over Brew,Cold,Almond,Drip,Fine,18.1,202.7,139,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Espresso Machine', 'Filter', 'Grinder']",228.4,60.7,94,28,272,1:16,"['Fruity', 'Rich', 'Balanced']",James Hoffmann,4.0,197,user123,True,2025-01-28
|
||||||
|
2de45288-81a5-4ee4-b890-592ecfac6fa9,Pour Over Brew,Iced,Soy,Espresso,Medium,16.8,267.2,214,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Grinder', 'Scale', 'Kettle']",241.9,53.8,95,44,276,1:16,"['Balanced', 'Fruity', 'Rich']",James Hoffmann,3.2,210,user123,True,2025-03-03
|
||||||
|
f8fc1981-20fd-4658-a4ff-f7c92b3302e9,French Press Brew,Iced,Almond,Espresso,Medium,21.9,230.1,162,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Espresso Machine', 'Filter', 'Scale']",209.2,70.2,93,50,274,1:16,"['Balanced', 'Fruity', 'Rich']",James Hoffmann,1.4,359,user123,True,2024-10-22
|
||||||
|
b08bfdf8-3f20-4987-893a-bab5a8df51eb,Drip Brew,Iced,Coconut,Espresso,Medium,17.2,211.7,231,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Scale', 'Filter', 'Kettle']",202.8,75.4,94,57,300,1:16,"['Balanced', 'Fruity', 'Rich']",James Hoffmann,3.6,372,user123,True,2024-10-04
|
||||||
|
63b4adf7-791f-4b2f-bce2-c95570c1e475,French Press Brew,Hot,Almond,Drip,Medium,18.0,240.2,289,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Kettle', 'Grinder', 'Espresso Machine']",217.2,60.3,91,59,187,1:16,"['Balanced', 'Rich', 'Fruity']",James Hoffmann,3.3,859,user123,True,2025-05-14
|
||||||
|
331180a0-54a2-4e16-9f49-6a379bfe7a04,Drip Brew,Hot,Oat,Pour Over,Medium,25.0,256.2,214,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Grinder', 'Espresso Machine', 'Scale']",247.7,56.4,94,27,221,1:16,"['Rich', 'Balanced', 'Fruity']",James Hoffmann,1.4,383,user123,True,2024-12-30
|
||||||
|
cba4d200-b318-44e7-9165-f901e07d97fc,Drip Brew,Cold,Coconut,Drip,Medium,18.3,242.2,194,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Scale', 'Filter', 'Kettle']",284.6,64.9,95,56,187,1:16,"['Fruity', 'Rich', 'Balanced']",James Hoffmann,2.5,392,user123,True,2024-09-09
|
||||||
|
9b0c3b3f-d134-487b-a286-50f1d6d22b56,French Press Brew,Cold,Almond,Drip,Fine,23.2,228.1,199,Step-by-step brewing instructions.,Use fresh filtered water.,Intermediate,"['Grinder', 'Kettle', 'Filter']",291.1,66.2,91,58,209,1:16,"['Balanced', 'Rich', 'Fruity']",James Hoffmann,3.7,731,user123,True,2024-09-21
|
||||||
|
eb307dae-ee9e-429d-bb92-cba33510048b,French Press Brew,Cold,Skim,Pour Over,Medium,18.9,248.2,280,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Grinder', 'Filter', 'Kettle']",296.5,72.1,92,24,230,1:16,"['Fruity', 'Balanced', 'Rich']",James Hoffmann,1.7,393,user123,True,2024-09-13
|
||||||
|
4f15a395-c39a-40b3-b79e-7ee5c2a66fae,Drip Brew,Cold,Coconut,Drip,Fine,24.1,272.4,238,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Espresso Machine', 'Kettle', 'Scale']",249.3,65.4,91,45,285,1:16,"['Fruity', 'Balanced', 'Rich']",James Hoffmann,2.4,13,user123,True,2025-04-13
|
||||||
|
5d9c02a6-e537-4a1d-b46f-00a242d26a3c,Espresso Brew,Cold,Oat,Drip,Medium,22.3,252.5,187,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Scale', 'Grinder', 'Filter']",212.4,74.0,90,46,273,1:16,"['Fruity', 'Rich', 'Balanced']",James Hoffmann,3.2,288,user123,True,2024-11-03
|
||||||
|
b2fabec0-d495-4f17-92ee-d0cc9c0ea1c5,French Press Brew,Iced,Skim,Drip,Coarse,16.3,233.9,199,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Kettle', 'Filter', 'Espresso Machine']",244.8,75.5,91,35,190,1:16,"['Balanced', 'Fruity', 'Rich']",James Hoffmann,3.7,465,user123,True,2024-07-28
|
||||||
|
c48b89e9-239d-4027-bc2a-7bae2ec88367,Pour Over Brew,Cold,Whole,Pour Over,Coarse,15.9,220.6,145,Step-by-step brewing instructions.,Use fresh filtered water.,Intermediate,"['Filter', 'Espresso Machine', 'Kettle']",201.4,51.1,96,53,218,1:16,"['Rich', 'Fruity', 'Balanced']",James Hoffmann,3.1,340,user123,True,2024-07-10
|
||||||
|
68c4d331-50c0-4b7a-8a96-4aca650c3c86,Espresso Brew,Hot,Oat,Espresso,Fine,15.2,254.8,132,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Scale', 'Filter', 'Kettle']",267.0,52.0,93,22,253,1:16,"['Fruity', 'Rich', 'Balanced']",James Hoffmann,2.6,682,user123,True,2024-12-15
|
||||||
|
84fdd0f1-dde6-4271-8c22-df92c993baeb,Drip Brew,Hot,Whole,French Press,Medium,24.9,210.8,267,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Filter', 'Espresso Machine', 'Kettle']",217.0,65.5,94,30,272,1:16,"['Fruity', 'Balanced', 'Rich']",James Hoffmann,5.0,927,user123,True,2024-10-18
|
||||||
|
d64efb2c-bd9d-42c4-81ab-e48a08152e1c,Drip Brew,Cold,Whole,French Press,Medium,16.0,228.9,231,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Grinder', 'Filter', 'Kettle']",211.0,78.8,93,40,281,1:16,"['Rich', 'Balanced', 'Fruity']",James Hoffmann,1.7,666,user123,True,2024-10-08
|
||||||
|
4180d9ad-43ee-4bc5-844e-b240ba41f809,Drip Brew,Hot,Soy,Pour Over,Medium,24.1,218.4,138,Step-by-step brewing instructions.,Use fresh filtered water.,Intermediate,"['Grinder', 'Kettle', 'Espresso Machine']",237.1,47.7,96,28,291,1:16,"['Rich', 'Balanced', 'Fruity']",James Hoffmann,2.2,328,user123,True,2025-01-30
|
||||||
|
4558bfb0-fb12-4b57-a969-fd84936c88ae,French Press Brew,Iced,Oat,French Press,Coarse,20.5,213.4,268,Step-by-step brewing instructions.,Use fresh filtered water.,Intermediate,"['Kettle', 'Espresso Machine', 'Filter']",260.3,60.2,92,36,277,1:16,"['Rich', 'Balanced', 'Fruity']",James Hoffmann,1.1,52,user123,True,2025-02-09
|
||||||
|
10e001a7-7458-4b8e-b4e6-f032cc7b36a0,Espresso Brew,Cold,Coconut,Pour Over,Coarse,17.6,216.8,166,Step-by-step brewing instructions.,Use fresh filtered water.,Intermediate,"['Scale', 'Grinder', 'Espresso Machine']",205.5,56.9,92,51,223,1:16,"['Balanced', 'Rich', 'Fruity']",James Hoffmann,2.7,357,user123,True,2024-11-15
|
||||||
|
94968ed4-f573-446e-8036-9450d29a9614,Pour Over Brew,Cold,Whole,Pour Over,Coarse,21.9,237.1,235,Step-by-step brewing instructions.,Use fresh filtered water.,Intermediate,"['Espresso Machine', 'Filter', 'Kettle']",245.5,57.5,94,23,181,1:16,"['Rich', 'Balanced', 'Fruity']",James Hoffmann,4.1,759,user123,True,2024-12-19
|
||||||
|
2c0e107e-6c24-4957-8750-9e8810dd2b23,Espresso Brew,Iced,Pistachio,Pour Over,Coarse,22.5,258.3,270,Step-by-step brewing instructions.,Use fresh filtered water.,Intermediate,"['Espresso Machine', 'Grinder', 'Filter']",261.1,61.5,92,45,268,1:16,"['Rich', 'Fruity', 'Balanced']",James Hoffmann,2.9,645,user123,True,2024-12-26
|
||||||
|
fa1a620d-0401-42fe-90dd-e40aa012ccd9,Espresso Brew,Iced,Oat,French Press,Fine,17.8,233.0,293,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Kettle', 'Espresso Machine', 'Grinder']",243.9,41.3,94,23,300,1:16,"['Rich', 'Balanced', 'Fruity']",James Hoffmann,4.5,499,user123,True,2025-04-13
|
||||||
|
e1eabec5-4e95-4240-9df6-1db5364417d5,French Press Brew,Cold,Coconut,Pour Over,Coarse,19.9,260.4,241,Step-by-step brewing instructions.,Use fresh filtered water.,Advanced,"['Espresso Machine', 'Grinder', 'Scale']",268.3,62.9,96,57,260,1:16,"['Balanced', 'Rich', 'Fruity']",James Hoffmann,1.4,684,user123,True,2025-04-06
|
||||||
|
872282ae-a31f-4e3f-a566-915b57a3292f,Drip Brew,Cold,Oat,Pour Over,Coarse,18.0,252.7,300,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Grinder', 'Espresso Machine', 'Filter']",244.3,72.7,92,25,221,1:16,"['Balanced', 'Rich', 'Fruity']",James Hoffmann,1.1,714,user123,True,2024-11-09
|
||||||
|
a2e830bb-d45a-4160-bb5f-45d93cf262ef,Pour Over Brew,Cold,Pistachio,French Press,Fine,20.7,244.3,173,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Kettle', 'Espresso Machine', 'Filter']",270.1,66.1,93,54,294,1:16,"['Balanced', 'Rich', 'Fruity']",James Hoffmann,4.2,781,user123,True,2025-01-05
|
||||||
|
4b5c76c2-1024-4225-8060-01b8a6edb042,Drip Brew,Iced,Skim,Pour Over,Fine,17.4,201.2,156,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Scale', 'Grinder', 'Kettle']",296.2,47.5,92,29,235,1:16,"['Fruity', 'Rich', 'Balanced']",James Hoffmann,3.8,624,user123,True,2024-11-11
|
||||||
|
ecfa9ff0-6fb3-4f57-85c7-aa4eb05a97da,French Press Brew,Cold,Whole,Pour Over,Coarse,16.0,295.7,273,Step-by-step brewing instructions.,Use fresh filtered water.,Intermediate,"['Espresso Machine', 'Grinder', 'Scale']",237.8,78.4,94,49,208,1:16,"['Rich', 'Balanced', 'Fruity']",James Hoffmann,3.2,842,user123,True,2024-07-22
|
||||||
|
f5e83135-365a-4a5c-9204-21e7d9c2b1ca,Pour Over Brew,Hot,Oat,Espresso,Medium,21.8,264.8,175,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Grinder', 'Kettle', 'Scale']",270.9,64.7,93,22,186,1:16,"['Rich', 'Balanced', 'Fruity']",James Hoffmann,2.2,32,user123,True,2024-07-28
|
||||||
|
caf68c12-38f4-4d80-b981-26aae300662d,French Press Brew,Cold,Coconut,Drip,Medium,22.9,277.1,123,Step-by-step brewing instructions.,Use fresh filtered water.,Beginner,"['Scale', 'Grinder', 'Kettle']",204.5,75.0,95,38,300,1:16,"['Balanced', 'Fruity', 'Rich']",James Hoffmann,1.3,82,user123,True,2024-07-19
|
||||||
|
51
lib/database/Coffee_Beans.csv
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
id,name,origin,farm,producer,varietal,altitude,processingMethod,harvestSeason,flavorNotes,acidity,body,sweetness,roastLevel,cupScore,price,availability,certifications,roaster,roastDate,bestByDate,brewingMethods,isOwned,quantity,notes
|
||||||
|
006cc56c-37c6-41cd-b14d-1b7727459b0a,Single Origin Panama,Ethiopia,Finca Esperanza,Juan Valdez,4cb8c887-67a0-4fcd-a0a2-56e42b1b44b2,1870,Wet-hulled,October-February,"['Chocolate', 'Floral', 'Nutty']",Medium-High,Full,2,Medium-Dark,84.1,14.5,Sold Out,"['Direct Trade', 'Fair Trade', 'Organic']",Onyx Coffee Lab,2024-08-14,2024-07-21,"['Drip', 'Espresso', 'French Press']",False,111.5,Great for espresso and pour over.
|
||||||
|
1779f0d7-4c6a-4fe5-9f6e-6dc1282d7db2,Single Origin Brazil,Ethiopia,Finca Esperanza,Juan Valdez,defaec07-2383-42b2-9cb7-b7d1534adc36,2050,Natural,October-February,"['Spicy', 'Berry', 'Chocolate']",Medium,Full,1,Light,91.6,14.2,Available,"['Organic', 'Rainforest Alliance', 'Direct Trade']",Onyx Coffee Lab,2024-09-05,2025-06-02,"['Pour Over', 'Drip', 'French Press']",False,276.9,Great for espresso and pour over.
|
||||||
|
c3f9c312-6626-4855-ab7e-46441c0ae019,Single Origin Yemen,Panama,Finca Esperanza,Juan Valdez,cb9ffb87-251f-42e1-a64e-eb8fbb1883a5,1367,Semi-washed,October-February,"['Fruity', 'Chocolate', 'Citrus']",High,Medium-Light,10,Light,89.5,17.4,Seasonal,"['Fair Trade', 'Direct Trade', 'Rainforest Alliance']",Onyx Coffee Lab,2024-11-02,2025-06-01,"['Drip', 'Espresso', 'French Press']",False,462.7,Great for espresso and pour over.
|
||||||
|
656c7024-d68d-4c55-907a-e45ce246f4b5,Single Origin Honduras,Panama,Finca Esperanza,Juan Valdez,dc90c31d-5184-4f81-9fa9-e53a219366f1,1302,Natural,October-February,"['Floral', 'Citrus', 'Berry']",Medium-High,Medium-Light,7,Medium,85.9,15.3,Sold Out,"['Rainforest Alliance', 'Organic', 'Fair Trade']",Onyx Coffee Lab,2024-08-06,2024-09-28,"['French Press', 'Espresso', 'Drip']",True,135.2,Great for espresso and pour over.
|
||||||
|
1aae3999-0487-46b7-a45d-8bdb92ff4fb7,Single Origin Yemen,Yemen,Finca Esperanza,Juan Valdez,1db22ac4-4abe-4608-ba28-91de51598a8b,1717,Washed,October-February,"['Fruity', 'Floral', 'Berry']",Medium-High,Medium-Full,8,Dark,83.0,30.0,Sold Out,"['Fair Trade', 'Rainforest Alliance', 'Direct Trade']",Onyx Coffee Lab,2024-10-23,2025-05-21,"['Pour Over', 'French Press', 'Espresso']",True,230.2,Great for espresso and pour over.
|
||||||
|
b5ea3e07-5ce1-41dd-87d5-a3f1fcedef4a,Single Origin Honduras,Panama,Finca Esperanza,Juan Valdez,b5098c67-16b0-4e25-8569-98b2df912df4,1785,Wet-hulled,October-February,"['Caramel', 'Floral', 'Berry']",Medium-High,Medium-Light,3,Medium-Dark,91.7,23.9,Seasonal,"['Rainforest Alliance', 'Direct Trade', 'Fair Trade']",Onyx Coffee Lab,2024-09-16,2024-12-31,"['Espresso', 'Drip', 'Pour Over']",True,145.3,Great for espresso and pour over.
|
||||||
|
81364c47-3c1a-46e0-a163-17bf00dca3f6,Single Origin Ethiopia,Kenya,Finca Esperanza,Juan Valdez,25864ddb-5277-4be3-9c1b-e84f8c42697a,1397,Washed,October-February,"['Citrus', 'Spicy', 'Berry']",Medium-High,Medium-Light,2,Medium,88.8,28.7,Seasonal,"['Direct Trade', 'Organic', 'Fair Trade']",Onyx Coffee Lab,2024-08-04,2025-01-14,"['French Press', 'Drip', 'Pour Over']",True,152.9,Great for espresso and pour over.
|
||||||
|
8e7edbb6-b950-41e4-90c2-5d87cbc66146,Single Origin Panama,Kenya,Finca Esperanza,Juan Valdez,1bf9739d-ac22-40ac-844f-fe7e7db39e76,1591,Wet-hulled,October-February,"['Chocolate', 'Spicy', 'Fruity']",Medium,Medium-Light,9,Dark,88.2,15.5,Limited,"['Direct Trade', 'Organic', 'Fair Trade']",Onyx Coffee Lab,2025-05-03,2025-02-23,"['French Press', 'Drip', 'Espresso']",True,249.4,Great for espresso and pour over.
|
||||||
|
e5122acf-4b5b-4393-979d-f1c048116040,Single Origin Honduras,Colombia,Finca Esperanza,Juan Valdez,1e1811a3-b567-47c0-b8ce-a720010cd280,1662,Honey,October-February,"['Chocolate', 'Citrus', 'Fruity']",Medium,Medium,3,Medium-Dark,92.5,10.2,Available,"['Rainforest Alliance', 'Fair Trade', 'Organic']",Onyx Coffee Lab,2024-10-13,2024-12-05,"['French Press', 'Drip', 'Pour Over']",False,64.8,Great for espresso and pour over.
|
||||||
|
9b9cb407-250d-4e3a-9854-860ce72e46c8,Single Origin Costa Rica,Jamaica,Finca Esperanza,Juan Valdez,35aa7bc2-2ebc-4136-b028-90c6e6eccbfd,1491,Natural,October-February,"['Berry', 'Caramel', 'Fruity']",Medium-High,Medium-Light,6,Medium-Dark,81.5,10.7,Seasonal,"['Direct Trade', 'Organic', 'Fair Trade']",Onyx Coffee Lab,2024-10-22,2025-06-13,"['Pour Over', 'French Press', 'Drip']",True,323.2,Great for espresso and pour over.
|
||||||
|
6bc4d458-6039-4b4a-a1aa-7073a419101b,Single Origin Costa Rica,Ethiopia,Finca Esperanza,Juan Valdez,176ed0db-b8b0-4572-a5d3-f770170f78b1,1255,Washed,October-February,"['Caramel', 'Chocolate', 'Spicy']",High,Full,3,Medium-Dark,85.9,16.6,Available,"['Organic', 'Direct Trade', 'Rainforest Alliance']",Onyx Coffee Lab,2025-01-23,2024-09-21,"['Pour Over', 'Drip', 'French Press']",False,164.3,Great for espresso and pour over.
|
||||||
|
736a8934-4109-48d9-bf93-4c83c1090754,Single Origin Jamaica,Honduras,Finca Esperanza,Juan Valdez,911a40dd-7f2f-4730-8089-b737c8a8f2de,1724,Semi-washed,October-February,"['Floral', 'Nutty', 'Citrus']",Low,Full,4,Medium,92.0,16.6,Limited,"['Organic', 'Fair Trade', 'Direct Trade']",Onyx Coffee Lab,2024-10-19,2025-06-04,"['Pour Over', 'Drip', 'French Press']",True,366.9,Great for espresso and pour over.
|
||||||
|
df66be3f-ecee-47e0-9ca2-c420ab21e70a,Single Origin Jamaica,Ethiopia,Finca Esperanza,Juan Valdez,4912d9f9-cb2a-41ce-a4dc-879f347b9bb5,1475,Natural,October-February,"['Fruity', 'Berry', 'Floral']",Medium,Medium-Light,5,Light,82.7,17.1,Limited,"['Fair Trade', 'Rainforest Alliance', 'Organic']",Onyx Coffee Lab,2024-12-29,2025-03-20,"['French Press', 'Espresso', 'Pour Over']",True,453.5,Great for espresso and pour over.
|
||||||
|
5e54067b-d2ba-45be-b398-c989217643da,Single Origin Colombia,Jamaica,Finca Esperanza,Juan Valdez,f72494df-f47e-4a47-86dd-2faf385c9200,1716,Wet-hulled,October-February,"['Fruity', 'Citrus', 'Berry']",Low,Medium-Light,6,Medium-Light,84.8,15.2,Limited,"['Direct Trade', 'Organic', 'Rainforest Alliance']",Onyx Coffee Lab,2025-06-06,2024-07-15,"['Espresso', 'French Press', 'Pour Over']",False,413.7,Great for espresso and pour over.
|
||||||
|
fb4af9c3-0ed3-4b46-9444-cb3d26cf30d4,Single Origin Ethiopia,Honduras,Finca Esperanza,Juan Valdez,11a7e600-7861-49ea-9536-fa67c689f63a,1356,Washed,October-February,"['Chocolate', 'Nutty', 'Fruity']",High,Light,1,Medium-Light,82.8,12.4,Sold Out,"['Fair Trade', 'Direct Trade', 'Rainforest Alliance']",Onyx Coffee Lab,2025-04-24,2024-07-10,"['Pour Over', 'Drip', 'Espresso']",True,402.2,Great for espresso and pour over.
|
||||||
|
bcb4d8a6-6b86-4863-8900-c22994993e6f,Single Origin Costa Rica,Honduras,Finca Esperanza,Juan Valdez,c90b8225-53d1-4280-bba9-0c4133b4b975,1713,Semi-washed,October-February,"['Chocolate', 'Caramel', 'Fruity']",Medium,Medium,8,Medium,94.7,20.4,Sold Out,"['Fair Trade', 'Organic', 'Rainforest Alliance']",Onyx Coffee Lab,2024-11-27,2024-07-05,"['Pour Over', 'French Press', 'Drip']",True,473.7,Great for espresso and pour over.
|
||||||
|
78ecb4d2-4990-4558-a30c-7ade7723d4a7,Single Origin Costa Rica,Panama,Finca Esperanza,Juan Valdez,5d6feb96-4633-43b9-b44f-6579418c5994,1754,Washed,October-February,"['Floral', 'Berry', 'Citrus']",Low,Medium-Full,10,Medium,93.8,15.1,Available,"['Direct Trade', 'Rainforest Alliance', 'Organic']",Onyx Coffee Lab,2024-07-12,2025-06-13,"['Drip', 'Espresso', 'French Press']",False,437.7,Great for espresso and pour over.
|
||||||
|
27528cec-6823-4125-bd06-c36382d5d0ea,Single Origin Guatemala,Brazil,Finca Esperanza,Juan Valdez,32cfc671-0ec5-49ac-b507-894776fd3f11,1982,Semi-washed,October-February,"['Spicy', 'Citrus', 'Fruity']",Medium-Low,Full,8,Medium-Dark,94.3,21.5,Sold Out,"['Organic', 'Direct Trade', 'Rainforest Alliance']",Onyx Coffee Lab,2024-07-18,2024-07-25,"['Drip', 'Pour Over', 'French Press']",False,329.1,Great for espresso and pour over.
|
||||||
|
4600df1d-deb8-495e-b121-663fee586a0f,Single Origin Brazil,Costa Rica,Finca Esperanza,Juan Valdez,a692d28b-de2f-4a6e-b056-b34ffa8abedb,1521,Natural,October-February,"['Chocolate', 'Berry', 'Floral']",Low,Medium-Full,2,Medium-Light,90.8,16.1,Limited,"['Fair Trade', 'Organic', 'Direct Trade']",Onyx Coffee Lab,2025-01-27,2025-04-26,"['Espresso', 'Pour Over', 'Drip']",True,132.0,Great for espresso and pour over.
|
||||||
|
f43b95a1-47c4-48df-83a9-5bf66008d7ff,Single Origin Jamaica,Panama,Finca Esperanza,Juan Valdez,d3858040-88a3-48dc-a5f9-a372619a1ef1,1571,Wet-hulled,October-February,"['Chocolate', 'Fruity', 'Nutty']",Medium,Medium-Light,8,Medium,93.5,28.8,Seasonal,"['Fair Trade', 'Rainforest Alliance', 'Organic']",Onyx Coffee Lab,2025-03-19,2024-10-06,"['Espresso', 'Drip', 'Pour Over']",True,419.3,Great for espresso and pour over.
|
||||||
|
41cbf6a3-12b5-4c5f-a2dd-cd2d1dde14b0,Single Origin Honduras,Colombia,Finca Esperanza,Juan Valdez,8998ecd3-426e-4875-bd97-44cf0d16126c,1578,Semi-washed,October-February,"['Berry', 'Floral', 'Chocolate']",Medium,Medium,7,Light,91.9,29.8,Sold Out,"['Rainforest Alliance', 'Organic', 'Direct Trade']",Onyx Coffee Lab,2024-11-29,2024-11-05,"['Drip', 'Pour Over', 'Espresso']",True,336.1,Great for espresso and pour over.
|
||||||
|
d590fe31-383d-4f78-809e-b2cd1f758c02,Single Origin Guatemala,Honduras,Finca Esperanza,Juan Valdez,c5986848-f4f5-4623-bcf0-1c8089cb59df,1796,Semi-washed,October-February,"['Caramel', 'Nutty', 'Chocolate']",Medium-Low,Full,4,Medium-Dark,86.7,21.0,Available,"['Organic', 'Fair Trade', 'Rainforest Alliance']",Onyx Coffee Lab,2025-06-16,2024-10-03,"['French Press', 'Espresso', 'Drip']",False,219.9,Great for espresso and pour over.
|
||||||
|
14f444a1-4e1d-4c22-867f-25d4931149fc,Single Origin Jamaica,Honduras,Finca Esperanza,Juan Valdez,b5fc0ec8-cb76-4a56-ae5d-a751374d7be9,1594,Natural,October-February,"['Nutty', 'Chocolate', 'Citrus']",Medium,Medium,9,Dark,83.8,13.5,Seasonal,"['Direct Trade', 'Rainforest Alliance', 'Organic']",Onyx Coffee Lab,2025-05-18,2025-05-30,"['French Press', 'Pour Over', 'Espresso']",False,494.7,Great for espresso and pour over.
|
||||||
|
729c3a6c-b922-4a1b-8bea-b998ae944429,Single Origin Jamaica,Ethiopia,Finca Esperanza,Juan Valdez,71d5b237-37b7-412a-86f9-8cf2120cc6c0,1611,Washed,October-February,"['Nutty', 'Berry', 'Caramel']",Medium,Full,1,Medium,87.5,20.4,Sold Out,"['Direct Trade', 'Organic', 'Fair Trade']",Onyx Coffee Lab,2025-05-02,2025-06-06,"['Drip', 'Espresso', 'Pour Over']",False,66.4,Great for espresso and pour over.
|
||||||
|
2a59bf8c-b3e5-41ca-8dfe-0356422b7422,Single Origin Costa Rica,Honduras,Finca Esperanza,Juan Valdez,bf45f0d8-3ca7-41a7-a3b9-85cb33d827f0,1290,Semi-washed,October-February,"['Chocolate', 'Citrus', 'Fruity']",High,Medium,7,Medium,86.8,23.6,Available,"['Organic', 'Direct Trade', 'Rainforest Alliance']",Onyx Coffee Lab,2025-06-02,2025-04-06,"['French Press', 'Drip', 'Espresso']",True,333.1,Great for espresso and pour over.
|
||||||
|
4cc02da6-8e47-4f33-a909-12b6bc77bf9c,Single Origin Honduras,Ethiopia,Finca Esperanza,Juan Valdez,1f73a289-b3a0-4098-af8f-f34cb9af4149,1305,Honey,October-February,"['Nutty', 'Fruity', 'Floral']",Medium-High,Light,3,Medium,89.1,30.0,Available,"['Organic', 'Fair Trade', 'Rainforest Alliance']",Onyx Coffee Lab,2024-07-10,2024-07-28,"['Drip', 'Pour Over', 'French Press']",True,491.1,Great for espresso and pour over.
|
||||||
|
cdaa60df-8fea-44e8-8902-f94b594433a3,Single Origin Yemen,Guatemala,Finca Esperanza,Juan Valdez,67274ddf-a2d8-4105-85b3-ea51e2c6fe5c,2035,Washed,October-February,"['Chocolate', 'Fruity', 'Floral']",Medium-High,Full,9,Medium-Dark,92.3,29.5,Limited,"['Organic', 'Rainforest Alliance', 'Fair Trade']",Onyx Coffee Lab,2025-02-11,2024-12-15,"['Pour Over', 'Espresso', 'Drip']",True,164.1,Great for espresso and pour over.
|
||||||
|
5f4a4c34-7695-403a-995c-3b26309cc617,Single Origin Colombia,Guatemala,Finca Esperanza,Juan Valdez,cde7eef0-1cce-418f-a2b3-403e856573de,1849,Washed,October-February,"['Citrus', 'Spicy', 'Caramel']",High,Medium,8,Dark,93.0,15.7,Sold Out,"['Fair Trade', 'Organic', 'Rainforest Alliance']",Onyx Coffee Lab,2025-02-24,2024-09-17,"['Pour Over', 'Drip', 'Espresso']",True,344.5,Great for espresso and pour over.
|
||||||
|
8438ed6c-c20a-4343-9105-18d451c7de82,Single Origin Brazil,Jamaica,Finca Esperanza,Juan Valdez,734335e8-9379-4b47-9490-5b7500c1bc15,1830,Wet-hulled,October-February,"['Chocolate', 'Citrus', 'Floral']",Low,Medium,4,Dark,88.2,15.2,Sold Out,"['Rainforest Alliance', 'Direct Trade', 'Organic']",Onyx Coffee Lab,2024-12-16,2024-10-29,"['Pour Over', 'Espresso', 'French Press']",False,401.8,Great for espresso and pour over.
|
||||||
|
1f75f995-88dc-45cf-9c50-652ea77503a6,Single Origin Brazil,Yemen,Finca Esperanza,Juan Valdez,cadd51b2-8f05-4f8e-a8d9-0855a65a17cf,1921,Honey,October-February,"['Citrus', 'Fruity', 'Caramel']",High,Medium-Light,10,Light,83.9,26.1,Available,"['Organic', 'Direct Trade', 'Fair Trade']",Onyx Coffee Lab,2025-06-26,2024-08-20,"['Drip', 'Pour Over', 'French Press']",False,163.5,Great for espresso and pour over.
|
||||||
|
59c0cef9-aebb-4d01-bfb5-743cca07be36,Single Origin Guatemala,Jamaica,Finca Esperanza,Juan Valdez,42ff6b25-902c-471d-a3f7-89475d6b8de4,2067,Semi-washed,October-February,"['Caramel', 'Nutty', 'Spicy']",Medium-High,Light,3,Light,90.8,21.7,Seasonal,"['Fair Trade', 'Direct Trade', 'Rainforest Alliance']",Onyx Coffee Lab,2024-08-17,2025-05-27,"['Drip', 'French Press', 'Pour Over']",False,137.4,Great for espresso and pour over.
|
||||||
|
6ca36fab-cb8a-4806-8ee2-f313fd2faa28,Single Origin Kenya,Ethiopia,Finca Esperanza,Juan Valdez,acbf9271-9c75-457c-b69d-6e1eee47aec9,1352,Honey,October-February,"['Spicy', 'Nutty', 'Caramel']",High,Medium-Full,10,Medium-Light,84.4,10.0,Available,"['Rainforest Alliance', 'Direct Trade', 'Fair Trade']",Onyx Coffee Lab,2025-03-11,2024-11-10,"['Pour Over', 'French Press', 'Espresso']",True,464.6,Great for espresso and pour over.
|
||||||
|
cfeac1a5-b263-465a-b48a-ed16091db208,Single Origin Ethiopia,Costa Rica,Finca Esperanza,Juan Valdez,5669dad0-9a6f-469e-b00e-216cec82b9c9,1402,Wet-hulled,October-February,"['Citrus', 'Chocolate', 'Caramel']",Low,Medium-Full,10,Medium-Light,90.2,18.1,Sold Out,"['Organic', 'Direct Trade', 'Fair Trade']",Onyx Coffee Lab,2024-09-15,2024-11-06,"['French Press', 'Pour Over', 'Espresso']",False,243.8,Great for espresso and pour over.
|
||||||
|
c6d7471a-d323-4328-8265-8bd46f3cea65,Single Origin Kenya,Yemen,Finca Esperanza,Juan Valdez,fd9c9ed4-9c90-4606-98ec-7a12b1e6e546,1542,Washed,October-February,"['Citrus', 'Nutty', 'Floral']",Medium-High,Full,6,Dark,90.9,13.3,Limited,"['Organic', 'Direct Trade', 'Rainforest Alliance']",Onyx Coffee Lab,2025-02-19,2024-07-21,"['Pour Over', 'French Press', 'Drip']",True,210.4,Great for espresso and pour over.
|
||||||
|
ac77a9d6-4c03-421a-88cd-b443f9f0f697,Single Origin Ethiopia,Ethiopia,Finca Esperanza,Juan Valdez,ee28a942-a5d2-4234-a448-e19832b20e41,1244,Semi-washed,October-February,"['Chocolate', 'Citrus', 'Berry']",Medium,Medium-Full,7,Medium,82.0,13.5,Sold Out,"['Rainforest Alliance', 'Fair Trade', 'Organic']",Onyx Coffee Lab,2024-12-24,2024-09-27,"['French Press', 'Pour Over', 'Drip']",True,268.0,Great for espresso and pour over.
|
||||||
|
01797a60-2bdf-4107-954d-f113afe955fa,Single Origin Guatemala,Honduras,Finca Esperanza,Juan Valdez,0aa366aa-4e45-443e-99b9-dfd53b8a8c28,1492,Natural,October-February,"['Spicy', 'Nutty', 'Chocolate']",High,Medium-Full,1,Medium-Light,91.8,10.2,Limited,"['Organic', 'Direct Trade', 'Rainforest Alliance']",Onyx Coffee Lab,2025-03-17,2024-12-07,"['Espresso', 'French Press', 'Pour Over']",True,171.5,Great for espresso and pour over.
|
||||||
|
15f6b982-a0f3-40bd-986e-d2278621d200,Single Origin Costa Rica,Honduras,Finca Esperanza,Juan Valdez,aea4bf64-96f9-4f48-8c00-0fa52eb259c9,1618,Washed,October-February,"['Nutty', 'Citrus', 'Berry']",Low,Medium-Full,1,Light,88.2,12.7,Limited,"['Direct Trade', 'Organic', 'Fair Trade']",Onyx Coffee Lab,2024-11-30,2024-08-15,"['French Press', 'Espresso', 'Drip']",True,378.5,Great for espresso and pour over.
|
||||||
|
9691703e-02fa-43b9-abbe-74cf12ccfe54,Single Origin Costa Rica,Brazil,Finca Esperanza,Juan Valdez,039a57ff-5e78-4942-a29d-297da50acfa9,1228,Natural,October-February,"['Caramel', 'Floral', 'Spicy']",High,Medium,1,Medium,93.0,28.1,Limited,"['Organic', 'Fair Trade', 'Rainforest Alliance']",Onyx Coffee Lab,2025-02-10,2025-06-01,"['Espresso', 'French Press', 'Drip']",True,320.5,Great for espresso and pour over.
|
||||||
|
d51e1935-bc10-4a1f-9d51-afc99ed85400,Single Origin Ethiopia,Ethiopia,Finca Esperanza,Juan Valdez,988705f9-8b17-459a-ba75-10bedaa5176c,1519,Washed,October-February,"['Nutty', 'Floral', 'Berry']",Medium-High,Light,7,Medium-Light,85.0,26.6,Available,"['Rainforest Alliance', 'Organic', 'Fair Trade']",Onyx Coffee Lab,2025-03-18,2025-06-07,"['Pour Over', 'Espresso', 'Drip']",False,305.5,Great for espresso and pour over.
|
||||||
|
51fbe353-964b-4f59-af25-05b7f6cd6ee1,Single Origin Brazil,Guatemala,Finca Esperanza,Juan Valdez,d0b12210-ee55-4805-a2af-1826e9c00c50,1495,Semi-washed,October-February,"['Berry', 'Floral', 'Chocolate']",Medium-High,Medium,7,Medium,82.5,28.0,Sold Out,"['Direct Trade', 'Organic', 'Rainforest Alliance']",Onyx Coffee Lab,2024-11-18,2024-09-05,"['Pour Over', 'Espresso', 'Drip']",False,261.8,Great for espresso and pour over.
|
||||||
|
2a209c31-fdd3-49ca-97b0-77d92bbc8d23,Single Origin Brazil,Ethiopia,Finca Esperanza,Juan Valdez,846ec766-cbbf-449c-bfc9-275bed60d45d,1421,Wet-hulled,October-February,"['Caramel', 'Fruity', 'Floral']",High,Medium-Full,2,Light,89.2,18.6,Available,"['Direct Trade', 'Organic', 'Fair Trade']",Onyx Coffee Lab,2024-10-03,2025-01-17,"['Drip', 'Pour Over', 'Espresso']",False,67.7,Great for espresso and pour over.
|
||||||
|
162db465-308b-4b76-b120-05b616be8fcb,Single Origin Yemen,Kenya,Finca Esperanza,Juan Valdez,45b7966b-5c8e-4ff7-a4b7-206cc42388fa,1868,Natural,October-February,"['Berry', 'Spicy', 'Fruity']",High,Light,8,Light,81.6,11.2,Available,"['Organic', 'Fair Trade', 'Direct Trade']",Onyx Coffee Lab,2025-01-02,2025-05-24,"['Pour Over', 'Espresso', 'Drip']",True,151.1,Great for espresso and pour over.
|
||||||
|
46307722-bada-4b6b-93f8-cc89e55583ea,Single Origin Jamaica,Guatemala,Finca Esperanza,Juan Valdez,a6f896d8-c7e5-40d2-876d-9b711488ced2,1740,Natural,October-February,"['Spicy', 'Nutty', 'Chocolate']",Medium,Full,1,Dark,91.7,16.9,Available,"['Rainforest Alliance', 'Direct Trade', 'Fair Trade']",Onyx Coffee Lab,2024-11-17,2025-06-16,"['Espresso', 'French Press', 'Pour Over']",False,295.1,Great for espresso and pour over.
|
||||||
|
87716b81-baa4-4727-bb21-d2aca8cbc08b,Single Origin Guatemala,Kenya,Finca Esperanza,Juan Valdez,1ec6883d-857b-4a7b-8175-cff999023659,1862,Wet-hulled,October-February,"['Caramel', 'Floral', 'Chocolate']",High,Light,9,Dark,80.5,14.1,Sold Out,"['Direct Trade', 'Organic', 'Fair Trade']",Onyx Coffee Lab,2024-12-17,2025-03-25,"['Pour Over', 'Espresso', 'Drip']",True,76.3,Great for espresso and pour over.
|
||||||
|
8f52dd60-8b5c-4cce-a939-7699d6a86cff,Single Origin Kenya,Kenya,Finca Esperanza,Juan Valdez,a7cff0fc-c150-4860-ba2a-7afcc72cad2d,1803,Wet-hulled,October-February,"['Caramel', 'Berry', 'Floral']",Medium-High,Medium-Light,9,Medium-Light,80.1,11.7,Seasonal,"['Direct Trade', 'Rainforest Alliance', 'Fair Trade']",Onyx Coffee Lab,2024-10-03,2024-07-16,"['Drip', 'French Press', 'Pour Over']",False,433.5,Great for espresso and pour over.
|
||||||
|
17f15fb4-4b5d-4d9d-8344-113cfab461ee,Single Origin Jamaica,Jamaica,Finca Esperanza,Juan Valdez,72cc1c60-4137-41e0-8cf2-79dbb2102016,1788,Washed,October-February,"['Chocolate', 'Spicy', 'Nutty']",Low,Full,3,Light,82.5,16.3,Seasonal,"['Direct Trade', 'Organic', 'Fair Trade']",Onyx Coffee Lab,2025-01-27,2025-05-12,"['French Press', 'Pour Over', 'Espresso']",False,495.8,Great for espresso and pour over.
|
||||||
|
64065c4d-8d01-4a0a-b4ed-249c1b7fcad4,Single Origin Jamaica,Guatemala,Finca Esperanza,Juan Valdez,35fc6bcc-ba65-4ac7-bfa4-5518a15f14b8,1430,Semi-washed,October-February,"['Floral', 'Caramel', 'Fruity']",Medium-Low,Medium,10,Dark,81.3,28.6,Available,"['Organic', 'Rainforest Alliance', 'Direct Trade']",Onyx Coffee Lab,2025-01-02,2025-04-03,"['Pour Over', 'Drip', 'Espresso']",False,207.7,Great for espresso and pour over.
|
||||||
|
82c0f214-4a33-4f4d-a86e-beff1170a887,Single Origin Jamaica,Yemen,Finca Esperanza,Juan Valdez,11492b6f-d649-4a6f-8efd-c62b66f45258,1391,Natural,October-February,"['Floral', 'Fruity', 'Caramel']",Low,Medium,4,Medium,88.2,15.2,Limited,"['Fair Trade', 'Organic', 'Direct Trade']",Onyx Coffee Lab,2025-03-08,2025-03-22,"['Drip', 'Espresso', 'French Press']",False,257.5,Great for espresso and pour over.
|
||||||
|
f42664e3-0e7a-48b7-bba6-0ea8c63e29db,Single Origin Colombia,Panama,Finca Esperanza,Juan Valdez,1dd3c6c7-99a3-467a-8e86-05b9b57340cf,1215,Natural,October-February,"['Caramel', 'Nutty', 'Citrus']",Medium,Medium-Light,8,Medium-Dark,94.4,23.7,Available,"['Direct Trade', 'Fair Trade', 'Rainforest Alliance']",Onyx Coffee Lab,2025-04-22,2025-05-04,"['Pour Over', 'Espresso', 'Drip']",False,205.8,Great for espresso and pour over.
|
||||||
|
b7eedb8b-e4f0-4b8c-b560-4f01627016aa,Single Origin Brazil,Honduras,Finca Esperanza,Juan Valdez,205ae039-3f8c-47e8-a5d9-2e8f5e01babe,1280,Wet-hulled,October-February,"['Nutty', 'Citrus', 'Caramel']",Low,Medium-Light,1,Light,89.4,25.7,Available,"['Rainforest Alliance', 'Organic', 'Fair Trade']",Onyx Coffee Lab,2024-12-19,2024-07-07,"['Espresso', 'Pour Over', 'Drip']",False,457.9,Great for espresso and pour over.
|
||||||
|
51
lib/database/Coffee_Machines.csv
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
id,manufacturer,year,model,type,steamWand,details,isOwned,rating,popularity,portafilters,specifications
|
||||||
|
9f68fe90-a40c-4de9-bae5-cbd8350f15d9,La Marzocco,2017,Model 584,Drip,True,"Stainless steel body, user-friendly controls.",False,2.4,100,"[{'id': '2369181c-df26-4b1e-98cb-e2215b0a5871', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
96fbb2fc-c139-43b8-b285-6d8c70408696,Rocket,2021,Model 811,E61,False,"Stainless steel body, user-friendly controls.",False,3.0,100,"[{'id': '9135f83b-138a-45be-b56e-b029f9658434', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
07657e06-2b49-4c53-9d8e-5c0298eed45a,Gaggia,2016,Model 425,E61,True,"Stainless steel body, user-friendly controls.",False,3.4,45,"[{'id': '933851e9-3147-4e86-a5bb-111398063395', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
4e1be4f7-7611-4585-bd86-cab2736722a1,Rocket,2020,Model 922,Espresso Pod,False,"Stainless steel body, user-friendly controls.",True,4.4,16,"[{'id': 'c149dc17-e931-4142-8043-3629a191b767', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
da5ce31d-1b34-4747-bc7f-20d8a7a266b3,Rancilio,2022,Model 877,French Press,True,"Stainless steel body, user-friendly controls.",False,4.7,76,"[{'id': '5ace346d-8a77-4e77-ad39-84095472d306', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
31aed043-2d87-40f0-a824-90bc863e49b4,Gaggia,2020,Model 971,Espresso Pod,False,"Stainless steel body, user-friendly controls.",False,2.6,48,"[{'id': '3ea2bb4f-56b8-4e98-8ede-e75ab7e8f56a', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
7e933369-4160-4c10-bfdd-ab2624dac1d0,La Marzocco,2020,Model 941,Pod,False,"Stainless steel body, user-friendly controls.",False,4.5,45,"[{'id': 'f83cffe2-11f8-44b5-84cb-e52f916b73c8', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
06051aee-61bf-4a59-97db-e4799beef357,Gaggia,2019,Model 901,Grinder,False,"Stainless steel body, user-friendly controls.",True,3.2,75,"[{'id': 'f9c154cd-2d25-4134-a3ea-425cf8851dfe', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
3f394f15-fef5-41d7-8b0a-8c6780be5a69,Rocket,2020,Model 195,Grinder,False,"Stainless steel body, user-friendly controls.",False,2.8,30,"[{'id': 'ba198010-6987-47d0-be75-550f979ab542', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
f7a28b47-4484-47e9-b55b-3040edb53b6c,DeLonghi,2022,Model 147,Espresso,False,"Stainless steel body, user-friendly controls.",False,2.9,48,"[{'id': '78fc9726-b016-4510-9d1b-d5f20c7137a3', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
272055c6-42bf-447b-bf3b-fc49517a1a9f,Rocket,2021,Model 614,Espresso Pod,True,"Stainless steel body, user-friendly controls.",True,1.5,69,"[{'id': '9272d327-1c7c-4215-9d48-4fd04cecd137', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
2495a0e6-4cd9-43e1-8e73-a6a058de9177,Gaggia,2022,Model 294,Cold Brew,False,"Stainless steel body, user-friendly controls.",False,1.5,19,"[{'id': 'b119011f-d54c-46b3-b818-7427e1ab03e8', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
f19bd621-fbf9-4397-a94f-7e0568bf3b73,La Marzocco,2015,Model 415,Grinder,False,"Stainless steel body, user-friendly controls.",False,3.0,54,"[{'id': 'edc74dab-9502-42a3-a5b2-92bea4d2dfef', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
c0efb75b-c072-4dc4-9b67-01c06ef5e7a5,DeLonghi,2018,Model 632,Drip,False,"Stainless steel body, user-friendly controls.",False,1.3,41,"[{'id': 'bc216970-410e-47c6-ad2f-bb1422d3f9ec', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
ecc282ec-2857-48a7-bb0b-cd3eb3990f20,Breville,2018,Model 606,Cold Brew,False,"Stainless steel body, user-friendly controls.",False,2.9,46,"[{'id': 'c60bdad5-d5d0-4abb-b34c-ace1abb868a5', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
145eead8-b9c4-4d2e-a031-e7c488525eaa,Breville,2020,Model 180,Grinder,True,"Stainless steel body, user-friendly controls.",True,4.3,28,"[{'id': 'fb42d62e-2310-4c80-a721-5e2ebbf97ec2', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
b9a9f96c-1ba2-4bff-8de1-9230bc467d43,Rancilio,2016,Model 414,Drip,False,"Stainless steel body, user-friendly controls.",False,1.3,87,"[{'id': 'ee9cf949-b7fa-4afd-908a-e9e2167a718e', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
fa4b4ade-4fab-4428-b392-87fc331b86d0,Breville,2017,Model 794,Espresso,True,"Stainless steel body, user-friendly controls.",False,4.9,13,"[{'id': '2a9da80b-e6db-44cd-86ab-6d92ea3fc99b', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
4e1d352e-677d-4578-8adb-44c92245bcfb,DeLonghi,2021,Model 349,Percolation,False,"Stainless steel body, user-friendly controls.",True,4.4,57,"[{'id': '925d6f92-2455-41c1-8c1c-46c4112bcffc', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
eb5362c0-7131-4731-b4b7-e0f3df3c26b9,Breville,2022,Model 914,Espresso Pod,False,"Stainless steel body, user-friendly controls.",False,1.3,6,"[{'id': 'a22e10b8-4ec2-47ed-9702-2528f9fb803a', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
2d0bbbf5-7457-40f3-9836-4b81db57ed74,Rancilio,2019,Model 162,Percolation,False,"Stainless steel body, user-friendly controls.",False,1.0,66,"[{'id': '798a042e-3926-4f9d-b38c-511644be3a01', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
949bd3b3-599e-4f65-90db-d41e76556c46,Breville,2021,Model 195,Cold Brew,False,"Stainless steel body, user-friendly controls.",True,2.6,77,"[{'id': 'ba057949-6dbe-486f-ab42-1e073e5e5d76', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
f553e7e7-26c5-4032-b21c-40443892ca4f,Gaggia,2023,Model 706,Grinder,True,"Stainless steel body, user-friendly controls.",True,2.1,30,"[{'id': '1e47fae0-76e1-46c4-b8d7-f867cfb97330', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
1ea08a62-97aa-47be-8ad1-1d97d21daaec,DeLonghi,2022,Model 780,French Press,False,"Stainless steel body, user-friendly controls.",True,4.3,98,"[{'id': '54b912e2-479e-4d48-be3c-1b2b849b5ea0', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
7ec14dd4-a4eb-400b-9c1c-de6eb9b75a45,Gaggia,2022,Model 941,Percolation,False,"Stainless steel body, user-friendly controls.",False,4.3,99,"[{'id': '2ceb3f6a-1c94-4eca-a1a1-ad89993a4699', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
f911ef99-f96e-44c0-8551-0b176e78423e,Breville,2019,Model 574,Cold Brew,False,"Stainless steel body, user-friendly controls.",True,4.6,92,"[{'id': 'ac7d283f-d88e-49d0-a08c-52f176470cb1', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
01525f66-e9da-46ab-ae12-4d2e40b26cf1,Gaggia,2024,Model 286,Pod,False,"Stainless steel body, user-friendly controls.",False,1.9,5,"[{'id': 'bec2434e-f646-4de9-be3a-a40d9f5501e2', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
5ff13ae7-9591-407a-a0fb-cdcd0cc5127d,Rocket,2020,Model 264,French Press,False,"Stainless steel body, user-friendly controls.",False,1.9,95,"[{'id': '5ed0348c-a8d5-49ab-9a77-2576f193fd1b', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
8cffb75c-dd8c-43e5-94d9-27b311ca41db,Rancilio,2021,Model 286,Grinder,True,"Stainless steel body, user-friendly controls.",True,1.6,26,"[{'id': 'ba6aa266-d108-4a2f-af8e-04d743c30b92', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
61ec0959-f657-4dfc-9a8d-12a2e8ac9cfe,Gaggia,2016,Model 905,Percolation,False,"Stainless steel body, user-friendly controls.",True,2.3,25,"[{'id': 'f9b69c15-4a32-4326-979a-bc0b4ba08241', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
9ee2b394-7111-4972-905d-eceebeea0b8f,La Marzocco,2019,Model 777,Cold Brew,False,"Stainless steel body, user-friendly controls.",False,4.1,7,"[{'id': '6fb7b281-d587-4b89-b23d-7b38b0afae4e', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
38fe46de-0197-4792-8f3d-d54f555dc919,Rancilio,2016,Model 984,Espresso Pod,False,"Stainless steel body, user-friendly controls.",False,1.4,83,"[{'id': '1f3a9f0b-0d4b-4ddd-b4cb-c077c793be9a', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
ef219dde-f302-47db-9e06-7ee91921d7cf,La Marzocco,2024,Model 713,Espresso Pod,False,"Stainless steel body, user-friendly controls.",False,4.7,25,"[{'id': 'be456aa4-1592-4ed4-8617-dc75b1df8568', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
8ea4811c-8af1-4a3f-bf0a-997f21ed9243,La Marzocco,2024,Model 847,Drip,False,"Stainless steel body, user-friendly controls.",True,2.6,42,"[{'id': '57caf05f-4286-4c69-bbe3-f09a8e20aede', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
a97e27b5-d343-4b17-b7c9-2ac034610e55,Gaggia,2018,Model 121,E61,False,"Stainless steel body, user-friendly controls.",False,3.2,77,"[{'id': 'cf9f4f4d-821e-43a3-b2b7-4d767966c4c9', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
060090b0-90b3-4ce2-bad3-e5b7ef8f3a72,Rocket,2024,Model 918,Cold Brew,False,"Stainless steel body, user-friendly controls.",True,1.3,41,"[{'id': '48f69361-2c66-4c1f-8fa4-c5cd55ceb391', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
f01d9d29-3b15-4be1-8ec5-5cb6b6ae96a3,DeLonghi,2024,Model 268,Pod,False,"Stainless steel body, user-friendly controls.",True,4.4,13,"[{'id': '829f8d62-73f0-4299-b27f-41d3f752ecf7', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
7f519fd6-c0ba-4436-8a54-ca7b969686e6,Gaggia,2022,Model 727,Espresso Pod,True,"Stainless steel body, user-friendly controls.",True,4.9,87,"[{'id': '1feebc79-674d-4686-af82-492c8a208570', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
399f934a-8318-4532-a2ca-d9d1db9a5b45,Rocket,2019,Model 404,Percolation,True,"Stainless steel body, user-friendly controls.",False,4.2,89,"[{'id': 'e5bae35b-7bbf-4c25-8755-fcfdcde641e5', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
3d55b160-1a94-47bf-9089-922f1d7b3ae5,La Marzocco,2016,Model 862,Espresso,True,"Stainless steel body, user-friendly controls.",False,1.4,33,"[{'id': '36965d5e-7eac-4874-8fb5-2a0ba7f01c2f', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
67a4b7f7-3490-4a0b-9ded-8d0141ac0a1b,Rocket,2021,Model 442,Espresso,True,"Stainless steel body, user-friendly controls.",True,3.7,88,"[{'id': '484d395b-0fb2-46ea-9a8d-08dcb082d66e', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
7f349782-0cef-436a-b6ce-75d23c32e52f,Rocket,2018,Model 919,Cold Brew,True,"Stainless steel body, user-friendly controls.",True,4.6,71,"[{'id': '217922b0-d25a-4724-b89f-ef9c8be13917', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
45c797fe-411f-46bc-be27-68efcef94699,Breville,2019,Model 228,Pod,True,"Stainless steel body, user-friendly controls.",True,4.6,16,"[{'id': '871068f2-f516-4a41-899d-d38aed9753cf', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
ad6521e3-c588-4b77-9a68-4d42e9c3f40f,DeLonghi,2018,Model 801,Percolation,True,"Stainless steel body, user-friendly controls.",True,4.4,48,"[{'id': 'ded0a526-283f-4e9e-903d-0d4525ec74c8', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
1403c4e9-fe0b-4980-a0a0-399cb0e643bd,DeLonghi,2023,Model 583,Cold Brew,False,"Stainless steel body, user-friendly controls.",True,2.8,19,"[{'id': '74d185af-87cb-463b-8414-727b28721fb6', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
33d49b46-81e5-45ae-958c-bfc58ca7304f,Rocket,2024,Model 204,French Press,False,"Stainless steel body, user-friendly controls.",True,2.9,64,"[{'id': 'efdebdd5-46e4-4133-aa15-b706a2526c23', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
986f82bd-7999-42f9-9600-b3e24f038c3a,Breville,2021,Model 780,Espresso Pod,True,"Stainless steel body, user-friendly controls.",False,4.1,94,"[{'id': '666192ba-4003-421a-84df-60e31d5c37aa', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
b042f05d-1fb6-42aa-8792-9fe3f2c3465e,Rancilio,2016,Model 935,French Press,True,"Stainless steel body, user-friendly controls.",False,2.2,81,"[{'id': '37e24303-8f02-4571-b9b3-96435bb02204', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
ec7eac0e-799f-49e9-a2e4-e2162e22960f,Rocket,2024,Model 211,Cold Brew,True,"Stainless steel body, user-friendly controls.",True,1.6,8,"[{'id': '8a8856e7-5c6b-450e-9d11-7414b12cf739', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
8a17a266-916f-4b41-bcb0-74b7e5ecfeab,Breville,2020,Model 428,Cold Brew,False,"Stainless steel body, user-friendly controls.",True,3.1,0,"[{'id': '597a5c4c-2d59-4e98-8304-07cc56f81801', 'size': '58mm', 'material': 'Stainless Steel'}]","{'pressure': '15 bar', 'waterTank': '1.8L', 'brewingTime': '25-30 seconds', 'recommendedGrind': 'Fine', 'maintenance': 'Monthly descaling', 'priceRange': '$300-$500', 'bestFor': 'Home espresso enthusiasts'}"
|
||||||
|
51
lib/database/Origin_Countries.csv
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
id,name,continent,regions,altitudeRange,harvestSeason,commonVarietals,processingMethods,flavorProfile,characteristics,climate,soilType,rating,coffeeCulture,exportVolume,mainPorts,certifications,averagePrice,seasonality
|
||||||
|
44150328-5ad7-4116-bfaa-8aa2463933e5,Yemen,Central America,"['Kirinyaga', 'Huila', 'Kayanza']",1200-2000m,October-March,"['Maragogipe', 'Typica', 'SL28']","['Washed', 'Wet-hulled', 'Honey']","['Berry', 'Caramel', 'Spicy']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.9,Coffee is an integral part of daily life and traditions.,57305,"['Mombasa', 'Addis Ababa', 'Panama City']","['Direct Trade', 'Organic', 'Rainforest Alliance']",4.9,"{'plantingMonths': ['March', 'May', 'April'], 'harvestMonths': ['December', 'October', 'November'], 'dryingMonths': ['December', 'November', 'January']}"
|
||||||
|
9ae3c3a9-7345-493a-b158-03df4aa4deae,Jamaica,South America,"['Antioquia', 'Yirgacheffe', 'Kirinyaga']",1200-2000m,October-March,"['Bourbon', 'Maragogipe', 'SL34']","['Washed', 'Natural', 'Honey']","['Spicy', 'Fruity', 'Berry']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.8,Coffee is an integral part of daily life and traditions.,19150,"['Mombasa', 'Cartagena', 'Santos']","['Fair Trade', 'Organic', 'Rainforest Alliance']",5.8,"{'plantingMonths': ['March', 'May', 'April'], 'harvestMonths': ['November', 'December', 'October'], 'dryingMonths': ['December', 'November', 'January']}"
|
||||||
|
5e8438e0-d78c-4022-9e91-21443853be3b,Guatemala,Asia,"['Kirinyaga', 'Boquete', 'Yirgacheffe']",1200-2000m,October-March,"['Geisha', 'Kent', 'SL28']","['Washed', 'Natural', 'Honey']","['Chocolate', 'Berry', 'Nutty']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.5,Coffee is an integral part of daily life and traditions.,99700,"['Cartagena', 'Santos', 'Addis Ababa']","['Rainforest Alliance', 'Fair Trade', 'Direct Trade']",7.7,"{'plantingMonths': ['May', 'March', 'April'], 'harvestMonths': ['October', 'November', 'December'], 'dryingMonths': ['January', 'December', 'November']}"
|
||||||
|
b4dbdfa5-1ab7-4f08-86c9-fd1cdde6e885,Costa Rica,Asia,"['Yirgacheffe', 'Boquete', 'Nyeri']",1200-2000m,October-March,"['Kent', 'Typica', 'Pacamara']","['Wet-hulled', 'Semi-washed', 'Washed']","['Citrus', 'Chocolate', 'Floral']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.6,Coffee is an integral part of daily life and traditions.,71638,"['Mombasa', 'Cartagena', 'Panama City']","['Rainforest Alliance', 'Organic', 'Fair Trade']",5.5,"{'plantingMonths': ['May', 'April', 'March'], 'harvestMonths': ['October', 'December', 'November'], 'dryingMonths': ['November', 'January', 'December']}"
|
||||||
|
1389e222-3439-4625-a455-c14bd011a8de,Guatemala,Africa,"['Boquete', 'Kayanza', 'Yirgacheffe']",1200-2000m,October-March,"['Pacamara', 'Maragogipe', 'Typica']","['Semi-washed', 'Wet-hulled', 'Natural']","['Caramel', 'Fruity', 'Berry']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.8,Coffee is an integral part of daily life and traditions.,45482,"['Santos', 'Addis Ababa', 'Mombasa']","['Fair Trade', 'Direct Trade', 'Rainforest Alliance']",7.4,"{'plantingMonths': ['March', 'May', 'April'], 'harvestMonths': ['October', 'December', 'November'], 'dryingMonths': ['January', 'November', 'December']}"
|
||||||
|
725f3c8a-ec1d-436f-bfd2-ce622d9decb0,Jamaica,Asia,"['Yirgacheffe', 'Antioquia', 'Kirinyaga']",1200-2000m,October-March,"['Bourbon', 'Catuai', 'Maragogipe']","['Natural', 'Semi-washed', 'Washed']","['Floral', 'Chocolate', 'Spicy']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.9,Coffee is an integral part of daily life and traditions.,96008,"['Mombasa', 'Santos', 'Cartagena']","['Rainforest Alliance', 'Organic', 'Direct Trade']",4.6,"{'plantingMonths': ['May', 'March', 'April'], 'harvestMonths': ['December', 'November', 'October'], 'dryingMonths': ['January', 'November', 'December']}"
|
||||||
|
d747e836-73d6-4fb5-bd74-9711f1953a9c,Colombia,South America,"['Huila', 'Nyeri', 'Kayanza']",1200-2000m,October-March,"['Catuai', 'SL34', 'Kent']","['Semi-washed', 'Natural', 'Washed']","['Fruity', 'Floral', 'Berry']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.4,Coffee is an integral part of daily life and traditions.,57003,"['Cartagena', 'Addis Ababa', 'Mombasa']","['Direct Trade', 'Organic', 'Fair Trade']",5.3,"{'plantingMonths': ['March', 'May', 'April'], 'harvestMonths': ['November', 'October', 'December'], 'dryingMonths': ['December', 'January', 'November']}"
|
||||||
|
db3c3b15-6309-46b3-bf77-a8a9b1cb49a4,Colombia,Africa,"['Kirinyaga', 'Nyeri', 'Kayanza']",1200-2000m,October-March,"['SL28', 'Bourbon', 'Maragogipe']","['Honey', 'Wet-hulled', 'Washed']","['Berry', 'Floral', 'Chocolate']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.4,Coffee is an integral part of daily life and traditions.,43070,"['Mombasa', 'Santos', 'Cartagena']","['Rainforest Alliance', 'Organic', 'Fair Trade']",6.4,"{'plantingMonths': ['April', 'May', 'March'], 'harvestMonths': ['October', 'December', 'November'], 'dryingMonths': ['December', 'November', 'January']}"
|
||||||
|
1343dce5-0860-4474-89bf-3dec40b22354,Honduras,Africa,"['Kirinyaga', 'Yirgacheffe', 'Kayanza']",1200-2000m,October-March,"['Maragogipe', 'SL34', 'Catuai']","['Semi-washed', 'Wet-hulled', 'Washed']","['Citrus', 'Chocolate', 'Floral']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.8,Coffee is an integral part of daily life and traditions.,60891,"['Panama City', 'Santos', 'Mombasa']","['Fair Trade', 'Direct Trade', 'Rainforest Alliance']",8.0,"{'plantingMonths': ['May', 'April', 'March'], 'harvestMonths': ['November', 'October', 'December'], 'dryingMonths': ['December', 'January', 'November']}"
|
||||||
|
1b018f62-74ee-4c7b-8ea9-c7722fedd0ef,Costa Rica,Africa,"['Antioquia', 'Kayanza', 'Nyeri']",1200-2000m,October-March,"['Pacamara', 'Geisha', 'Caturra']","['Semi-washed', 'Honey', 'Natural']","['Fruity', 'Citrus', 'Caramel']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.8,Coffee is an integral part of daily life and traditions.,17070,"['Mombasa', 'Addis Ababa', 'Santos']","['Direct Trade', 'Organic', 'Rainforest Alliance']",5.8,"{'plantingMonths': ['March', 'April', 'May'], 'harvestMonths': ['October', 'December', 'November'], 'dryingMonths': ['December', 'January', 'November']}"
|
||||||
|
a929fae6-c9fd-4ad1-89e6-0ecb0a75a251,Kenya,Central America,"['Antioquia', 'Kayanza', 'Sidamo']",1200-2000m,October-March,"['Typica', 'Geisha', 'Kent']","['Semi-washed', 'Washed', 'Honey']","['Citrus', 'Nutty', 'Chocolate']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.9,Coffee is an integral part of daily life and traditions.,62282,"['Cartagena', 'Santos', 'Addis Ababa']","['Organic', 'Direct Trade', 'Fair Trade']",4.5,"{'plantingMonths': ['April', 'March', 'May'], 'harvestMonths': ['October', 'December', 'November'], 'dryingMonths': ['November', 'December', 'January']}"
|
||||||
|
df407b18-90af-49d0-947a-802f32af361a,Brazil,Asia,"['Boquete', 'Tarrazú', 'Huila']",1200-2000m,October-March,"['Caturra', 'Kent', 'Geisha']","['Wet-hulled', 'Honey', 'Natural']","['Citrus', 'Spicy', 'Berry']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.8,Coffee is an integral part of daily life and traditions.,53054,"['Mombasa', 'Addis Ababa', 'Cartagena']","['Rainforest Alliance', 'Direct Trade', 'Organic']",8.0,"{'plantingMonths': ['May', 'April', 'March'], 'harvestMonths': ['November', 'December', 'October'], 'dryingMonths': ['November', 'December', 'January']}"
|
||||||
|
5b5a6051-a221-49e7-810b-a6860a634a34,Colombia,Asia,"['Antioquia', 'Huila', 'Nyeri']",1200-2000m,October-March,"['Maragogipe', 'Catuai', 'Pacamara']","['Washed', 'Wet-hulled', 'Natural']","['Fruity', 'Citrus', 'Floral']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.1,Coffee is an integral part of daily life and traditions.,98186,"['Addis Ababa', 'Cartagena', 'Mombasa']","['Fair Trade', 'Organic', 'Rainforest Alliance']",5.6,"{'plantingMonths': ['May', 'March', 'April'], 'harvestMonths': ['November', 'October', 'December'], 'dryingMonths': ['November', 'December', 'January']}"
|
||||||
|
18a5fd34-3b58-4467-9b59-30cf77fb9ea3,Ethiopia,Asia,"['Huila', 'Kirinyaga', 'Antioquia']",1200-2000m,October-March,"['Caturra', 'Typica', 'SL34']","['Natural', 'Washed', 'Semi-washed']","['Caramel', 'Chocolate', 'Nutty']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.5,Coffee is an integral part of daily life and traditions.,28246,"['Panama City', 'Addis Ababa', 'Santos']","['Direct Trade', 'Fair Trade', 'Rainforest Alliance']",7.6,"{'plantingMonths': ['April', 'March', 'May'], 'harvestMonths': ['December', 'October', 'November'], 'dryingMonths': ['January', 'December', 'November']}"
|
||||||
|
d19c986c-09b3-4dca-bd71-4ae6f22ec909,Honduras,Asia,"['Kirinyaga', 'Huila', 'Sidamo']",1200-2000m,October-March,"['Maragogipe', 'SL34', 'Typica']","['Washed', 'Wet-hulled', 'Semi-washed']","['Floral', 'Caramel', 'Nutty']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.6,Coffee is an integral part of daily life and traditions.,36994,"['Santos', 'Panama City', 'Cartagena']","['Rainforest Alliance', 'Organic', 'Direct Trade']",7.4,"{'plantingMonths': ['March', 'May', 'April'], 'harvestMonths': ['November', 'December', 'October'], 'dryingMonths': ['December', 'November', 'January']}"
|
||||||
|
6a5e4866-2f70-4e31-a653-47bda0a573cd,Brazil,South America,"['Nyeri', 'Boquete', 'Huila']",1200-2000m,October-March,"['Typica', 'SL28', 'Bourbon']","['Wet-hulled', 'Washed', 'Semi-washed']","['Citrus', 'Nutty', 'Spicy']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.7,Coffee is an integral part of daily life and traditions.,34082,"['Panama City', 'Santos', 'Addis Ababa']","['Fair Trade', 'Organic', 'Direct Trade']",4.7,"{'plantingMonths': ['March', 'May', 'April'], 'harvestMonths': ['October', 'December', 'November'], 'dryingMonths': ['January', 'December', 'November']}"
|
||||||
|
43e1ff98-7f8a-4113-b245-71da7966d03d,Honduras,South America,"['Tarrazú', 'Kayanza', 'Antioquia']",1200-2000m,October-March,"['Bourbon', 'Geisha', 'Kent']","['Honey', 'Wet-hulled', 'Washed']","['Nutty', 'Caramel', 'Spicy']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.3,Coffee is an integral part of daily life and traditions.,16337,"['Mombasa', 'Panama City', 'Addis Ababa']","['Rainforest Alliance', 'Organic', 'Direct Trade']",6.8,"{'plantingMonths': ['April', 'March', 'May'], 'harvestMonths': ['October', 'December', 'November'], 'dryingMonths': ['January', 'November', 'December']}"
|
||||||
|
81118dca-bf6f-4588-8975-190f1332bdb0,Ethiopia,South America,"['Tarrazú', 'Boquete', 'Kirinyaga']",1200-2000m,October-March,"['Bourbon', 'Typica', 'Caturra']","['Washed', 'Wet-hulled', 'Semi-washed']","['Caramel', 'Chocolate', 'Fruity']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.3,Coffee is an integral part of daily life and traditions.,48420,"['Cartagena', 'Mombasa', 'Addis Ababa']","['Direct Trade', 'Rainforest Alliance', 'Fair Trade']",7.5,"{'plantingMonths': ['April', 'March', 'May'], 'harvestMonths': ['October', 'November', 'December'], 'dryingMonths': ['December', 'January', 'November']}"
|
||||||
|
c2dece47-eab5-4f5d-8ce7-4e53804a9cbe,Brazil,Asia,"['Huila', 'Kayanza', 'Nyeri']",1200-2000m,October-March,"['Maragogipe', 'Bourbon', 'Caturra']","['Honey', 'Washed', 'Wet-hulled']","['Chocolate', 'Citrus', 'Nutty']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.2,Coffee is an integral part of daily life and traditions.,96436,"['Mombasa', 'Panama City', 'Addis Ababa']","['Organic', 'Rainforest Alliance', 'Fair Trade']",5.8,"{'plantingMonths': ['May', 'April', 'March'], 'harvestMonths': ['December', 'October', 'November'], 'dryingMonths': ['January', 'December', 'November']}"
|
||||||
|
7ca93f0a-e3f9-4223-8e20-ad1ad519d3ee,Yemen,Africa,"['Yirgacheffe', 'Huila', 'Sidamo']",1200-2000m,October-March,"['SL34', 'Maragogipe', 'Geisha']","['Natural', 'Washed', 'Wet-hulled']","['Citrus', 'Chocolate', 'Nutty']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.8,Coffee is an integral part of daily life and traditions.,69750,"['Santos', 'Addis Ababa', 'Panama City']","['Organic', 'Rainforest Alliance', 'Fair Trade']",4.6,"{'plantingMonths': ['March', 'April', 'May'], 'harvestMonths': ['November', 'October', 'December'], 'dryingMonths': ['January', 'November', 'December']}"
|
||||||
|
d1d64f50-2891-4d86-ae5a-ea4b97cef4cb,Guatemala,Central America,"['Sidamo', 'Antioquia', 'Nyeri']",1200-2000m,October-March,"['Caturra', 'Maragogipe', 'SL28']","['Semi-washed', 'Washed', 'Wet-hulled']","['Caramel', 'Nutty', 'Chocolate']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.0,Coffee is an integral part of daily life and traditions.,92702,"['Mombasa', 'Addis Ababa', 'Panama City']","['Direct Trade', 'Rainforest Alliance', 'Organic']",6.2,"{'plantingMonths': ['May', 'April', 'March'], 'harvestMonths': ['October', 'November', 'December'], 'dryingMonths': ['November', 'January', 'December']}"
|
||||||
|
5ab2e55a-de85-4fe0-a9fd-982cec0242d1,Jamaica,Central America,"['Kayanza', 'Kirinyaga', 'Yirgacheffe']",1200-2000m,October-March,"['Typica', 'SL28', 'Kent']","['Wet-hulled', 'Semi-washed', 'Honey']","['Chocolate', 'Caramel', 'Floral']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.9,Coffee is an integral part of daily life and traditions.,57720,"['Cartagena', 'Panama City', 'Mombasa']","['Organic', 'Direct Trade', 'Rainforest Alliance']",4.9,"{'plantingMonths': ['March', 'April', 'May'], 'harvestMonths': ['November', 'October', 'December'], 'dryingMonths': ['December', 'November', 'January']}"
|
||||||
|
1f9bd1f5-e64d-417e-b12e-c39fbe0814f9,Colombia,Central America,"['Nyeri', 'Yirgacheffe', 'Kayanza']",1200-2000m,October-March,"['Maragogipe', 'Caturra', 'Pacamara']","['Honey', 'Washed', 'Natural']","['Fruity', 'Caramel', 'Chocolate']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.5,Coffee is an integral part of daily life and traditions.,48630,"['Addis Ababa', 'Cartagena', 'Mombasa']","['Fair Trade', 'Direct Trade', 'Organic']",4.1,"{'plantingMonths': ['March', 'May', 'April'], 'harvestMonths': ['November', 'December', 'October'], 'dryingMonths': ['January', 'November', 'December']}"
|
||||||
|
ed53fba1-e84c-4efc-a696-836703fe9d29,Guatemala,Asia,"['Kirinyaga', 'Kayanza', 'Huila']",1200-2000m,October-March,"['Catuai', 'SL28', 'Caturra']","['Natural', 'Wet-hulled', 'Washed']","['Nutty', 'Berry', 'Caramel']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.9,Coffee is an integral part of daily life and traditions.,11272,"['Cartagena', 'Mombasa', 'Panama City']","['Direct Trade', 'Organic', 'Rainforest Alliance']",6.1,"{'plantingMonths': ['May', 'March', 'April'], 'harvestMonths': ['November', 'October', 'December'], 'dryingMonths': ['January', 'December', 'November']}"
|
||||||
|
bff16d2b-43b9-40e8-b595-cc3da9b0d71f,Brazil,Africa,"['Antioquia', 'Kirinyaga', 'Yirgacheffe']",1200-2000m,October-March,"['Kent', 'Pacamara', 'SL34']","['Natural', 'Honey', 'Washed']","['Spicy', 'Chocolate', 'Floral']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.6,Coffee is an integral part of daily life and traditions.,11776,"['Santos', 'Mombasa', 'Cartagena']","['Direct Trade', 'Fair Trade', 'Organic']",4.4,"{'plantingMonths': ['April', 'March', 'May'], 'harvestMonths': ['October', 'November', 'December'], 'dryingMonths': ['December', 'January', 'November']}"
|
||||||
|
0d4be036-e6ff-4735-b2f6-22b8b3c16511,Costa Rica,Central America,"['Yirgacheffe', 'Kayanza', 'Boquete']",1200-2000m,October-March,"['Typica', 'Kent', 'Caturra']","['Natural', 'Honey', 'Semi-washed']","['Fruity', 'Floral', 'Berry']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.0,Coffee is an integral part of daily life and traditions.,16623,"['Santos', 'Mombasa', 'Cartagena']","['Rainforest Alliance', 'Organic', 'Fair Trade']",7.2,"{'plantingMonths': ['March', 'April', 'May'], 'harvestMonths': ['November', 'October', 'December'], 'dryingMonths': ['December', 'November', 'January']}"
|
||||||
|
8874922e-2425-4053-a794-e01010c5fe88,Ethiopia,South America,"['Kirinyaga', 'Tarrazú', 'Nyeri']",1200-2000m,October-March,"['Geisha', 'Catuai', 'Caturra']","['Honey', 'Washed', 'Wet-hulled']","['Citrus', 'Caramel', 'Berry']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.4,Coffee is an integral part of daily life and traditions.,76738,"['Santos', 'Panama City', 'Cartagena']","['Fair Trade', 'Rainforest Alliance', 'Direct Trade']",6.7,"{'plantingMonths': ['March', 'April', 'May'], 'harvestMonths': ['October', 'November', 'December'], 'dryingMonths': ['November', 'December', 'January']}"
|
||||||
|
2468ffa6-b166-4e5c-8e12-9da13013bce9,Panama,South America,"['Kirinyaga', 'Boquete', 'Nyeri']",1200-2000m,October-March,"['SL34', 'Kent', 'SL28']","['Natural', 'Washed', 'Honey']","['Fruity', 'Citrus', 'Floral']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.0,Coffee is an integral part of daily life and traditions.,69747,"['Cartagena', 'Panama City', 'Santos']","['Fair Trade', 'Organic', 'Rainforest Alliance']",6.6,"{'plantingMonths': ['May', 'April', 'March'], 'harvestMonths': ['October', 'November', 'December'], 'dryingMonths': ['December', 'November', 'January']}"
|
||||||
|
60b8c45c-8651-4107-9541-d797063e3985,Panama,Central America,"['Kirinyaga', 'Boquete', 'Antioquia']",1200-2000m,October-March,"['SL34', 'Typica', 'Pacamara']","['Natural', 'Washed', 'Wet-hulled']","['Berry', 'Caramel', 'Floral']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.6,Coffee is an integral part of daily life and traditions.,99107,"['Addis Ababa', 'Mombasa', 'Panama City']","['Fair Trade', 'Organic', 'Rainforest Alliance']",6.9,"{'plantingMonths': ['March', 'April', 'May'], 'harvestMonths': ['November', 'October', 'December'], 'dryingMonths': ['November', 'January', 'December']}"
|
||||||
|
a5ab6483-b8ca-4e5b-8bc6-5d7b5bdf3430,Brazil,South America,"['Tarrazú', 'Boquete', 'Kirinyaga']",1200-2000m,October-March,"['Caturra', 'Maragogipe', 'Bourbon']","['Natural', 'Honey', 'Wet-hulled']","['Citrus', 'Floral', 'Spicy']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.5,Coffee is an integral part of daily life and traditions.,94418,"['Santos', 'Addis Ababa', 'Cartagena']","['Direct Trade', 'Fair Trade', 'Rainforest Alliance']",7.8,"{'plantingMonths': ['April', 'May', 'March'], 'harvestMonths': ['November', 'December', 'October'], 'dryingMonths': ['December', 'January', 'November']}"
|
||||||
|
7b0f4169-7275-40e9-98f2-ebf80de6c5f6,Panama,South America,"['Huila', 'Sidamo', 'Tarrazú']",1200-2000m,October-March,"['Catuai', 'Caturra', 'Geisha']","['Natural', 'Honey', 'Semi-washed']","['Spicy', 'Floral', 'Berry']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.9,Coffee is an integral part of daily life and traditions.,37283,"['Panama City', 'Mombasa', 'Cartagena']","['Direct Trade', 'Organic', 'Fair Trade']",6.7,"{'plantingMonths': ['March', 'April', 'May'], 'harvestMonths': ['December', 'October', 'November'], 'dryingMonths': ['November', 'January', 'December']}"
|
||||||
|
1790eceb-c9fa-4669-ae95-a95a46edf5df,Costa Rica,Africa,"['Yirgacheffe', 'Tarrazú', 'Huila']",1200-2000m,October-March,"['Geisha', 'Catuai', 'Kent']","['Washed', 'Natural', 'Semi-washed']","['Chocolate', 'Spicy', 'Fruity']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,5.0,Coffee is an integral part of daily life and traditions.,38599,"['Santos', 'Addis Ababa', 'Mombasa']","['Rainforest Alliance', 'Direct Trade', 'Organic']",7.9,"{'plantingMonths': ['May', 'March', 'April'], 'harvestMonths': ['October', 'November', 'December'], 'dryingMonths': ['December', 'January', 'November']}"
|
||||||
|
5537b852-1013-4527-aa60-11928c9d306b,Colombia,Central America,"['Nyeri', 'Yirgacheffe', 'Kirinyaga']",1200-2000m,October-March,"['Maragogipe', 'Geisha', 'SL34']","['Honey', 'Washed', 'Natural']","['Caramel', 'Berry', 'Nutty']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.8,Coffee is an integral part of daily life and traditions.,62416,"['Cartagena', 'Panama City', 'Addis Ababa']","['Fair Trade', 'Direct Trade', 'Organic']",7.3,"{'plantingMonths': ['April', 'May', 'March'], 'harvestMonths': ['October', 'November', 'December'], 'dryingMonths': ['December', 'January', 'November']}"
|
||||||
|
fd24ecae-d115-412c-8883-31e80681d419,Costa Rica,Central America,"['Huila', 'Yirgacheffe', 'Nyeri']",1200-2000m,October-March,"['Bourbon', 'Maragogipe', 'Catuai']","['Natural', 'Wet-hulled', 'Honey']","['Chocolate', 'Nutty', 'Citrus']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.7,Coffee is an integral part of daily life and traditions.,27006,"['Cartagena', 'Panama City', 'Addis Ababa']","['Organic', 'Fair Trade', 'Rainforest Alliance']",5.5,"{'plantingMonths': ['April', 'March', 'May'], 'harvestMonths': ['November', 'December', 'October'], 'dryingMonths': ['December', 'November', 'January']}"
|
||||||
|
fb4fbe42-01b5-444f-8150-97e29e7c7b97,Colombia,South America,"['Boquete', 'Sidamo', 'Tarrazú']",1200-2000m,October-March,"['Catuai', 'Typica', 'SL34']","['Wet-hulled', 'Washed', 'Natural']","['Floral', 'Nutty', 'Spicy']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.8,Coffee is an integral part of daily life and traditions.,24727,"['Panama City', 'Santos', 'Addis Ababa']","['Fair Trade', 'Organic', 'Direct Trade']",5.6,"{'plantingMonths': ['March', 'May', 'April'], 'harvestMonths': ['October', 'November', 'December'], 'dryingMonths': ['November', 'December', 'January']}"
|
||||||
|
2eae7bf9-474d-43cf-811a-3563a3e8b5d9,Costa Rica,Central America,"['Tarrazú', 'Boquete', 'Sidamo']",1200-2000m,October-March,"['Pacamara', 'SL28', 'Typica']","['Natural', 'Semi-washed', 'Wet-hulled']","['Caramel', 'Nutty', 'Spicy']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.1,Coffee is an integral part of daily life and traditions.,29113,"['Santos', 'Cartagena', 'Panama City']","['Organic', 'Direct Trade', 'Fair Trade']",4.6,"{'plantingMonths': ['March', 'April', 'May'], 'harvestMonths': ['November', 'October', 'December'], 'dryingMonths': ['November', 'December', 'January']}"
|
||||||
|
83168641-110c-4109-98d6-5ca4d3892896,Colombia,South America,"['Kirinyaga', 'Boquete', 'Kayanza']",1200-2000m,October-March,"['SL28', 'Catuai', 'SL34']","['Semi-washed', 'Wet-hulled', 'Washed']","['Caramel', 'Chocolate', 'Citrus']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.2,Coffee is an integral part of daily life and traditions.,47124,"['Panama City', 'Santos', 'Cartagena']","['Rainforest Alliance', 'Direct Trade', 'Organic']",4.3,"{'plantingMonths': ['April', 'March', 'May'], 'harvestMonths': ['December', 'October', 'November'], 'dryingMonths': ['November', 'January', 'December']}"
|
||||||
|
7a5b8322-7dfa-47c5-a44e-ad273cdfd259,Guatemala,Africa,"['Kayanza', 'Tarrazú', 'Huila']",1200-2000m,October-March,"['Pacamara', 'Caturra', 'Kent']","['Natural', 'Honey', 'Wet-hulled']","['Spicy', 'Fruity', 'Floral']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.9,Coffee is an integral part of daily life and traditions.,41869,"['Mombasa', 'Santos', 'Cartagena']","['Direct Trade', 'Organic', 'Rainforest Alliance']",6.7,"{'plantingMonths': ['April', 'May', 'March'], 'harvestMonths': ['October', 'December', 'November'], 'dryingMonths': ['November', 'January', 'December']}"
|
||||||
|
25bc8058-e94d-4345-ab72-a8ac27dfe926,Panama,South America,"['Tarrazú', 'Kirinyaga', 'Nyeri']",1200-2000m,October-March,"['SL34', 'Caturra', 'Catuai']","['Wet-hulled', 'Natural', 'Semi-washed']","['Berry', 'Floral', 'Spicy']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.6,Coffee is an integral part of daily life and traditions.,81107,"['Santos', 'Mombasa', 'Cartagena']","['Organic', 'Rainforest Alliance', 'Fair Trade']",6.5,"{'plantingMonths': ['March', 'April', 'May'], 'harvestMonths': ['December', 'November', 'October'], 'dryingMonths': ['January', 'November', 'December']}"
|
||||||
|
c6e6b9dc-9227-4d78-848a-a5699c942833,Yemen,South America,"['Yirgacheffe', 'Huila', 'Nyeri']",1200-2000m,October-March,"['SL34', 'Typica', 'Pacamara']","['Wet-hulled', 'Honey', 'Semi-washed']","['Citrus', 'Nutty', 'Floral']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.9,Coffee is an integral part of daily life and traditions.,12033,"['Mombasa', 'Santos', 'Panama City']","['Organic', 'Rainforest Alliance', 'Direct Trade']",6.5,"{'plantingMonths': ['March', 'April', 'May'], 'harvestMonths': ['October', 'November', 'December'], 'dryingMonths': ['December', 'November', 'January']}"
|
||||||
|
5117bf61-4223-4b41-8e2b-c3cd0ae0b023,Jamaica,Africa,"['Sidamo', 'Nyeri', 'Boquete']",1200-2000m,October-March,"['Maragogipe', 'Catuai', 'Typica']","['Natural', 'Honey', 'Semi-washed']","['Caramel', 'Citrus', 'Floral']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.4,Coffee is an integral part of daily life and traditions.,75938,"['Addis Ababa', 'Mombasa', 'Santos']","['Rainforest Alliance', 'Fair Trade', 'Organic']",7.5,"{'plantingMonths': ['March', 'May', 'April'], 'harvestMonths': ['November', 'December', 'October'], 'dryingMonths': ['November', 'January', 'December']}"
|
||||||
|
89e0e1f1-0b1a-4d63-bdd6-0a0674ad0ca5,Brazil,Africa,"['Antioquia', 'Sidamo', 'Huila']",1200-2000m,October-March,"['SL34', 'SL28', 'Maragogipe']","['Washed', 'Natural', 'Honey']","['Spicy', 'Fruity', 'Nutty']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.3,Coffee is an integral part of daily life and traditions.,59131,"['Santos', 'Cartagena', 'Addis Ababa']","['Fair Trade', 'Rainforest Alliance', 'Direct Trade']",6.7,"{'plantingMonths': ['April', 'May', 'March'], 'harvestMonths': ['October', 'November', 'December'], 'dryingMonths': ['January', 'November', 'December']}"
|
||||||
|
67e87737-9337-4295-a9cc-eee9636d5b94,Costa Rica,South America,"['Tarrazú', 'Huila', 'Sidamo']",1200-2000m,October-March,"['Kent', 'SL34', 'Maragogipe']","['Honey', 'Wet-hulled', 'Natural']","['Berry', 'Chocolate', 'Floral']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.9,Coffee is an integral part of daily life and traditions.,49146,"['Panama City', 'Addis Ababa', 'Mombasa']","['Rainforest Alliance', 'Fair Trade', 'Direct Trade']",7.4,"{'plantingMonths': ['March', 'April', 'May'], 'harvestMonths': ['December', 'November', 'October'], 'dryingMonths': ['November', 'December', 'January']}"
|
||||||
|
ef613af9-5d21-4c71-aaed-8eb80b7dbf75,Panama,South America,"['Kirinyaga', 'Kayanza', 'Antioquia']",1200-2000m,October-March,"['SL34', 'SL28', 'Pacamara']","['Semi-washed', 'Natural', 'Washed']","['Fruity', 'Nutty', 'Floral']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.8,Coffee is an integral part of daily life and traditions.,41664,"['Panama City', 'Addis Ababa', 'Santos']","['Direct Trade', 'Fair Trade', 'Rainforest Alliance']",4.6,"{'plantingMonths': ['May', 'March', 'April'], 'harvestMonths': ['November', 'December', 'October'], 'dryingMonths': ['January', 'November', 'December']}"
|
||||||
|
3b3f2e08-1094-4c40-9f78-e3fda9ebb858,Guatemala,Asia,"['Tarrazú', 'Antioquia', 'Kayanza']",1200-2000m,October-March,"['SL34', 'Maragogipe', 'Geisha']","['Natural', 'Semi-washed', 'Washed']","['Nutty', 'Floral', 'Caramel']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,3.5,Coffee is an integral part of daily life and traditions.,31299,"['Panama City', 'Mombasa', 'Addis Ababa']","['Organic', 'Direct Trade', 'Rainforest Alliance']",4.2,"{'plantingMonths': ['April', 'May', 'March'], 'harvestMonths': ['October', 'November', 'December'], 'dryingMonths': ['November', 'December', 'January']}"
|
||||||
|
31488cb5-1362-4f4e-b175-575bfda3ea46,Kenya,Asia,"['Boquete', 'Sidamo', 'Huila']",1200-2000m,October-March,"['SL34', 'Typica', 'Caturra']","['Washed', 'Honey', 'Natural']","['Caramel', 'Fruity', 'Chocolate']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.3,Coffee is an integral part of daily life and traditions.,18346,"['Santos', 'Addis Ababa', 'Cartagena']","['Direct Trade', 'Organic', 'Rainforest Alliance']",7.7,"{'plantingMonths': ['March', 'May', 'April'], 'harvestMonths': ['October', 'December', 'November'], 'dryingMonths': ['January', 'November', 'December']}"
|
||||||
|
92b63ae1-d2d0-45e7-9d58-e6e8094eccb7,Yemen,South America,"['Yirgacheffe', 'Tarrazú', 'Kayanza']",1200-2000m,October-March,"['Pacamara', 'SL28', 'Maragogipe']","['Washed', 'Wet-hulled', 'Natural']","['Caramel', 'Fruity', 'Berry']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.3,Coffee is an integral part of daily life and traditions.,28724,"['Cartagena', 'Mombasa', 'Addis Ababa']","['Rainforest Alliance', 'Organic', 'Fair Trade']",5.6,"{'plantingMonths': ['March', 'April', 'May'], 'harvestMonths': ['October', 'November', 'December'], 'dryingMonths': ['January', 'November', 'December']}"
|
||||||
|
54b5a624-a690-444a-87ab-a3df9c19f9d9,Brazil,Central America,"['Antioquia', 'Nyeri', 'Yirgacheffe']",1200-2000m,October-March,"['Geisha', 'Maragogipe', 'Caturra']","['Semi-washed', 'Washed', 'Wet-hulled']","['Caramel', 'Floral', 'Berry']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.1,Coffee is an integral part of daily life and traditions.,87062,"['Mombasa', 'Cartagena', 'Addis Ababa']","['Organic', 'Fair Trade', 'Rainforest Alliance']",4.4,"{'plantingMonths': ['May', 'March', 'April'], 'harvestMonths': ['November', 'December', 'October'], 'dryingMonths': ['January', 'November', 'December']}"
|
||||||
|
9fab2f87-4dec-49d6-a983-ea7d5e6d9afd,Honduras,South America,"['Kirinyaga', 'Sidamo', 'Tarrazú']",1200-2000m,October-March,"['SL28', 'Typica', 'Catuai']","['Wet-hulled', 'Honey', 'Washed']","['Citrus', 'Spicy', 'Chocolate']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.8,Coffee is an integral part of daily life and traditions.,50373,"['Santos', 'Cartagena', 'Mombasa']","['Organic', 'Fair Trade', 'Direct Trade']",6.4,"{'plantingMonths': ['March', 'May', 'April'], 'harvestMonths': ['November', 'October', 'December'], 'dryingMonths': ['January', 'November', 'December']}"
|
||||||
|
6eb93ee9-2fa9-4fe6-8f0d-34d62d97f216,Honduras,South America,"['Antioquia', 'Yirgacheffe', 'Boquete']",1200-2000m,October-March,"['Caturra', 'Maragogipe', 'SL28']","['Washed', 'Wet-hulled', 'Semi-washed']","['Citrus', 'Spicy', 'Fruity']",Known for bright acidity and floral notes.,Tropical highland climate,Volcanic loam,4.6,Coffee is an integral part of daily life and traditions.,99073,"['Cartagena', 'Addis Ababa', 'Panama City']","['Fair Trade', 'Direct Trade', 'Rainforest Alliance']",6.2,"{'plantingMonths': ['March', 'May', 'April'], 'harvestMonths': ['December', 'October', 'November'], 'dryingMonths': ['January', 'November', 'December']}"
|
||||||
|
51
lib/database/Roasters.csv
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
id,name,location,foundedYear,specialty,roastingStyle,description,website,size,focusRegions,certifications,rating,priceRange,directTrade,subscriptionAvailable,shippingRegions,popularBlends,roastingMethods,contact
|
||||||
|
5fb31c85-aca9-4182-a790-20cb2a492e42,Verve Coffee Roasters,"San Francisco, USA",2017,Single origin and micro-lot coffees,Medium-Dark,Award-winning specialty coffee roaster.,https://example.com,Small,"['Brazil', 'Honduras', 'Colombia']","['Fair Trade', 'Organic', 'Direct Trade']",4.8,Luxury,True,False,"['Europe', 'North America', 'Asia']","['Sunrise Roast', 'House Blend', 'Midnight Espresso']","['drum', 'air']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
ef73f3e6-dc1c-45bf-9a56-860a38af26fb,Counter Culture Coffee Roasters,"New York, USA",2015,Single origin and micro-lot coffees,Light,Award-winning specialty coffee roaster.,https://example.com,Micro,"['Brazil', 'Kenya', 'Jamaica']","['Direct Trade', 'Rainforest Alliance', 'Organic']",4.7,Luxury,False,True,"['Australia', 'Europe', 'North America']","['Sunrise Roast', 'House Blend', 'Midnight Espresso']","['drum', 'air']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
63880b66-1fb0-47fc-b8e3-5219b2f60dd9,Verve Coffee Roasters,"Portland, Canada",2013,Single origin and micro-lot coffees,Medium,Award-winning specialty coffee roaster.,https://example.com,Small,"['Colombia', 'Jamaica', 'Ethiopia']","['Direct Trade', 'Rainforest Alliance', 'Fair Trade']",3.9,Budget,True,True,"['Asia', 'Europe', 'Australia']","['Sunrise Roast', 'Midnight Espresso', 'House Blend']","['air', 'drum']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
338142db-114c-4f5a-847b-3f29b62c2d5c,Counter Culture Coffee Roasters,"New York, USA",2008,Single origin and micro-lot coffees,Medium,Award-winning specialty coffee roaster.,https://example.com,Large,"['Guatemala', 'Ethiopia', 'Kenya']","['Rainforest Alliance', 'Organic', 'Direct Trade']",4.5,Mid-range,True,False,"['Asia', 'Europe', 'Australia']","['Midnight Espresso', 'House Blend', 'Sunrise Roast']","['drum', 'air']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
333c1488-fedc-46dd-9cd9-f0428aa4d72b,Blue Bottle Coffee Roasters,"New York, Canada",2011,Single origin and micro-lot coffees,Medium-Dark,Award-winning specialty coffee roaster.,https://example.com,Micro,"['Honduras', 'Colombia', 'Brazil']","['Fair Trade', 'Organic', 'Direct Trade']",3.6,Mid-range,False,False,"['Asia', 'North America', 'Europe']","['House Blend', 'Midnight Espresso', 'Sunrise Roast']","['air', 'drum']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
4f3c6d8e-d311-4a8b-b6ad-e0958ac87fd8,Blue Bottle Coffee Roasters,"Portland, Canada",2002,Single origin and micro-lot coffees,Light,Award-winning specialty coffee roaster.,https://example.com,Medium,"['Yemen', 'Panama', 'Brazil']","['Organic', 'Rainforest Alliance', 'Fair Trade']",4.8,Premium,False,False,"['Australia', 'North America', 'Europe']","['House Blend', 'Sunrise Roast', 'Midnight Espresso']","['air', 'drum']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
208d84f0-cc92-4039-a3ce-f03f72e77f86,Onyx Coffee Roasters,"San Francisco, Canada",2006,Single origin and micro-lot coffees,Medium-Light,Award-winning specialty coffee roaster.,https://example.com,Multinational,"['Ethiopia', 'Jamaica', 'Colombia']","['Direct Trade', 'Rainforest Alliance', 'Fair Trade']",4.3,Mid-range,False,False,"['Europe', 'Australia', 'North America']","['Sunrise Roast', 'House Blend', 'Midnight Espresso']","['air', 'drum']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
c6932fd8-a71b-413a-9b33-95376af79232,Onyx Coffee Roasters,"Portland, Canada",2010,Single origin and micro-lot coffees,Medium-Light,Award-winning specialty coffee roaster.,https://example.com,Large,"['Kenya', 'Ethiopia', 'Brazil']","['Direct Trade', 'Fair Trade', 'Rainforest Alliance']",4.4,Budget,True,True,"['North America', 'Asia', 'Australia']","['House Blend', 'Midnight Espresso', 'Sunrise Roast']","['air', 'drum']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
a47c1497-469d-4c54-ac4e-88fce051d506,Stumptown Coffee Roasters,"San Francisco, Canada",2017,Single origin and micro-lot coffees,Medium,Award-winning specialty coffee roaster.,https://example.com,Multinational,"['Kenya', 'Costa Rica', 'Jamaica']","['Fair Trade', 'Organic', 'Direct Trade']",4.1,Luxury,True,True,"['Europe', 'North America', 'Asia']","['Sunrise Roast', 'House Blend', 'Midnight Espresso']","['air', 'drum']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
3d49379b-2503-4621-b3b5-4a46a7f6c4bd,Blue Bottle Coffee Roasters,"New York, Canada",2010,Single origin and micro-lot coffees,Dark,Award-winning specialty coffee roaster.,https://example.com,Large,"['Kenya', 'Yemen', 'Costa Rica']","['Direct Trade', 'Organic', 'Rainforest Alliance']",4.5,Mid-range,False,True,"['Asia', 'North America', 'Australia']","['Midnight Espresso', 'Sunrise Roast', 'House Blend']","['drum', 'air']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
3e2eb9d0-ef08-4ac6-82e7-6588cd6d66c3,Onyx Coffee Roasters,"Portland, Canada",2009,Single origin and micro-lot coffees,Dark,Award-winning specialty coffee roaster.,https://example.com,Medium,"['Panama', 'Guatemala', 'Yemen']","['Fair Trade', 'Direct Trade', 'Organic']",3.9,Premium,False,True,"['Australia', 'North America', 'Europe']","['House Blend', 'Sunrise Roast', 'Midnight Espresso']","['air', 'drum']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
5f7d6dcb-e74c-4e69-8c80-9d4ae6d83bda,Verve Coffee Roasters,"Los Angeles, USA",2006,Single origin and micro-lot coffees,Light,Award-winning specialty coffee roaster.,https://example.com,Small,"['Kenya', 'Ethiopia', 'Colombia']","['Fair Trade', 'Direct Trade', 'Rainforest Alliance']",3.9,Premium,False,True,"['North America', 'Asia', 'Europe']","['Sunrise Roast', 'House Blend', 'Midnight Espresso']","['air', 'drum']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
078386d3-02ea-4ec8-a360-a3b372d34983,Verve Coffee Roasters,"Portland, Canada",2015,Single origin and micro-lot coffees,Medium-Light,Award-winning specialty coffee roaster.,https://example.com,Multinational,"['Yemen', 'Guatemala', 'Colombia']","['Fair Trade', 'Organic', 'Direct Trade']",3.6,Premium,True,True,"['Asia', 'Europe', 'North America']","['House Blend', 'Sunrise Roast', 'Midnight Espresso']","['drum', 'air']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
f2bb8024-2397-4152-982b-c1495674cd93,Counter Culture Coffee Roasters,"New York, Canada",2017,Single origin and micro-lot coffees,Medium-Light,Award-winning specialty coffee roaster.,https://example.com,Small,"['Guatemala', 'Jamaica', 'Yemen']","['Fair Trade', 'Rainforest Alliance', 'Direct Trade']",4.8,Luxury,False,True,"['Australia', 'Europe', 'Asia']","['House Blend', 'Midnight Espresso', 'Sunrise Roast']","['drum', 'air']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
4baa2d3c-7a17-46cd-a75a-4a969faa7d01,Stumptown Coffee Roasters,"New York, Canada",2010,Single origin and micro-lot coffees,Medium-Light,Award-winning specialty coffee roaster.,https://example.com,Large,"['Ethiopia', 'Kenya', 'Honduras']","['Direct Trade', 'Organic', 'Rainforest Alliance']",3.6,Luxury,False,False,"['Asia', 'North America', 'Australia']","['Midnight Espresso', 'House Blend', 'Sunrise Roast']","['drum', 'air']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
05f62129-05a5-4aa9-9c77-c3bd29c69f45,Onyx Coffee Roasters,"San Francisco, USA",2000,Single origin and micro-lot coffees,Medium-Light,Award-winning specialty coffee roaster.,https://example.com,Multinational,"['Costa Rica', 'Yemen', 'Guatemala']","['Organic', 'Rainforest Alliance', 'Direct Trade']",3.5,Luxury,True,True,"['Europe', 'North America', 'Australia']","['Sunrise Roast', 'Midnight Espresso', 'House Blend']","['drum', 'air']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
02ac411e-832d-44d7-a2ee-58f9149fcef5,Verve Coffee Roasters,"San Francisco, Canada",2008,Single origin and micro-lot coffees,Medium,Award-winning specialty coffee roaster.,https://example.com,Large,"['Guatemala', 'Colombia', 'Costa Rica']","['Organic', 'Direct Trade', 'Fair Trade']",4.6,Mid-range,False,True,"['Australia', 'Europe', 'Asia']","['Sunrise Roast', 'House Blend', 'Midnight Espresso']","['drum', 'air']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
d23ce639-f4ee-4649-88bc-e2b3bc5c22f0,Verve Coffee Roasters,"Los Angeles, USA",2015,Single origin and micro-lot coffees,Medium,Award-winning specialty coffee roaster.,https://example.com,Medium,"['Costa Rica', 'Honduras', 'Kenya']","['Fair Trade', 'Rainforest Alliance', 'Direct Trade']",4.7,Premium,False,False,"['Asia', 'North America', 'Europe']","['House Blend', 'Midnight Espresso', 'Sunrise Roast']","['air', 'drum']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
b2d3e277-1587-45fd-bde0-5d946573248e,Verve Coffee Roasters,"New York, USA",2011,Single origin and micro-lot coffees,Medium-Dark,Award-winning specialty coffee roaster.,https://example.com,Large,"['Brazil', 'Kenya', 'Costa Rica']","['Fair Trade', 'Rainforest Alliance', 'Direct Trade']",3.8,Mid-range,False,False,"['Australia', 'Asia', 'Europe']","['Midnight Espresso', 'Sunrise Roast', 'House Blend']","['air', 'drum']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
207e0e01-15b6-499b-916d-2aeada092525,Stumptown Coffee Roasters,"Portland, Canada",2013,Single origin and micro-lot coffees,Medium,Award-winning specialty coffee roaster.,https://example.com,Small,"['Honduras', 'Kenya', 'Costa Rica']","['Fair Trade', 'Organic', 'Direct Trade']",3.9,Mid-range,False,True,"['Europe', 'Asia', 'North America']","['Sunrise Roast', 'House Blend', 'Midnight Espresso']","['air', 'drum']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
10a49f83-8c86-483c-8f1e-0deb9a6e0036,Onyx Coffee Roasters,"San Francisco, USA",2011,Single origin and micro-lot coffees,Medium-Dark,Award-winning specialty coffee roaster.,https://example.com,Medium,"['Panama', 'Jamaica', 'Costa Rica']","['Rainforest Alliance', 'Direct Trade', 'Organic']",3.7,Mid-range,False,False,"['Asia', 'North America', 'Europe']","['Midnight Espresso', 'House Blend', 'Sunrise Roast']","['air', 'drum']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
43c87921-6d2d-4622-ac00-f9b9160a40d0,Blue Bottle Coffee Roasters,"Portland, USA",2014,Single origin and micro-lot coffees,Medium,Award-winning specialty coffee roaster.,https://example.com,Medium,"['Brazil', 'Kenya', 'Ethiopia']","['Organic', 'Rainforest Alliance', 'Direct Trade']",4.6,Luxury,True,False,"['Europe', 'Australia', 'Asia']","['Midnight Espresso', 'House Blend', 'Sunrise Roast']","['drum', 'air']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
8000a2ad-656e-4ac1-b001-d518f611b58a,Stumptown Coffee Roasters,"San Francisco, USA",2013,Single origin and micro-lot coffees,Medium-Dark,Award-winning specialty coffee roaster.,https://example.com,Large,"['Yemen', 'Kenya', 'Ethiopia']","['Direct Trade', 'Rainforest Alliance', 'Fair Trade']",4.4,Luxury,False,False,"['Asia', 'North America', 'Europe']","['House Blend', 'Midnight Espresso', 'Sunrise Roast']","['air', 'drum']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
96458800-48e0-414b-b8f2-80f8d0db8fa8,Stumptown Coffee Roasters,"New York, Canada",2011,Single origin and micro-lot coffees,Medium-Light,Award-winning specialty coffee roaster.,https://example.com,Small,"['Jamaica', 'Brazil', 'Guatemala']","['Direct Trade', 'Organic', 'Fair Trade']",4.2,Mid-range,False,False,"['Asia', 'Australia', 'North America']","['House Blend', 'Midnight Espresso', 'Sunrise Roast']","['air', 'drum']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
9409a0c4-fef0-412c-bf27-515643532674,Verve Coffee Roasters,"Portland, USA",2020,Single origin and micro-lot coffees,Medium,Award-winning specialty coffee roaster.,https://example.com,Multinational,"['Yemen', 'Guatemala', 'Ethiopia']","['Rainforest Alliance', 'Fair Trade', 'Organic']",3.9,Budget,False,False,"['Asia', 'Australia', 'North America']","['Midnight Espresso', 'House Blend', 'Sunrise Roast']","['drum', 'air']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
0d7f3cb7-99ff-474d-a464-a29afb305119,Verve Coffee Roasters,"Portland, Canada",2010,Single origin and micro-lot coffees,Medium-Dark,Award-winning specialty coffee roaster.,https://example.com,Medium,"['Colombia', 'Guatemala', 'Yemen']","['Organic', 'Fair Trade', 'Rainforest Alliance']",4.5,Mid-range,True,False,"['North America', 'Australia', 'Asia']","['Midnight Espresso', 'Sunrise Roast', 'House Blend']","['air', 'drum']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
32421f12-1a93-434b-9e9a-c93adff971fc,Counter Culture Coffee Roasters,"San Francisco, USA",2013,Single origin and micro-lot coffees,Light,Award-winning specialty coffee roaster.,https://example.com,Micro,"['Colombia', 'Ethiopia', 'Honduras']","['Direct Trade', 'Organic', 'Fair Trade']",3.8,Mid-range,True,False,"['Asia', 'North America', 'Australia']","['Midnight Espresso', 'Sunrise Roast', 'House Blend']","['drum', 'air']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
5795569d-21d3-4eab-b678-6633f1b79304,Stumptown Coffee Roasters,"Los Angeles, USA",2016,Single origin and micro-lot coffees,Medium-Light,Award-winning specialty coffee roaster.,https://example.com,Small,"['Colombia', 'Ethiopia', 'Jamaica']","['Direct Trade', 'Fair Trade', 'Organic']",3.8,Premium,True,False,"['Australia', 'North America', 'Europe']","['Sunrise Roast', 'Midnight Espresso', 'House Blend']","['drum', 'air']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
7ae147df-9ef1-4f8a-b77b-6d019a1b5245,Verve Coffee Roasters,"San Francisco, USA",2007,Single origin and micro-lot coffees,Dark,Award-winning specialty coffee roaster.,https://example.com,Large,"['Yemen', 'Colombia', 'Panama']","['Fair Trade', 'Direct Trade', 'Rainforest Alliance']",3.7,Luxury,True,True,"['North America', 'Europe', 'Australia']","['House Blend', 'Midnight Espresso', 'Sunrise Roast']","['drum', 'air']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
1454c360-7a72-4f8b-9fdc-0073dc3fdf6c,Counter Culture Coffee Roasters,"New York, Canada",2012,Single origin and micro-lot coffees,Medium,Award-winning specialty coffee roaster.,https://example.com,Large,"['Guatemala', 'Jamaica', 'Costa Rica']","['Direct Trade', 'Organic', 'Rainforest Alliance']",5.0,Mid-range,False,True,"['North America', 'Australia', 'Europe']","['House Blend', 'Midnight Espresso', 'Sunrise Roast']","['air', 'drum']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
ee55e3f7-44cf-4a04-bd61-5a73f6da5548,Blue Bottle Coffee Roasters,"Los Angeles, Canada",2012,Single origin and micro-lot coffees,Medium,Award-winning specialty coffee roaster.,https://example.com,Micro,"['Ethiopia', 'Costa Rica', 'Honduras']","['Direct Trade', 'Organic', 'Fair Trade']",4.5,Luxury,True,True,"['Australia', 'Asia', 'Europe']","['Midnight Espresso', 'Sunrise Roast', 'House Blend']","['drum', 'air']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
5d5b0dfd-cfc2-492b-b0e0-3f9c7ca262f4,Blue Bottle Coffee Roasters,"New York, USA",2008,Single origin and micro-lot coffees,Dark,Award-winning specialty coffee roaster.,https://example.com,Micro,"['Honduras', 'Kenya', 'Yemen']","['Direct Trade', 'Rainforest Alliance', 'Organic']",4.7,Mid-range,False,True,"['Asia', 'North America', 'Europe']","['Midnight Espresso', 'House Blend', 'Sunrise Roast']","['air', 'drum']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
92c6d7d8-db70-4434-8dc0-cfc78af6c299,Onyx Coffee Roasters,"Portland, Canada",2015,Single origin and micro-lot coffees,Medium-Dark,Award-winning specialty coffee roaster.,https://example.com,Small,"['Panama', 'Costa Rica', 'Yemen']","['Rainforest Alliance', 'Fair Trade', 'Organic']",3.7,Budget,False,True,"['North America', 'Australia', 'Europe']","['Sunrise Roast', 'Midnight Espresso', 'House Blend']","['drum', 'air']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
2715b725-a51a-4e66-9e86-b5a3e24d10a4,Blue Bottle Coffee Roasters,"San Francisco, Canada",2018,Single origin and micro-lot coffees,Medium-Light,Award-winning specialty coffee roaster.,https://example.com,Large,"['Panama', 'Honduras', 'Colombia']","['Organic', 'Fair Trade', 'Rainforest Alliance']",4.3,Budget,False,False,"['Australia', 'Europe', 'North America']","['Midnight Espresso', 'Sunrise Roast', 'House Blend']","['drum', 'air']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
df719d83-a5f8-443f-a373-06101687d393,Stumptown Coffee Roasters,"San Francisco, Canada",2012,Single origin and micro-lot coffees,Dark,Award-winning specialty coffee roaster.,https://example.com,Multinational,"['Yemen', 'Colombia', 'Costa Rica']","['Fair Trade', 'Direct Trade', 'Rainforest Alliance']",4.0,Budget,False,True,"['Europe', 'Asia', 'Australia']","['Midnight Espresso', 'Sunrise Roast', 'House Blend']","['drum', 'air']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
ed829e15-b249-40e2-ad9d-6a8d2f4cd794,Blue Bottle Coffee Roasters,"New York, Canada",2004,Single origin and micro-lot coffees,Medium,Award-winning specialty coffee roaster.,https://example.com,Small,"['Jamaica', 'Colombia', 'Costa Rica']","['Organic', 'Rainforest Alliance', 'Fair Trade']",4.4,Premium,True,True,"['North America', 'Asia', 'Europe']","['House Blend', 'Midnight Espresso', 'Sunrise Roast']","['air', 'drum']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
3caff86c-06ae-4d56-a4ef-69a95731f1f9,Counter Culture Coffee Roasters,"New York, Canada",2010,Single origin and micro-lot coffees,Medium-Light,Award-winning specialty coffee roaster.,https://example.com,Multinational,"['Colombia', 'Guatemala', 'Ethiopia']","['Rainforest Alliance', 'Direct Trade', 'Organic']",3.5,Mid-range,True,True,"['North America', 'Australia', 'Asia']","['House Blend', 'Sunrise Roast', 'Midnight Espresso']","['drum', 'air']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
8807988b-22e4-4922-a610-d9627003fccc,Stumptown Coffee Roasters,"New York, Canada",2008,Single origin and micro-lot coffees,Medium-Dark,Award-winning specialty coffee roaster.,https://example.com,Small,"['Colombia', 'Panama', 'Yemen']","['Direct Trade', 'Rainforest Alliance', 'Organic']",4.4,Mid-range,False,False,"['Asia', 'Europe', 'North America']","['Midnight Espresso', 'Sunrise Roast', 'House Blend']","['drum', 'air']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
247bad2f-2acb-47a9-8689-791a12e401d6,Blue Bottle Coffee Roasters,"New York, Canada",2004,Single origin and micro-lot coffees,Medium-Light,Award-winning specialty coffee roaster.,https://example.com,Medium,"['Guatemala', 'Ethiopia', 'Brazil']","['Direct Trade', 'Organic', 'Rainforest Alliance']",4.3,Mid-range,False,True,"['North America', 'Asia', 'Europe']","['Midnight Espresso', 'House Blend', 'Sunrise Roast']","['air', 'drum']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
bcb9997a-a8a2-4f09-a286-0098b1afebdb,Blue Bottle Coffee Roasters,"New York, USA",2012,Single origin and micro-lot coffees,Light,Award-winning specialty coffee roaster.,https://example.com,Medium,"['Yemen', 'Guatemala', 'Colombia']","['Organic', 'Direct Trade', 'Fair Trade']",4.8,Luxury,True,True,"['Asia', 'Europe', 'Australia']","['Sunrise Roast', 'Midnight Espresso', 'House Blend']","['air', 'drum']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
0de0c578-577a-4e12-a4c1-467a53addf56,Blue Bottle Coffee Roasters,"Los Angeles, Canada",2009,Single origin and micro-lot coffees,Dark,Award-winning specialty coffee roaster.,https://example.com,Small,"['Panama', 'Colombia', 'Brazil']","['Fair Trade', 'Direct Trade', 'Organic']",4.3,Luxury,True,False,"['North America', 'Australia', 'Asia']","['Sunrise Roast', 'Midnight Espresso', 'House Blend']","['air', 'drum']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
117c2b45-ca97-4f6f-98cd-78db7427b311,Stumptown Coffee Roasters,"Portland, USA",2005,Single origin and micro-lot coffees,Light,Award-winning specialty coffee roaster.,https://example.com,Medium,"['Kenya', 'Ethiopia', 'Jamaica']","['Direct Trade', 'Rainforest Alliance', 'Organic']",3.8,Budget,False,True,"['North America', 'Australia', 'Asia']","['Midnight Espresso', 'Sunrise Roast', 'House Blend']","['drum', 'air']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
8752d52c-7f4d-467f-9cc3-26ecdebe373d,Counter Culture Coffee Roasters,"New York, USA",2007,Single origin and micro-lot coffees,Dark,Award-winning specialty coffee roaster.,https://example.com,Large,"['Jamaica', 'Guatemala', 'Yemen']","['Organic', 'Fair Trade', 'Rainforest Alliance']",4.6,Mid-range,True,False,"['Europe', 'North America', 'Asia']","['Midnight Espresso', 'House Blend', 'Sunrise Roast']","['air', 'drum']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
10c882a7-7924-46bf-9a54-603facd5d03f,Verve Coffee Roasters,"Portland, USA",2000,Single origin and micro-lot coffees,Dark,Award-winning specialty coffee roaster.,https://example.com,Large,"['Costa Rica', 'Guatemala', 'Brazil']","['Fair Trade', 'Direct Trade', 'Rainforest Alliance']",5.0,Premium,True,False,"['Australia', 'North America', 'Europe']","['House Blend', 'Midnight Espresso', 'Sunrise Roast']","['air', 'drum']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
b6c11147-c368-47b4-b569-0e1a49a467da,Stumptown Coffee Roasters,"New York, Canada",2010,Single origin and micro-lot coffees,Medium-Light,Award-winning specialty coffee roaster.,https://example.com,Medium,"['Colombia', 'Jamaica', 'Kenya']","['Fair Trade', 'Rainforest Alliance', 'Direct Trade']",4.4,Luxury,True,True,"['North America', 'Europe', 'Australia']","['Sunrise Roast', 'House Blend', 'Midnight Espresso']","['air', 'drum']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
4ff7c2c5-acfe-4fee-a3b1-7d0549aa9927,Onyx Coffee Roasters,"San Francisco, USA",2018,Single origin and micro-lot coffees,Light,Award-winning specialty coffee roaster.,https://example.com,Medium,"['Guatemala', 'Costa Rica', 'Honduras']","['Rainforest Alliance', 'Direct Trade', 'Organic']",3.9,Luxury,False,True,"['Australia', 'Europe', 'North America']","['Midnight Espresso', 'Sunrise Roast', 'House Blend']","['air', 'drum']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
0920adce-edf9-43ae-8286-ad53dfd09379,Stumptown Coffee Roasters,"Los Angeles, USA",2002,Single origin and micro-lot coffees,Medium,Award-winning specialty coffee roaster.,https://example.com,Micro,"['Yemen', 'Brazil', 'Honduras']","['Rainforest Alliance', 'Direct Trade', 'Fair Trade']",4.2,Premium,False,False,"['Europe', 'Asia', 'North America']","['Midnight Espresso', 'House Blend', 'Sunrise Roast']","['drum', 'air']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
3d4557f8-7a0c-4fc4-9977-ffe4b7686f1d,Onyx Coffee Roasters,"Los Angeles, USA",2005,Single origin and micro-lot coffees,Light,Award-winning specialty coffee roaster.,https://example.com,Multinational,"['Ethiopia', 'Colombia', 'Yemen']","['Direct Trade', 'Fair Trade', 'Rainforest Alliance']",4.0,Luxury,True,False,"['North America', 'Asia', 'Europe']","['Midnight Espresso', 'Sunrise Roast', 'House Blend']","['air', 'drum']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
5d5f5e02-7456-428a-b7e7-4e32f9e2901e,Counter Culture Coffee Roasters,"Los Angeles, USA",2009,Single origin and micro-lot coffees,Medium-Dark,Award-winning specialty coffee roaster.,https://example.com,Large,"['Ethiopia', 'Panama', 'Brazil']","['Direct Trade', 'Fair Trade', 'Organic']",4.6,Premium,False,True,"['North America', 'Australia', 'Asia']","['Midnight Espresso', 'Sunrise Roast', 'House Blend']","['air', 'drum']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
60addb7b-f735-47c2-84f1-e50dfe3f104b,Blue Bottle Coffee Roasters,"Los Angeles, USA",2001,Single origin and micro-lot coffees,Light,Award-winning specialty coffee roaster.,https://example.com,Medium,"['Costa Rica', 'Ethiopia', 'Kenya']","['Organic', 'Fair Trade', 'Rainforest Alliance']",4.5,Premium,True,False,"['North America', 'Asia', 'Europe']","['Midnight Espresso', 'House Blend', 'Sunrise Roast']","['air', 'drum']","{'email': 'info@example.com', 'phone': '+1234567890', 'address': '123 Roaster Lane, Beanville'}"
|
||||||
|
178
lib/examples/csv_crud_example.dart
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
// Example: How to use CRUD operations with CSV data
|
||||||
|
|
||||||
|
import '../services/csv_data_service.dart';
|
||||||
|
import '../models/bean.dart';
|
||||||
|
import '../models/machine.dart';
|
||||||
|
|
||||||
|
class CsvCrudExample {
|
||||||
|
final CsvDataService _csvService = CsvDataService();
|
||||||
|
|
||||||
|
// Example 1: Add a custom bean to the catalog
|
||||||
|
Future<void> addCustomBean() async {
|
||||||
|
final customBean = Bean(
|
||||||
|
id: 'custom_bean_1',
|
||||||
|
name: 'My Special Blend',
|
||||||
|
origin: 'Custom',
|
||||||
|
farm: 'Home Roastery',
|
||||||
|
producer: 'Me',
|
||||||
|
varietal: 'Mixed',
|
||||||
|
altitude: 1200,
|
||||||
|
processingMethod: 'Washed',
|
||||||
|
harvestSeason: '2025',
|
||||||
|
flavorNotes: [TastingNotes.chocolate, TastingNotes.nutty],
|
||||||
|
acidity: Acidity.medium,
|
||||||
|
body: Body.medium,
|
||||||
|
sweetness: 7,
|
||||||
|
roastLevel: RoastLevel.medium,
|
||||||
|
cupScore: 88.0,
|
||||||
|
price: 25.0,
|
||||||
|
availability: Availability.available,
|
||||||
|
certifications: ['Custom Roasted'],
|
||||||
|
roaster: 'Home Roaster',
|
||||||
|
roastDate: DateTime.now(),
|
||||||
|
bestByDate: DateTime.now().add(Duration(days: 30)),
|
||||||
|
brewingMethods: ['Espresso', 'Pour Over'],
|
||||||
|
isOwned: false,
|
||||||
|
quantity: 0.0,
|
||||||
|
notes: 'My own custom blend',
|
||||||
|
);
|
||||||
|
|
||||||
|
await _csvService.saveBean(customBean);
|
||||||
|
print('Custom bean added to catalog!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example 2: List all beans (CSV + custom)
|
||||||
|
Future<void> listAllBeans() async {
|
||||||
|
final allBeans = await _csvService.getBeans();
|
||||||
|
|
||||||
|
print('Total beans in catalog: ${allBeans.length}');
|
||||||
|
|
||||||
|
// Separate CSV and custom beans
|
||||||
|
final csvBeans = await _csvService.getCsvBeans();
|
||||||
|
final customBeans = _csvService.getCustomBeans();
|
||||||
|
|
||||||
|
print('CSV beans: ${csvBeans.length}');
|
||||||
|
print('Custom beans: ${customBeans.length}');
|
||||||
|
|
||||||
|
// List custom beans
|
||||||
|
for (final bean in customBeans) {
|
||||||
|
print('Custom: ${bean.name} - ${bean.roaster}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example 3: Try to delete a CSV bean (will fail)
|
||||||
|
Future<void> tryDeleteCsvBean() async {
|
||||||
|
try {
|
||||||
|
await _csvService.deleteBean('bean_1'); // Assuming this is a CSV bean
|
||||||
|
} catch (e) {
|
||||||
|
print('Expected error: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example 4: Delete a custom bean (will succeed)
|
||||||
|
Future<void> deleteCustomBean() async {
|
||||||
|
try {
|
||||||
|
await _csvService.deleteBean('custom_bean_1');
|
||||||
|
print('Custom bean deleted successfully!');
|
||||||
|
} catch (e) {
|
||||||
|
print('Error deleting custom bean: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example 5: Check if item is from CSV or custom
|
||||||
|
Future<void> checkItemSource() async {
|
||||||
|
final allBeans = await _csvService.getBeans();
|
||||||
|
|
||||||
|
for (final bean in allBeans.take(5)) {
|
||||||
|
final isFromCsv = _csvService.isCsvBean(bean.id);
|
||||||
|
print('${bean.name}: ${isFromCsv ? "CSV" : "Custom"}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example 6: Add custom machine
|
||||||
|
Future<void> addCustomMachine() async {
|
||||||
|
final customMachine = Machine(
|
||||||
|
id: 'custom_machine_1',
|
||||||
|
manufacturer: 'Custom',
|
||||||
|
model: 'DIY Espresso Machine',
|
||||||
|
year: 2025,
|
||||||
|
type: MachineType.espresso,
|
||||||
|
steamWand: true,
|
||||||
|
details: 'Built from scratch with love',
|
||||||
|
isOwned: false,
|
||||||
|
rating: 5.0,
|
||||||
|
popularity: 1,
|
||||||
|
portafilters: [
|
||||||
|
Portafilter(
|
||||||
|
id: 'custom_pf_1',
|
||||||
|
size: '58mm',
|
||||||
|
material: 'Stainless Steel',
|
||||||
|
)
|
||||||
|
],
|
||||||
|
specifications: {
|
||||||
|
'Boiler': 'Single',
|
||||||
|
'Pressure': '9 bar',
|
||||||
|
'Water Tank': '2L',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await _csvService.saveMachine(customMachine);
|
||||||
|
print('Custom machine added to catalog!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example 7: Update existing custom item
|
||||||
|
Future<void> updateCustomBean() async {
|
||||||
|
final customBeans = _csvService.getCustomBeans();
|
||||||
|
if (customBeans.isNotEmpty) {
|
||||||
|
final existingBean = customBeans.first;
|
||||||
|
final beanToUpdate = Bean(
|
||||||
|
id: existingBean.id,
|
||||||
|
name: existingBean.name,
|
||||||
|
origin: existingBean.origin,
|
||||||
|
farm: existingBean.farm,
|
||||||
|
producer: existingBean.producer,
|
||||||
|
varietal: existingBean.varietal,
|
||||||
|
altitude: existingBean.altitude,
|
||||||
|
processingMethod: existingBean.processingMethod,
|
||||||
|
harvestSeason: existingBean.harvestSeason,
|
||||||
|
flavorNotes: existingBean.flavorNotes,
|
||||||
|
acidity: existingBean.acidity,
|
||||||
|
body: existingBean.body,
|
||||||
|
sweetness: existingBean.sweetness,
|
||||||
|
roastLevel: existingBean.roastLevel,
|
||||||
|
cupScore: existingBean.cupScore,
|
||||||
|
price: 30.0, // Update price
|
||||||
|
availability: existingBean.availability,
|
||||||
|
certifications: existingBean.certifications,
|
||||||
|
roaster: existingBean.roaster,
|
||||||
|
roastDate: existingBean.roastDate,
|
||||||
|
bestByDate: existingBean.bestByDate,
|
||||||
|
brewingMethods: existingBean.brewingMethods,
|
||||||
|
isOwned: existingBean.isOwned,
|
||||||
|
quantity: existingBean.quantity,
|
||||||
|
notes: 'Updated notes: Even better now!', // Update notes
|
||||||
|
);
|
||||||
|
|
||||||
|
await _csvService.saveBean(beanToUpdate);
|
||||||
|
print('Custom bean updated!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage example
|
||||||
|
void main() async {
|
||||||
|
final example = CsvCrudExample();
|
||||||
|
|
||||||
|
// Add custom items
|
||||||
|
await example.addCustomBean();
|
||||||
|
await example.addCustomMachine();
|
||||||
|
|
||||||
|
// List and check items
|
||||||
|
await example.listAllBeans();
|
||||||
|
await example.checkItemSource();
|
||||||
|
|
||||||
|
// Try operations
|
||||||
|
await example.tryDeleteCsvBean(); // Will fail
|
||||||
|
await example.updateCustomBean(); // Will succeed
|
||||||
|
await example.deleteCustomBean(); // Will succeed
|
||||||
|
}
|
||||||
488
lib/main.dart
Normal file
@ -0,0 +1,488 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'providers/app_state.dart';
|
||||||
|
import 'screens/home_screen.dart';
|
||||||
|
import 'screens/beans_screen.dart';
|
||||||
|
import 'screens/machines_screen.dart';
|
||||||
|
import 'screens/recipes_screen.dart';
|
||||||
|
import 'screens/journal_screen.dart';
|
||||||
|
import 'screens/settings_screen.dart';
|
||||||
|
import 'components/global_search.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
runApp(const CoffeeAtHomeApp());
|
||||||
|
}
|
||||||
|
|
||||||
|
class CoffeeAtHomeApp extends StatelessWidget {
|
||||||
|
const CoffeeAtHomeApp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MultiProvider(
|
||||||
|
providers: [ChangeNotifierProvider(create: (_) => AppState())],
|
||||||
|
child: MaterialApp.router(
|
||||||
|
title: 'Coffee at Home',
|
||||||
|
theme: _buildTheme(),
|
||||||
|
routerConfig: _router,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ThemeData _buildTheme() {
|
||||||
|
return ThemeData(
|
||||||
|
useMaterial3: true,
|
||||||
|
colorScheme:
|
||||||
|
ColorScheme.fromSeed(
|
||||||
|
seedColor: const Color(0xFFD4A574), // Warm coffee brown
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
).copyWith(
|
||||||
|
primary: const Color(0xFFD4A574),
|
||||||
|
secondary: const Color(0xFF6F4E37),
|
||||||
|
surface: const Color(0xFF2D2D2D),
|
||||||
|
onPrimary: const Color(0xFF1A1A1A),
|
||||||
|
onSecondary: const Color(0xFFFFFFFF),
|
||||||
|
onSurface: const Color(0xFFF5F5DC),
|
||||||
|
),
|
||||||
|
scaffoldBackgroundColor: const Color(0xFF1A1A1A),
|
||||||
|
cardTheme: CardThemeData(
|
||||||
|
color: const Color(0xFF2D2D2D),
|
||||||
|
elevation: 4,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||||
|
side: const BorderSide(color: Color(0xFF3A3A3A)),
|
||||||
|
),
|
||||||
|
shadowColor: Colors.black.withValues(alpha: 0.4),
|
||||||
|
),
|
||||||
|
appBarTheme: const AppBarTheme(
|
||||||
|
backgroundColor: Color(0xFF2D2D2D),
|
||||||
|
foregroundColor: Color(0xFFF5F5DC),
|
||||||
|
elevation: 0,
|
||||||
|
titleTextStyle: TextStyle(
|
||||||
|
color: Color(0xFFF5F5DC),
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
surfaceTintColor: Colors.transparent,
|
||||||
|
),
|
||||||
|
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
|
||||||
|
backgroundColor: Color(0xFF2D2D2D),
|
||||||
|
selectedItemColor: Color(0xFFD4A574),
|
||||||
|
unselectedItemColor: Color(0xFFD2B48C),
|
||||||
|
type: BottomNavigationBarType.fixed,
|
||||||
|
elevation: 3,
|
||||||
|
),
|
||||||
|
floatingActionButtonTheme: const FloatingActionButtonThemeData(
|
||||||
|
backgroundColor: Color(0xFFD4A574),
|
||||||
|
foregroundColor: Color(0xFF1A1A1A),
|
||||||
|
),
|
||||||
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: const Color(0xFFD4A574),
|
||||||
|
foregroundColor: const Color(0xFF1A1A1A),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
|
textStyle: const TextStyle(fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
outlinedButtonTheme: OutlinedButtonThemeData(
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
foregroundColor: const Color(0xFFD4A574),
|
||||||
|
side: const BorderSide(color: Color(0xFFD4A574)),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
textTheme: const TextTheme(
|
||||||
|
headlineLarge: TextStyle(
|
||||||
|
color: Color(0xFFF5F5DC),
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
headlineMedium: TextStyle(
|
||||||
|
color: Color(0xFFF5F5DC),
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
headlineSmall: TextStyle(
|
||||||
|
color: Color(0xFFF5F5DC),
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
titleLarge: TextStyle(
|
||||||
|
color: Color(0xFFF5F5DC),
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
titleMedium: TextStyle(
|
||||||
|
color: Color(0xFFF5F5DC),
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
titleSmall: TextStyle(
|
||||||
|
color: Color(0xFFF5F5DC),
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
bodyLarge: TextStyle(color: Color(0xFFD2B48C)),
|
||||||
|
bodyMedium: TextStyle(color: Color(0xFFD2B48C)),
|
||||||
|
bodySmall: TextStyle(color: Color(0xFFD2B48C)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final GoRouter _router = GoRouter(
|
||||||
|
routes: [
|
||||||
|
ShellRoute(
|
||||||
|
builder: (context, state, child) {
|
||||||
|
return MainScaffold(child: child);
|
||||||
|
},
|
||||||
|
routes: [
|
||||||
|
GoRoute(path: '/', redirect: (_, __) => '/home'),
|
||||||
|
GoRoute(path: '/home', builder: (context, state) => const HomeScreen()),
|
||||||
|
GoRoute(
|
||||||
|
path: '/beans',
|
||||||
|
builder: (context, state) => const BeansScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/machines',
|
||||||
|
builder: (context, state) => const MachinesScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/recipes',
|
||||||
|
builder: (context, state) => const RecipesScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/journal',
|
||||||
|
builder: (context, state) => const JournalScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/settings',
|
||||||
|
builder: (context, state) => const SettingsScreen(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
class MainScaffold extends StatefulWidget {
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
const MainScaffold({super.key, required this.child});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MainScaffold> createState() => _MainScaffoldState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MainScaffoldState extends State<MainScaffold> {
|
||||||
|
int _currentIndex = 0;
|
||||||
|
bool _isSearchOpen = false;
|
||||||
|
|
||||||
|
final List<String> _routes = [
|
||||||
|
'/home',
|
||||||
|
'/beans',
|
||||||
|
'/machines',
|
||||||
|
'/recipes',
|
||||||
|
'/journal',
|
||||||
|
'/settings',
|
||||||
|
];
|
||||||
|
|
||||||
|
void _onItemTapped(int index) {
|
||||||
|
setState(() {
|
||||||
|
_currentIndex = index;
|
||||||
|
});
|
||||||
|
context.go(_routes[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _openSearch() {
|
||||||
|
setState(() {
|
||||||
|
_isSearchOpen = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _closeSearch() {
|
||||||
|
setState(() {
|
||||||
|
_isSearchOpen = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onSearchResultSelected(SearchResult result) {
|
||||||
|
switch (result.type) {
|
||||||
|
case 'Bean':
|
||||||
|
context.go('/beans');
|
||||||
|
break;
|
||||||
|
case 'Machine':
|
||||||
|
context.go('/machines');
|
||||||
|
break;
|
||||||
|
case 'Recipe':
|
||||||
|
context.go('/recipes');
|
||||||
|
break;
|
||||||
|
case 'Journal':
|
||||||
|
context.go('/journal');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// Update current index based on current route
|
||||||
|
final currentRoute = GoRouterState.of(context).uri.path;
|
||||||
|
final routeIndex = _routes.indexOf(currentRoute);
|
||||||
|
if (routeIndex != -1 && routeIndex != _currentIndex) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
setState(() {
|
||||||
|
_currentIndex = routeIndex;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
body: _isSearchOpen
|
||||||
|
? Stack(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
// AppBar
|
||||||
|
Container(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Color(0xFF2D2D2D),
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
color: Color(0xFF3A3A3A),
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: SafeArea(
|
||||||
|
child: Container(
|
||||||
|
height: 56,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Expanded(
|
||||||
|
child: Text(
|
||||||
|
'Coffee at Home',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xFFF5F5DC),
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.search,
|
||||||
|
color: Color(0xFFF5F5DC),
|
||||||
|
),
|
||||||
|
onPressed: _openSearch,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Body
|
||||||
|
Expanded(
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: constraints.maxWidth > 1200
|
||||||
|
? 1200
|
||||||
|
: constraints.maxWidth,
|
||||||
|
),
|
||||||
|
margin: EdgeInsets.symmetric(
|
||||||
|
horizontal: constraints.maxWidth > 1200
|
||||||
|
? (constraints.maxWidth - 1200) / 2
|
||||||
|
: 0,
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
child: widget.child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Bottom Navigation
|
||||||
|
Container(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Color(0xFF2D2D2D),
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(color: Color(0xFF3A3A3A), width: 1),
|
||||||
|
),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black26,
|
||||||
|
blurRadius: 8,
|
||||||
|
offset: Offset(0, -2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: SafeArea(
|
||||||
|
child: SizedBox(
|
||||||
|
height: 56,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
_buildBottomNavItem(0, Icons.home, 'Home'),
|
||||||
|
_buildBottomNavItem(1, Icons.coffee, 'Beans'),
|
||||||
|
_buildBottomNavItem(
|
||||||
|
2,
|
||||||
|
Icons.kitchen,
|
||||||
|
'Equipment',
|
||||||
|
),
|
||||||
|
_buildBottomNavItem(
|
||||||
|
3,
|
||||||
|
Icons.menu_book,
|
||||||
|
'Recipes',
|
||||||
|
),
|
||||||
|
_buildBottomNavItem(4, Icons.book, 'Journal'),
|
||||||
|
_buildBottomNavItem(
|
||||||
|
5,
|
||||||
|
Icons.settings,
|
||||||
|
'Settings',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
GlobalSearchWidget(
|
||||||
|
isOpen: _isSearchOpen,
|
||||||
|
onClose: _closeSearch,
|
||||||
|
onResultSelected: _onSearchResultSelected,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: Column(
|
||||||
|
children: [
|
||||||
|
// AppBar
|
||||||
|
Container(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Color(0xFF2D2D2D),
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(color: Color(0xFF3A3A3A), width: 1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: SafeArea(
|
||||||
|
child: Container(
|
||||||
|
height: 56,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Expanded(
|
||||||
|
child: Text(
|
||||||
|
'Coffee at Home',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Color(0xFFF5F5DC),
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.search,
|
||||||
|
color: Color(0xFFF5F5DC),
|
||||||
|
),
|
||||||
|
onPressed: _openSearch,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Body
|
||||||
|
Expanded(
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: constraints.maxWidth > 1200
|
||||||
|
? 1200
|
||||||
|
: constraints.maxWidth,
|
||||||
|
),
|
||||||
|
margin: EdgeInsets.symmetric(
|
||||||
|
horizontal: constraints.maxWidth > 1200
|
||||||
|
? (constraints.maxWidth - 1200) / 2
|
||||||
|
: 0,
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
child: widget.child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Bottom Navigation
|
||||||
|
Container(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Color(0xFF2D2D2D),
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(color: Color(0xFF3A3A3A), width: 1),
|
||||||
|
),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black26,
|
||||||
|
blurRadius: 8,
|
||||||
|
offset: Offset(0, -2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: SafeArea(
|
||||||
|
child: SizedBox(
|
||||||
|
height: 56,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
_buildBottomNavItem(0, Icons.home, 'Home'),
|
||||||
|
_buildBottomNavItem(1, Icons.coffee, 'Beans'),
|
||||||
|
_buildBottomNavItem(2, Icons.kitchen, 'Equipment'),
|
||||||
|
_buildBottomNavItem(3, Icons.menu_book, 'Recipes'),
|
||||||
|
_buildBottomNavItem(4, Icons.book, 'Journal'),
|
||||||
|
_buildBottomNavItem(5, Icons.settings, 'Settings'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildBottomNavItem(int index, IconData icon, String label) {
|
||||||
|
final isSelected = _currentIndex == index;
|
||||||
|
return Expanded(
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => _onItemTapped(index),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
icon,
|
||||||
|
color: isSelected
|
||||||
|
? const Color(0xFFD4A574)
|
||||||
|
: const Color(0xFFD2B48C),
|
||||||
|
size: 22,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: TextStyle(
|
||||||
|
color: isSelected
|
||||||
|
? const Color(0xFFD4A574)
|
||||||
|
: const Color(0xFFD2B48C),
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: isSelected
|
||||||
|
? FontWeight.w600
|
||||||
|
: FontWeight.normal,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
251
lib/models/bean.dart
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
part 'bean.g.dart';
|
||||||
|
|
||||||
|
enum RoastLevel {
|
||||||
|
@JsonValue('Light')
|
||||||
|
light,
|
||||||
|
@JsonValue('Medium')
|
||||||
|
medium,
|
||||||
|
@JsonValue('Medium-Dark')
|
||||||
|
mediumDark,
|
||||||
|
@JsonValue('Dark')
|
||||||
|
dark,
|
||||||
|
@JsonValue('Medium-Light')
|
||||||
|
mediumLight,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TastingNotes {
|
||||||
|
// Fruity notes
|
||||||
|
@JsonValue('Berry')
|
||||||
|
berry,
|
||||||
|
@JsonValue('Citrus')
|
||||||
|
citrus,
|
||||||
|
@JsonValue('Stone Fruit')
|
||||||
|
stoneFruit,
|
||||||
|
@JsonValue('Apple')
|
||||||
|
apple,
|
||||||
|
@JsonValue('Tropical Fruit')
|
||||||
|
tropical,
|
||||||
|
@JsonValue('Dried Fruit')
|
||||||
|
driedFruit,
|
||||||
|
@JsonValue('Cherry')
|
||||||
|
cherry,
|
||||||
|
@JsonValue('Grape')
|
||||||
|
grape,
|
||||||
|
@JsonValue('Fruity')
|
||||||
|
fruity,
|
||||||
|
|
||||||
|
// Floral notes
|
||||||
|
@JsonValue('Jasmine')
|
||||||
|
jasmine,
|
||||||
|
@JsonValue('Rose')
|
||||||
|
rose,
|
||||||
|
@JsonValue('Lavender')
|
||||||
|
lavender,
|
||||||
|
@JsonValue('Hibiscus')
|
||||||
|
hibiscus,
|
||||||
|
@JsonValue('Floral')
|
||||||
|
floral,
|
||||||
|
|
||||||
|
// Nutty & sweet
|
||||||
|
@JsonValue('Almond')
|
||||||
|
almond,
|
||||||
|
@JsonValue('Hazelnut')
|
||||||
|
hazelnut,
|
||||||
|
@JsonValue('Caramel')
|
||||||
|
caramel,
|
||||||
|
@JsonValue('Honey')
|
||||||
|
honey,
|
||||||
|
@JsonValue('Vanilla')
|
||||||
|
vanilla,
|
||||||
|
@JsonValue('Chocolate')
|
||||||
|
chocolate,
|
||||||
|
@JsonValue('Cocoa')
|
||||||
|
cocoa,
|
||||||
|
@JsonValue('Brown Sugar')
|
||||||
|
brownSugar,
|
||||||
|
@JsonValue('Molasses')
|
||||||
|
molasses,
|
||||||
|
@JsonValue('Maple')
|
||||||
|
maple,
|
||||||
|
@JsonValue('Nutty')
|
||||||
|
nutty,
|
||||||
|
|
||||||
|
// Earthy, herbal, and savory
|
||||||
|
@JsonValue('Tobacco')
|
||||||
|
tobacco,
|
||||||
|
@JsonValue('Leather')
|
||||||
|
leather,
|
||||||
|
@JsonValue('Spice')
|
||||||
|
spice,
|
||||||
|
@JsonValue('Spicy')
|
||||||
|
spicy,
|
||||||
|
@JsonValue('Clove')
|
||||||
|
clove,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Acidity {
|
||||||
|
@JsonValue('High')
|
||||||
|
high,
|
||||||
|
@JsonValue('Medium-High')
|
||||||
|
mediumHigh,
|
||||||
|
@JsonValue('Medium')
|
||||||
|
medium,
|
||||||
|
@JsonValue('Medium-Low')
|
||||||
|
mediumLow,
|
||||||
|
@JsonValue('Low')
|
||||||
|
low,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Body {
|
||||||
|
@JsonValue('Light')
|
||||||
|
light,
|
||||||
|
@JsonValue('Medium-Light')
|
||||||
|
mediumLight,
|
||||||
|
@JsonValue('Medium')
|
||||||
|
medium,
|
||||||
|
@JsonValue('Medium-Full')
|
||||||
|
mediumFull,
|
||||||
|
@JsonValue('Full')
|
||||||
|
full,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Availability {
|
||||||
|
@JsonValue('Available')
|
||||||
|
available,
|
||||||
|
@JsonValue('Limited')
|
||||||
|
limited,
|
||||||
|
@JsonValue('Seasonal')
|
||||||
|
seasonal,
|
||||||
|
@JsonValue('Sold Out')
|
||||||
|
soldOut,
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class OriginCountry {
|
||||||
|
final String id;
|
||||||
|
final String continent;
|
||||||
|
final int avgElevation;
|
||||||
|
final String details;
|
||||||
|
final String? image;
|
||||||
|
final String notes;
|
||||||
|
final double rating;
|
||||||
|
|
||||||
|
OriginCountry({
|
||||||
|
required this.id,
|
||||||
|
required this.continent,
|
||||||
|
required this.avgElevation,
|
||||||
|
required this.details,
|
||||||
|
this.image,
|
||||||
|
required this.notes,
|
||||||
|
required this.rating,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory OriginCountry.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$OriginCountryFromJson(json);
|
||||||
|
Map<String, dynamic> toJson() => _$OriginCountryToJson(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class OriginFarm {
|
||||||
|
final String id;
|
||||||
|
final String details;
|
||||||
|
|
||||||
|
OriginFarm({required this.id, required this.details});
|
||||||
|
|
||||||
|
factory OriginFarm.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$OriginFarmFromJson(json);
|
||||||
|
Map<String, dynamic> toJson() => _$OriginFarmToJson(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class Roaster {
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
final String location;
|
||||||
|
final String details;
|
||||||
|
|
||||||
|
Roaster({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.location,
|
||||||
|
required this.details,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Roaster.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$RoasterFromJson(json);
|
||||||
|
Map<String, dynamic> toJson() => _$RoasterToJson(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class Bean {
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
final String origin;
|
||||||
|
final String farm;
|
||||||
|
final String producer;
|
||||||
|
final String varietal;
|
||||||
|
final int altitude;
|
||||||
|
final String processingMethod;
|
||||||
|
final String harvestSeason;
|
||||||
|
final List<TastingNotes> flavorNotes;
|
||||||
|
final Acidity acidity;
|
||||||
|
final Body body;
|
||||||
|
final int sweetness;
|
||||||
|
final RoastLevel roastLevel;
|
||||||
|
final double cupScore;
|
||||||
|
final double price;
|
||||||
|
final Availability availability;
|
||||||
|
final List<String> certifications;
|
||||||
|
final String roaster;
|
||||||
|
@JsonKey(fromJson: _dateTimeFromJson, toJson: _dateTimeToJson)
|
||||||
|
final DateTime roastDate;
|
||||||
|
@JsonKey(fromJson: _dateTimeFromJson, toJson: _dateTimeToJson)
|
||||||
|
final DateTime bestByDate;
|
||||||
|
final List<String> brewingMethods;
|
||||||
|
final bool isOwned;
|
||||||
|
final double quantity;
|
||||||
|
final String notes;
|
||||||
|
|
||||||
|
// Keep these for compatibility with existing code
|
||||||
|
bool get preferred => isOwned;
|
||||||
|
List<TastingNotes> get tastingNotes => flavorNotes;
|
||||||
|
DateTime get roastedDate => roastDate;
|
||||||
|
OriginCountry? get originCountry => null; // Will be null for CSV data
|
||||||
|
|
||||||
|
Bean({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.origin,
|
||||||
|
required this.farm,
|
||||||
|
required this.producer,
|
||||||
|
required this.varietal,
|
||||||
|
required this.altitude,
|
||||||
|
required this.processingMethod,
|
||||||
|
required this.harvestSeason,
|
||||||
|
required this.flavorNotes,
|
||||||
|
required this.acidity,
|
||||||
|
required this.body,
|
||||||
|
required this.sweetness,
|
||||||
|
required this.roastLevel,
|
||||||
|
required this.cupScore,
|
||||||
|
required this.price,
|
||||||
|
required this.availability,
|
||||||
|
required this.certifications,
|
||||||
|
required this.roaster,
|
||||||
|
required this.roastDate,
|
||||||
|
required this.bestByDate,
|
||||||
|
required this.brewingMethods,
|
||||||
|
required this.isOwned,
|
||||||
|
required this.quantity,
|
||||||
|
required this.notes,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Bean.fromJson(Map<String, dynamic> json) => _$BeanFromJson(json);
|
||||||
|
Map<String, dynamic> toJson() => _$BeanToJson(this);
|
||||||
|
|
||||||
|
static DateTime _dateTimeFromJson(String json) => DateTime.parse(json);
|
||||||
|
static String _dateTimeToJson(DateTime dateTime) =>
|
||||||
|
dateTime.toIso8601String();
|
||||||
|
}
|
||||||
177
lib/models/bean.g.dart
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'bean.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
OriginCountry _$OriginCountryFromJson(Map<String, dynamic> json) =>
|
||||||
|
OriginCountry(
|
||||||
|
id: json['id'] as String,
|
||||||
|
continent: json['continent'] as String,
|
||||||
|
avgElevation: (json['avgElevation'] as num).toInt(),
|
||||||
|
details: json['details'] as String,
|
||||||
|
image: json['image'] as String?,
|
||||||
|
notes: json['notes'] as String,
|
||||||
|
rating: (json['rating'] as num).toDouble(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$OriginCountryToJson(OriginCountry instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'continent': instance.continent,
|
||||||
|
'avgElevation': instance.avgElevation,
|
||||||
|
'details': instance.details,
|
||||||
|
'image': instance.image,
|
||||||
|
'notes': instance.notes,
|
||||||
|
'rating': instance.rating,
|
||||||
|
};
|
||||||
|
|
||||||
|
OriginFarm _$OriginFarmFromJson(Map<String, dynamic> json) =>
|
||||||
|
OriginFarm(id: json['id'] as String, details: json['details'] as String);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$OriginFarmToJson(OriginFarm instance) =>
|
||||||
|
<String, dynamic>{'id': instance.id, 'details': instance.details};
|
||||||
|
|
||||||
|
Roaster _$RoasterFromJson(Map<String, dynamic> json) => Roaster(
|
||||||
|
id: json['id'] as String,
|
||||||
|
name: json['name'] as String,
|
||||||
|
location: json['location'] as String,
|
||||||
|
details: json['details'] as String,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$RoasterToJson(Roaster instance) => <String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'name': instance.name,
|
||||||
|
'location': instance.location,
|
||||||
|
'details': instance.details,
|
||||||
|
};
|
||||||
|
|
||||||
|
Bean _$BeanFromJson(Map<String, dynamic> json) => Bean(
|
||||||
|
id: json['id'] as String,
|
||||||
|
name: json['name'] as String,
|
||||||
|
origin: json['origin'] as String,
|
||||||
|
farm: json['farm'] as String,
|
||||||
|
producer: json['producer'] as String,
|
||||||
|
varietal: json['varietal'] as String,
|
||||||
|
altitude: (json['altitude'] as num).toInt(),
|
||||||
|
processingMethod: json['processingMethod'] as String,
|
||||||
|
harvestSeason: json['harvestSeason'] as String,
|
||||||
|
flavorNotes: (json['flavorNotes'] as List<dynamic>)
|
||||||
|
.map((e) => $enumDecode(_$TastingNotesEnumMap, e))
|
||||||
|
.toList(),
|
||||||
|
acidity: $enumDecode(_$AcidityEnumMap, json['acidity']),
|
||||||
|
body: $enumDecode(_$BodyEnumMap, json['body']),
|
||||||
|
sweetness: (json['sweetness'] as num).toInt(),
|
||||||
|
roastLevel: $enumDecode(_$RoastLevelEnumMap, json['roastLevel']),
|
||||||
|
cupScore: (json['cupScore'] as num).toDouble(),
|
||||||
|
price: (json['price'] as num).toDouble(),
|
||||||
|
availability: $enumDecode(_$AvailabilityEnumMap, json['availability']),
|
||||||
|
certifications: (json['certifications'] as List<dynamic>)
|
||||||
|
.map((e) => e as String)
|
||||||
|
.toList(),
|
||||||
|
roaster: json['roaster'] as String,
|
||||||
|
roastDate: Bean._dateTimeFromJson(json['roastDate'] as String),
|
||||||
|
bestByDate: Bean._dateTimeFromJson(json['bestByDate'] as String),
|
||||||
|
brewingMethods: (json['brewingMethods'] as List<dynamic>)
|
||||||
|
.map((e) => e as String)
|
||||||
|
.toList(),
|
||||||
|
isOwned: json['isOwned'] as bool,
|
||||||
|
quantity: (json['quantity'] as num).toDouble(),
|
||||||
|
notes: json['notes'] as String,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$BeanToJson(Bean instance) => <String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'name': instance.name,
|
||||||
|
'origin': instance.origin,
|
||||||
|
'farm': instance.farm,
|
||||||
|
'producer': instance.producer,
|
||||||
|
'varietal': instance.varietal,
|
||||||
|
'altitude': instance.altitude,
|
||||||
|
'processingMethod': instance.processingMethod,
|
||||||
|
'harvestSeason': instance.harvestSeason,
|
||||||
|
'flavorNotes': instance.flavorNotes
|
||||||
|
.map((e) => _$TastingNotesEnumMap[e]!)
|
||||||
|
.toList(),
|
||||||
|
'acidity': _$AcidityEnumMap[instance.acidity]!,
|
||||||
|
'body': _$BodyEnumMap[instance.body]!,
|
||||||
|
'sweetness': instance.sweetness,
|
||||||
|
'roastLevel': _$RoastLevelEnumMap[instance.roastLevel]!,
|
||||||
|
'cupScore': instance.cupScore,
|
||||||
|
'price': instance.price,
|
||||||
|
'availability': _$AvailabilityEnumMap[instance.availability]!,
|
||||||
|
'certifications': instance.certifications,
|
||||||
|
'roaster': instance.roaster,
|
||||||
|
'roastDate': Bean._dateTimeToJson(instance.roastDate),
|
||||||
|
'bestByDate': Bean._dateTimeToJson(instance.bestByDate),
|
||||||
|
'brewingMethods': instance.brewingMethods,
|
||||||
|
'isOwned': instance.isOwned,
|
||||||
|
'quantity': instance.quantity,
|
||||||
|
'notes': instance.notes,
|
||||||
|
};
|
||||||
|
|
||||||
|
const _$TastingNotesEnumMap = {
|
||||||
|
TastingNotes.berry: 'Berry',
|
||||||
|
TastingNotes.citrus: 'Citrus',
|
||||||
|
TastingNotes.stoneFruit: 'Stone Fruit',
|
||||||
|
TastingNotes.apple: 'Apple',
|
||||||
|
TastingNotes.tropical: 'Tropical Fruit',
|
||||||
|
TastingNotes.driedFruit: 'Dried Fruit',
|
||||||
|
TastingNotes.cherry: 'Cherry',
|
||||||
|
TastingNotes.grape: 'Grape',
|
||||||
|
TastingNotes.fruity: 'Fruity',
|
||||||
|
TastingNotes.jasmine: 'Jasmine',
|
||||||
|
TastingNotes.rose: 'Rose',
|
||||||
|
TastingNotes.lavender: 'Lavender',
|
||||||
|
TastingNotes.hibiscus: 'Hibiscus',
|
||||||
|
TastingNotes.floral: 'Floral',
|
||||||
|
TastingNotes.almond: 'Almond',
|
||||||
|
TastingNotes.hazelnut: 'Hazelnut',
|
||||||
|
TastingNotes.caramel: 'Caramel',
|
||||||
|
TastingNotes.honey: 'Honey',
|
||||||
|
TastingNotes.vanilla: 'Vanilla',
|
||||||
|
TastingNotes.chocolate: 'Chocolate',
|
||||||
|
TastingNotes.cocoa: 'Cocoa',
|
||||||
|
TastingNotes.brownSugar: 'Brown Sugar',
|
||||||
|
TastingNotes.molasses: 'Molasses',
|
||||||
|
TastingNotes.maple: 'Maple',
|
||||||
|
TastingNotes.nutty: 'Nutty',
|
||||||
|
TastingNotes.tobacco: 'Tobacco',
|
||||||
|
TastingNotes.leather: 'Leather',
|
||||||
|
TastingNotes.spice: 'Spice',
|
||||||
|
TastingNotes.spicy: 'Spicy',
|
||||||
|
TastingNotes.clove: 'Clove',
|
||||||
|
};
|
||||||
|
|
||||||
|
const _$AcidityEnumMap = {
|
||||||
|
Acidity.high: 'High',
|
||||||
|
Acidity.mediumHigh: 'Medium-High',
|
||||||
|
Acidity.medium: 'Medium',
|
||||||
|
Acidity.mediumLow: 'Medium-Low',
|
||||||
|
Acidity.low: 'Low',
|
||||||
|
};
|
||||||
|
|
||||||
|
const _$BodyEnumMap = {
|
||||||
|
Body.light: 'Light',
|
||||||
|
Body.mediumLight: 'Medium-Light',
|
||||||
|
Body.medium: 'Medium',
|
||||||
|
Body.mediumFull: 'Medium-Full',
|
||||||
|
Body.full: 'Full',
|
||||||
|
};
|
||||||
|
|
||||||
|
const _$RoastLevelEnumMap = {
|
||||||
|
RoastLevel.light: 'Light',
|
||||||
|
RoastLevel.medium: 'Medium',
|
||||||
|
RoastLevel.mediumDark: 'Medium-Dark',
|
||||||
|
RoastLevel.dark: 'Dark',
|
||||||
|
RoastLevel.mediumLight: 'Medium-Light',
|
||||||
|
};
|
||||||
|
|
||||||
|
const _$AvailabilityEnumMap = {
|
||||||
|
Availability.available: 'Available',
|
||||||
|
Availability.limited: 'Limited',
|
||||||
|
Availability.seasonal: 'Seasonal',
|
||||||
|
Availability.soldOut: 'Sold Out',
|
||||||
|
};
|
||||||
45
lib/models/drink.dart
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
import 'bean.dart';
|
||||||
|
import 'machine.dart';
|
||||||
|
import 'recipe.dart';
|
||||||
|
|
||||||
|
part 'drink.g.dart';
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class Drink {
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
final String details;
|
||||||
|
final String? image;
|
||||||
|
final String notes;
|
||||||
|
final bool preferred;
|
||||||
|
final double rating; // 1-5 stars
|
||||||
|
final String size;
|
||||||
|
final Bean? bean;
|
||||||
|
final Machine? machine;
|
||||||
|
final Recipe? recipe;
|
||||||
|
@JsonKey(fromJson: _dateTimeFromJson, toJson: _dateTimeToJson)
|
||||||
|
final DateTime dateCreated;
|
||||||
|
|
||||||
|
Drink({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.details,
|
||||||
|
this.image,
|
||||||
|
required this.notes,
|
||||||
|
required this.preferred,
|
||||||
|
required this.rating,
|
||||||
|
required this.size,
|
||||||
|
this.bean,
|
||||||
|
this.machine,
|
||||||
|
this.recipe,
|
||||||
|
required this.dateCreated,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Drink.fromJson(Map<String, dynamic> json) => _$DrinkFromJson(json);
|
||||||
|
Map<String, dynamic> toJson() => _$DrinkToJson(this);
|
||||||
|
|
||||||
|
static DateTime _dateTimeFromJson(String json) => DateTime.parse(json);
|
||||||
|
static String _dateTimeToJson(DateTime dateTime) =>
|
||||||
|
dateTime.toIso8601String();
|
||||||
|
}
|
||||||
43
lib/models/drink.g.dart
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'drink.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
Drink _$DrinkFromJson(Map<String, dynamic> json) => Drink(
|
||||||
|
id: json['id'] as String,
|
||||||
|
name: json['name'] as String,
|
||||||
|
details: json['details'] as String,
|
||||||
|
image: json['image'] as String?,
|
||||||
|
notes: json['notes'] as String,
|
||||||
|
preferred: json['preferred'] as bool,
|
||||||
|
rating: (json['rating'] as num).toDouble(),
|
||||||
|
size: json['size'] as String,
|
||||||
|
bean: json['bean'] == null
|
||||||
|
? null
|
||||||
|
: Bean.fromJson(json['bean'] as Map<String, dynamic>),
|
||||||
|
machine: json['machine'] == null
|
||||||
|
? null
|
||||||
|
: Machine.fromJson(json['machine'] as Map<String, dynamic>),
|
||||||
|
recipe: json['recipe'] == null
|
||||||
|
? null
|
||||||
|
: Recipe.fromJson(json['recipe'] as Map<String, dynamic>),
|
||||||
|
dateCreated: Drink._dateTimeFromJson(json['dateCreated'] as String),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$DrinkToJson(Drink instance) => <String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'name': instance.name,
|
||||||
|
'details': instance.details,
|
||||||
|
'image': instance.image,
|
||||||
|
'notes': instance.notes,
|
||||||
|
'preferred': instance.preferred,
|
||||||
|
'rating': instance.rating,
|
||||||
|
'size': instance.size,
|
||||||
|
'bean': instance.bean,
|
||||||
|
'machine': instance.machine,
|
||||||
|
'recipe': instance.recipe,
|
||||||
|
'dateCreated': Drink._dateTimeToJson(instance.dateCreated),
|
||||||
|
};
|
||||||
32
lib/models/journal_entry.dart
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
import 'drink.dart';
|
||||||
|
|
||||||
|
part 'journal_entry.g.dart';
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class JournalEntry {
|
||||||
|
final String id;
|
||||||
|
@JsonKey(fromJson: _dateTimeFromJson, toJson: _dateTimeToJson)
|
||||||
|
final DateTime date;
|
||||||
|
final Drink drink;
|
||||||
|
final String? notes;
|
||||||
|
final String? mood;
|
||||||
|
final String? weather;
|
||||||
|
|
||||||
|
JournalEntry({
|
||||||
|
required this.id,
|
||||||
|
required this.date,
|
||||||
|
required this.drink,
|
||||||
|
this.notes,
|
||||||
|
this.mood,
|
||||||
|
this.weather,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory JournalEntry.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$JournalEntryFromJson(json);
|
||||||
|
Map<String, dynamic> toJson() => _$JournalEntryToJson(this);
|
||||||
|
|
||||||
|
static DateTime _dateTimeFromJson(String json) => DateTime.parse(json);
|
||||||
|
static String _dateTimeToJson(DateTime dateTime) =>
|
||||||
|
dateTime.toIso8601String();
|
||||||
|
}
|
||||||
26
lib/models/journal_entry.g.dart
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'journal_entry.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
JournalEntry _$JournalEntryFromJson(Map<String, dynamic> json) => JournalEntry(
|
||||||
|
id: json['id'] as String,
|
||||||
|
date: JournalEntry._dateTimeFromJson(json['date'] as String),
|
||||||
|
drink: Drink.fromJson(json['drink'] as Map<String, dynamic>),
|
||||||
|
notes: json['notes'] as String?,
|
||||||
|
mood: json['mood'] as String?,
|
||||||
|
weather: json['weather'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$JournalEntryToJson(JournalEntry instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'date': JournalEntry._dateTimeToJson(instance.date),
|
||||||
|
'drink': instance.drink,
|
||||||
|
'notes': instance.notes,
|
||||||
|
'mood': instance.mood,
|
||||||
|
'weather': instance.weather,
|
||||||
|
};
|
||||||
169
lib/models/machine.dart
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
part 'machine.g.dart';
|
||||||
|
|
||||||
|
enum MachineType {
|
||||||
|
@JsonValue('Espresso')
|
||||||
|
espresso,
|
||||||
|
@JsonValue('Drip')
|
||||||
|
drip,
|
||||||
|
@JsonValue('Percolation')
|
||||||
|
percolation,
|
||||||
|
@JsonValue('French Press')
|
||||||
|
frenchPress,
|
||||||
|
@JsonValue('Cold Brew')
|
||||||
|
coldBrew,
|
||||||
|
@JsonValue('E61')
|
||||||
|
e61,
|
||||||
|
@JsonValue('Pod')
|
||||||
|
pod,
|
||||||
|
@JsonValue('Espresso Pod')
|
||||||
|
espressoPod,
|
||||||
|
@JsonValue('Grinder')
|
||||||
|
grinder,
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class Portafilter {
|
||||||
|
final String id;
|
||||||
|
final String size;
|
||||||
|
final String material;
|
||||||
|
|
||||||
|
Portafilter({required this.id, required this.size, required this.material});
|
||||||
|
|
||||||
|
factory Portafilter.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$PortafilterFromJson(json);
|
||||||
|
Map<String, dynamic> toJson() => _$PortafilterToJson(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class Machine {
|
||||||
|
final String id;
|
||||||
|
final String manufacturer;
|
||||||
|
final int year;
|
||||||
|
final String model;
|
||||||
|
final MachineType type;
|
||||||
|
final bool steamWand;
|
||||||
|
final String details;
|
||||||
|
final bool isOwned;
|
||||||
|
final double rating;
|
||||||
|
final int popularity;
|
||||||
|
final List<Portafilter> portafilters;
|
||||||
|
final Map<String, dynamic> specifications;
|
||||||
|
|
||||||
|
Machine({
|
||||||
|
required this.id,
|
||||||
|
required this.manufacturer,
|
||||||
|
required this.year,
|
||||||
|
required this.model,
|
||||||
|
required this.type,
|
||||||
|
required this.steamWand,
|
||||||
|
required this.details,
|
||||||
|
required this.isOwned,
|
||||||
|
required this.rating,
|
||||||
|
required this.popularity,
|
||||||
|
required this.portafilters,
|
||||||
|
required this.specifications,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Machine.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$MachineFromJson(json);
|
||||||
|
Map<String, dynamic> toJson() => _$MachineToJson(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class EspressoMachine extends Machine {
|
||||||
|
final String boilerType;
|
||||||
|
final String capacity;
|
||||||
|
final String? grinder;
|
||||||
|
final String heatSourceDescription;
|
||||||
|
final bool hopper;
|
||||||
|
final bool isDosing;
|
||||||
|
final int maxPressureBars;
|
||||||
|
final bool pressureGaugeIncluded;
|
||||||
|
final bool programmable;
|
||||||
|
final bool usesLever;
|
||||||
|
final int waterCapacity;
|
||||||
|
final Portafilter? portafilter;
|
||||||
|
|
||||||
|
EspressoMachine({
|
||||||
|
required super.id,
|
||||||
|
required super.manufacturer,
|
||||||
|
required super.year,
|
||||||
|
required super.model,
|
||||||
|
required super.type,
|
||||||
|
required super.steamWand,
|
||||||
|
required super.details,
|
||||||
|
required super.isOwned,
|
||||||
|
required super.rating,
|
||||||
|
required super.popularity,
|
||||||
|
required super.portafilters,
|
||||||
|
required super.specifications,
|
||||||
|
required this.boilerType,
|
||||||
|
required this.capacity,
|
||||||
|
this.grinder,
|
||||||
|
required this.heatSourceDescription,
|
||||||
|
required this.hopper,
|
||||||
|
required this.isDosing,
|
||||||
|
required this.maxPressureBars,
|
||||||
|
required this.pressureGaugeIncluded,
|
||||||
|
required this.programmable,
|
||||||
|
required this.usesLever,
|
||||||
|
required this.waterCapacity,
|
||||||
|
this.portafilter,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory EspressoMachine.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$EspressoMachineFromJson(json);
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() => _$EspressoMachineToJson(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class DripCoffee extends Machine {
|
||||||
|
final String brewerType;
|
||||||
|
final bool caraffe;
|
||||||
|
final String filterType;
|
||||||
|
final bool programmable;
|
||||||
|
|
||||||
|
DripCoffee({
|
||||||
|
required super.id,
|
||||||
|
required super.manufacturer,
|
||||||
|
required super.year,
|
||||||
|
required super.model,
|
||||||
|
required super.type,
|
||||||
|
required super.steamWand,
|
||||||
|
required super.details,
|
||||||
|
required super.isOwned,
|
||||||
|
required super.rating,
|
||||||
|
required super.popularity,
|
||||||
|
required super.portafilters,
|
||||||
|
required super.specifications,
|
||||||
|
required this.brewerType,
|
||||||
|
required this.caraffe,
|
||||||
|
required this.filterType,
|
||||||
|
required this.programmable,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory DripCoffee.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$DripCoffeeFromJson(json);
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() => _$DripCoffeeToJson(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class Grinder {
|
||||||
|
final String id;
|
||||||
|
final String burrType;
|
||||||
|
final int grindSettingsCount;
|
||||||
|
|
||||||
|
Grinder({
|
||||||
|
required this.id,
|
||||||
|
required this.burrType,
|
||||||
|
required this.grindSettingsCount,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Grinder.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$GrinderFromJson(json);
|
||||||
|
Map<String, dynamic> toJson() => _$GrinderToJson(this);
|
||||||
|
}
|
||||||
177
lib/models/machine.g.dart
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'machine.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
Portafilter _$PortafilterFromJson(Map<String, dynamic> json) => Portafilter(
|
||||||
|
id: json['id'] as String,
|
||||||
|
size: json['size'] as String,
|
||||||
|
material: json['material'] as String,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$PortafilterToJson(Portafilter instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'size': instance.size,
|
||||||
|
'material': instance.material,
|
||||||
|
};
|
||||||
|
|
||||||
|
Machine _$MachineFromJson(Map<String, dynamic> json) => Machine(
|
||||||
|
id: json['id'] as String,
|
||||||
|
manufacturer: json['manufacturer'] as String,
|
||||||
|
year: (json['year'] as num).toInt(),
|
||||||
|
model: json['model'] as String,
|
||||||
|
type: $enumDecode(_$MachineTypeEnumMap, json['type']),
|
||||||
|
steamWand: json['steamWand'] as bool,
|
||||||
|
details: json['details'] as String,
|
||||||
|
isOwned: json['isOwned'] as bool,
|
||||||
|
rating: (json['rating'] as num).toDouble(),
|
||||||
|
popularity: (json['popularity'] as num).toInt(),
|
||||||
|
portafilters: (json['portafilters'] as List<dynamic>)
|
||||||
|
.map((e) => Portafilter.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
specifications: json['specifications'] as Map<String, dynamic>,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$MachineToJson(Machine instance) => <String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'manufacturer': instance.manufacturer,
|
||||||
|
'year': instance.year,
|
||||||
|
'model': instance.model,
|
||||||
|
'type': _$MachineTypeEnumMap[instance.type]!,
|
||||||
|
'steamWand': instance.steamWand,
|
||||||
|
'details': instance.details,
|
||||||
|
'isOwned': instance.isOwned,
|
||||||
|
'rating': instance.rating,
|
||||||
|
'popularity': instance.popularity,
|
||||||
|
'portafilters': instance.portafilters,
|
||||||
|
'specifications': instance.specifications,
|
||||||
|
};
|
||||||
|
|
||||||
|
const _$MachineTypeEnumMap = {
|
||||||
|
MachineType.espresso: 'Espresso',
|
||||||
|
MachineType.drip: 'Drip',
|
||||||
|
MachineType.percolation: 'Percolation',
|
||||||
|
MachineType.frenchPress: 'French Press',
|
||||||
|
MachineType.coldBrew: 'Cold Brew',
|
||||||
|
MachineType.e61: 'E61',
|
||||||
|
MachineType.pod: 'Pod',
|
||||||
|
MachineType.espressoPod: 'Espresso Pod',
|
||||||
|
MachineType.grinder: 'Grinder',
|
||||||
|
};
|
||||||
|
|
||||||
|
EspressoMachine _$EspressoMachineFromJson(Map<String, dynamic> json) =>
|
||||||
|
EspressoMachine(
|
||||||
|
id: json['id'] as String,
|
||||||
|
manufacturer: json['manufacturer'] as String,
|
||||||
|
year: (json['year'] as num).toInt(),
|
||||||
|
model: json['model'] as String,
|
||||||
|
type: $enumDecode(_$MachineTypeEnumMap, json['type']),
|
||||||
|
steamWand: json['steamWand'] as bool,
|
||||||
|
details: json['details'] as String,
|
||||||
|
isOwned: json['isOwned'] as bool,
|
||||||
|
rating: (json['rating'] as num).toDouble(),
|
||||||
|
popularity: (json['popularity'] as num).toInt(),
|
||||||
|
portafilters: (json['portafilters'] as List<dynamic>)
|
||||||
|
.map((e) => Portafilter.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
specifications: json['specifications'] as Map<String, dynamic>,
|
||||||
|
boilerType: json['boilerType'] as String,
|
||||||
|
capacity: json['capacity'] as String,
|
||||||
|
grinder: json['grinder'] as String?,
|
||||||
|
heatSourceDescription: json['heatSourceDescription'] as String,
|
||||||
|
hopper: json['hopper'] as bool,
|
||||||
|
isDosing: json['isDosing'] as bool,
|
||||||
|
maxPressureBars: (json['maxPressureBars'] as num).toInt(),
|
||||||
|
pressureGaugeIncluded: json['pressureGaugeIncluded'] as bool,
|
||||||
|
programmable: json['programmable'] as bool,
|
||||||
|
usesLever: json['usesLever'] as bool,
|
||||||
|
waterCapacity: (json['waterCapacity'] as num).toInt(),
|
||||||
|
portafilter: json['portafilter'] == null
|
||||||
|
? null
|
||||||
|
: Portafilter.fromJson(json['portafilter'] as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$EspressoMachineToJson(EspressoMachine instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'manufacturer': instance.manufacturer,
|
||||||
|
'year': instance.year,
|
||||||
|
'model': instance.model,
|
||||||
|
'type': _$MachineTypeEnumMap[instance.type]!,
|
||||||
|
'steamWand': instance.steamWand,
|
||||||
|
'details': instance.details,
|
||||||
|
'isOwned': instance.isOwned,
|
||||||
|
'rating': instance.rating,
|
||||||
|
'popularity': instance.popularity,
|
||||||
|
'portafilters': instance.portafilters,
|
||||||
|
'specifications': instance.specifications,
|
||||||
|
'boilerType': instance.boilerType,
|
||||||
|
'capacity': instance.capacity,
|
||||||
|
'grinder': instance.grinder,
|
||||||
|
'heatSourceDescription': instance.heatSourceDescription,
|
||||||
|
'hopper': instance.hopper,
|
||||||
|
'isDosing': instance.isDosing,
|
||||||
|
'maxPressureBars': instance.maxPressureBars,
|
||||||
|
'pressureGaugeIncluded': instance.pressureGaugeIncluded,
|
||||||
|
'programmable': instance.programmable,
|
||||||
|
'usesLever': instance.usesLever,
|
||||||
|
'waterCapacity': instance.waterCapacity,
|
||||||
|
'portafilter': instance.portafilter,
|
||||||
|
};
|
||||||
|
|
||||||
|
DripCoffee _$DripCoffeeFromJson(Map<String, dynamic> json) => DripCoffee(
|
||||||
|
id: json['id'] as String,
|
||||||
|
manufacturer: json['manufacturer'] as String,
|
||||||
|
year: (json['year'] as num).toInt(),
|
||||||
|
model: json['model'] as String,
|
||||||
|
type: $enumDecode(_$MachineTypeEnumMap, json['type']),
|
||||||
|
steamWand: json['steamWand'] as bool,
|
||||||
|
details: json['details'] as String,
|
||||||
|
isOwned: json['isOwned'] as bool,
|
||||||
|
rating: (json['rating'] as num).toDouble(),
|
||||||
|
popularity: (json['popularity'] as num).toInt(),
|
||||||
|
portafilters: (json['portafilters'] as List<dynamic>)
|
||||||
|
.map((e) => Portafilter.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
specifications: json['specifications'] as Map<String, dynamic>,
|
||||||
|
brewerType: json['brewerType'] as String,
|
||||||
|
caraffe: json['caraffe'] as bool,
|
||||||
|
filterType: json['filterType'] as String,
|
||||||
|
programmable: json['programmable'] as bool,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$DripCoffeeToJson(DripCoffee instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'manufacturer': instance.manufacturer,
|
||||||
|
'year': instance.year,
|
||||||
|
'model': instance.model,
|
||||||
|
'type': _$MachineTypeEnumMap[instance.type]!,
|
||||||
|
'steamWand': instance.steamWand,
|
||||||
|
'details': instance.details,
|
||||||
|
'isOwned': instance.isOwned,
|
||||||
|
'rating': instance.rating,
|
||||||
|
'popularity': instance.popularity,
|
||||||
|
'portafilters': instance.portafilters,
|
||||||
|
'specifications': instance.specifications,
|
||||||
|
'brewerType': instance.brewerType,
|
||||||
|
'caraffe': instance.caraffe,
|
||||||
|
'filterType': instance.filterType,
|
||||||
|
'programmable': instance.programmable,
|
||||||
|
};
|
||||||
|
|
||||||
|
Grinder _$GrinderFromJson(Map<String, dynamic> json) => Grinder(
|
||||||
|
id: json['id'] as String,
|
||||||
|
burrType: json['burrType'] as String,
|
||||||
|
grindSettingsCount: (json['grindSettingsCount'] as num).toInt(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$GrinderToJson(Grinder instance) => <String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'burrType': instance.burrType,
|
||||||
|
'grindSettingsCount': instance.grindSettingsCount,
|
||||||
|
};
|
||||||
120
lib/models/recipe.dart
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
part 'recipe.g.dart';
|
||||||
|
|
||||||
|
enum ServingTemp {
|
||||||
|
@JsonValue('Hot')
|
||||||
|
hot,
|
||||||
|
@JsonValue('Cold')
|
||||||
|
cold,
|
||||||
|
@JsonValue('Iced')
|
||||||
|
iced,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MilkType {
|
||||||
|
@JsonValue('Whole')
|
||||||
|
whole,
|
||||||
|
@JsonValue('Skim')
|
||||||
|
skim,
|
||||||
|
@JsonValue('Soy')
|
||||||
|
soy,
|
||||||
|
@JsonValue('Almond')
|
||||||
|
almond,
|
||||||
|
@JsonValue('Coconut')
|
||||||
|
coconut,
|
||||||
|
@JsonValue('Oat')
|
||||||
|
oat,
|
||||||
|
@JsonValue('Pistachio')
|
||||||
|
pistachio,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum BrewMethod {
|
||||||
|
@JsonValue('Drip')
|
||||||
|
drip,
|
||||||
|
@JsonValue('French Press')
|
||||||
|
frenchPress,
|
||||||
|
@JsonValue('Pour Over')
|
||||||
|
pourOver,
|
||||||
|
@JsonValue('Espresso')
|
||||||
|
espresso,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum GrindSize {
|
||||||
|
@JsonValue('Fine')
|
||||||
|
fine,
|
||||||
|
@JsonValue('Medium')
|
||||||
|
medium,
|
||||||
|
@JsonValue('Coarse')
|
||||||
|
coarse,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Difficulty {
|
||||||
|
@JsonValue('Beginner')
|
||||||
|
beginner,
|
||||||
|
@JsonValue('Intermediate')
|
||||||
|
intermediate,
|
||||||
|
@JsonValue('Advanced')
|
||||||
|
advanced,
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class Recipe {
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
final ServingTemp servingTemp;
|
||||||
|
final MilkType? milkType;
|
||||||
|
final BrewMethod brewMethod;
|
||||||
|
final GrindSize grindSize;
|
||||||
|
final double coffeeAmount; // in grams
|
||||||
|
final double waterAmount; // in ml
|
||||||
|
final int brewTime; // in seconds
|
||||||
|
final String instructions;
|
||||||
|
final String? notes;
|
||||||
|
final Difficulty difficulty;
|
||||||
|
final List<String> equipmentNeeded;
|
||||||
|
final double yieldAmount;
|
||||||
|
final double caffeinePer100ml;
|
||||||
|
final int waterTemperature;
|
||||||
|
final int bloomTime;
|
||||||
|
final int totalExtractionTime;
|
||||||
|
final String grindToWaterRatio;
|
||||||
|
final List<String> tags;
|
||||||
|
final String origin;
|
||||||
|
final double rating;
|
||||||
|
final int popularity;
|
||||||
|
final String createdBy;
|
||||||
|
final bool isPublic;
|
||||||
|
final DateTime lastModified;
|
||||||
|
|
||||||
|
Recipe({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.servingTemp,
|
||||||
|
this.milkType,
|
||||||
|
required this.brewMethod,
|
||||||
|
required this.grindSize,
|
||||||
|
required this.coffeeAmount,
|
||||||
|
required this.waterAmount,
|
||||||
|
required this.brewTime,
|
||||||
|
required this.instructions,
|
||||||
|
this.notes,
|
||||||
|
required this.difficulty,
|
||||||
|
required this.equipmentNeeded,
|
||||||
|
required this.yieldAmount,
|
||||||
|
required this.caffeinePer100ml,
|
||||||
|
required this.waterTemperature,
|
||||||
|
required this.bloomTime,
|
||||||
|
required this.totalExtractionTime,
|
||||||
|
required this.grindToWaterRatio,
|
||||||
|
required this.tags,
|
||||||
|
required this.origin,
|
||||||
|
required this.rating,
|
||||||
|
required this.popularity,
|
||||||
|
required this.createdBy,
|
||||||
|
required this.isPublic,
|
||||||
|
required this.lastModified,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Recipe.fromJson(Map<String, dynamic> json) => _$RecipeFromJson(json);
|
||||||
|
Map<String, dynamic> toJson() => _$RecipeToJson(this);
|
||||||
|
}
|
||||||
102
lib/models/recipe.g.dart
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'recipe.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
Recipe _$RecipeFromJson(Map<String, dynamic> json) => Recipe(
|
||||||
|
id: json['id'] as String,
|
||||||
|
name: json['name'] as String,
|
||||||
|
servingTemp: $enumDecode(_$ServingTempEnumMap, json['servingTemp']),
|
||||||
|
milkType: $enumDecodeNullable(_$MilkTypeEnumMap, json['milkType']),
|
||||||
|
brewMethod: $enumDecode(_$BrewMethodEnumMap, json['brewMethod']),
|
||||||
|
grindSize: $enumDecode(_$GrindSizeEnumMap, json['grindSize']),
|
||||||
|
coffeeAmount: (json['coffeeAmount'] as num).toDouble(),
|
||||||
|
waterAmount: (json['waterAmount'] as num).toDouble(),
|
||||||
|
brewTime: (json['brewTime'] as num).toInt(),
|
||||||
|
instructions: json['instructions'] as String,
|
||||||
|
notes: json['notes'] as String?,
|
||||||
|
difficulty: $enumDecode(_$DifficultyEnumMap, json['difficulty']),
|
||||||
|
equipmentNeeded: (json['equipmentNeeded'] as List<dynamic>)
|
||||||
|
.map((e) => e as String)
|
||||||
|
.toList(),
|
||||||
|
yieldAmount: (json['yieldAmount'] as num).toDouble(),
|
||||||
|
caffeinePer100ml: (json['caffeinePer100ml'] as num).toDouble(),
|
||||||
|
waterTemperature: (json['waterTemperature'] as num).toInt(),
|
||||||
|
bloomTime: (json['bloomTime'] as num).toInt(),
|
||||||
|
totalExtractionTime: (json['totalExtractionTime'] as num).toInt(),
|
||||||
|
grindToWaterRatio: json['grindToWaterRatio'] as String,
|
||||||
|
tags: (json['tags'] as List<dynamic>).map((e) => e as String).toList(),
|
||||||
|
origin: json['origin'] as String,
|
||||||
|
rating: (json['rating'] as num).toDouble(),
|
||||||
|
popularity: (json['popularity'] as num).toInt(),
|
||||||
|
createdBy: json['createdBy'] as String,
|
||||||
|
isPublic: json['isPublic'] as bool,
|
||||||
|
lastModified: DateTime.parse(json['lastModified'] as String),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$RecipeToJson(Recipe instance) => <String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'name': instance.name,
|
||||||
|
'servingTemp': _$ServingTempEnumMap[instance.servingTemp]!,
|
||||||
|
'milkType': _$MilkTypeEnumMap[instance.milkType],
|
||||||
|
'brewMethod': _$BrewMethodEnumMap[instance.brewMethod]!,
|
||||||
|
'grindSize': _$GrindSizeEnumMap[instance.grindSize]!,
|
||||||
|
'coffeeAmount': instance.coffeeAmount,
|
||||||
|
'waterAmount': instance.waterAmount,
|
||||||
|
'brewTime': instance.brewTime,
|
||||||
|
'instructions': instance.instructions,
|
||||||
|
'notes': instance.notes,
|
||||||
|
'difficulty': _$DifficultyEnumMap[instance.difficulty]!,
|
||||||
|
'equipmentNeeded': instance.equipmentNeeded,
|
||||||
|
'yieldAmount': instance.yieldAmount,
|
||||||
|
'caffeinePer100ml': instance.caffeinePer100ml,
|
||||||
|
'waterTemperature': instance.waterTemperature,
|
||||||
|
'bloomTime': instance.bloomTime,
|
||||||
|
'totalExtractionTime': instance.totalExtractionTime,
|
||||||
|
'grindToWaterRatio': instance.grindToWaterRatio,
|
||||||
|
'tags': instance.tags,
|
||||||
|
'origin': instance.origin,
|
||||||
|
'rating': instance.rating,
|
||||||
|
'popularity': instance.popularity,
|
||||||
|
'createdBy': instance.createdBy,
|
||||||
|
'isPublic': instance.isPublic,
|
||||||
|
'lastModified': instance.lastModified.toIso8601String(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const _$ServingTempEnumMap = {
|
||||||
|
ServingTemp.hot: 'Hot',
|
||||||
|
ServingTemp.cold: 'Cold',
|
||||||
|
ServingTemp.iced: 'Iced',
|
||||||
|
};
|
||||||
|
|
||||||
|
const _$MilkTypeEnumMap = {
|
||||||
|
MilkType.whole: 'Whole',
|
||||||
|
MilkType.skim: 'Skim',
|
||||||
|
MilkType.soy: 'Soy',
|
||||||
|
MilkType.almond: 'Almond',
|
||||||
|
MilkType.coconut: 'Coconut',
|
||||||
|
MilkType.oat: 'Oat',
|
||||||
|
MilkType.pistachio: 'Pistachio',
|
||||||
|
};
|
||||||
|
|
||||||
|
const _$BrewMethodEnumMap = {
|
||||||
|
BrewMethod.drip: 'Drip',
|
||||||
|
BrewMethod.frenchPress: 'French Press',
|
||||||
|
BrewMethod.pourOver: 'Pour Over',
|
||||||
|
BrewMethod.espresso: 'Espresso',
|
||||||
|
};
|
||||||
|
|
||||||
|
const _$GrindSizeEnumMap = {
|
||||||
|
GrindSize.fine: 'Fine',
|
||||||
|
GrindSize.medium: 'Medium',
|
||||||
|
GrindSize.coarse: 'Coarse',
|
||||||
|
};
|
||||||
|
|
||||||
|
const _$DifficultyEnumMap = {
|
||||||
|
Difficulty.beginner: 'Beginner',
|
||||||
|
Difficulty.intermediate: 'Intermediate',
|
||||||
|
Difficulty.advanced: 'Advanced',
|
||||||
|
};
|
||||||
348
lib/providers/app_state.dart
Normal file
@ -0,0 +1,348 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import '../models/bean.dart';
|
||||||
|
import '../models/machine.dart';
|
||||||
|
import '../models/recipe.dart';
|
||||||
|
import '../models/drink.dart';
|
||||||
|
import '../models/journal_entry.dart';
|
||||||
|
import '../services/storage_service.dart';
|
||||||
|
import '../services/data_seeding_service.dart';
|
||||||
|
|
||||||
|
class AppState extends ChangeNotifier {
|
||||||
|
final StorageService _storageService = StorageService();
|
||||||
|
final DataSeedingService _dataSeedingService = DataSeedingService();
|
||||||
|
|
||||||
|
List<Bean> _beans = [];
|
||||||
|
List<Machine> _machines = [];
|
||||||
|
List<Recipe> _recipes = [];
|
||||||
|
List<Drink> _drinks = [];
|
||||||
|
List<JournalEntry> _journalEntries = [];
|
||||||
|
|
||||||
|
bool _isLoading = false;
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
List<Bean> get beans => _beans;
|
||||||
|
List<Machine> get machines => _machines;
|
||||||
|
List<Recipe> get recipes => _recipes;
|
||||||
|
List<Drink> get drinks => _drinks;
|
||||||
|
List<JournalEntry> get journalEntries => _journalEntries;
|
||||||
|
bool get isLoading => _isLoading;
|
||||||
|
|
||||||
|
// Preferred items
|
||||||
|
List<Bean> get preferredBeans =>
|
||||||
|
_beans.where((bean) => bean.preferred).toList();
|
||||||
|
List<Drink> get preferredDrinks =>
|
||||||
|
_drinks.where((drink) => drink.preferred).toList();
|
||||||
|
|
||||||
|
AppState() {
|
||||||
|
_loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadData() async {
|
||||||
|
_setLoading(true);
|
||||||
|
try {
|
||||||
|
// Seed initial data if needed (loads from CSV files)
|
||||||
|
await _dataSeedingService.seedInitialData();
|
||||||
|
|
||||||
|
_beans = await _storageService.getBeans();
|
||||||
|
_machines = await _storageService.getMachines();
|
||||||
|
_recipes = await _storageService.getRecipes();
|
||||||
|
_drinks = await _storageService.getDrinks();
|
||||||
|
_journalEntries = await _storageService.getJournalEntries();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error loading data: $e');
|
||||||
|
} finally {
|
||||||
|
_setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setLoading(bool loading) {
|
||||||
|
_isLoading = loading;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bean operations
|
||||||
|
Future<void> addBean(Bean bean) async {
|
||||||
|
try {
|
||||||
|
await _storageService.saveBean(bean);
|
||||||
|
// Reload beans from storage to keep data consistent
|
||||||
|
_beans = await _storageService.getBeans();
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error adding bean: $e');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateBean(Bean bean) async {
|
||||||
|
try {
|
||||||
|
await _storageService.saveBean(bean);
|
||||||
|
// Reload beans from storage to keep data consistent
|
||||||
|
_beans = await _storageService.getBeans();
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error updating bean: $e');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteBean(String id) async {
|
||||||
|
try {
|
||||||
|
await _storageService.deleteBean(id);
|
||||||
|
// Reload beans from storage to keep data consistent
|
||||||
|
_beans = await _storageService.getBeans();
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error deleting bean: $e');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Machine operations
|
||||||
|
Future<void> addMachine(Machine machine) async {
|
||||||
|
try {
|
||||||
|
await _storageService.saveMachine(machine);
|
||||||
|
// Reload machines from storage to keep data consistent
|
||||||
|
_machines = await _storageService.getMachines();
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error adding machine: $e');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateMachine(Machine machine) async {
|
||||||
|
try {
|
||||||
|
await _storageService.saveMachine(machine);
|
||||||
|
// Reload machines from storage to keep data consistent
|
||||||
|
_machines = await _storageService.getMachines();
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error updating machine: $e');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteMachine(String id) async {
|
||||||
|
try {
|
||||||
|
await _storageService.deleteMachine(id);
|
||||||
|
// Reload machines from storage to keep data consistent
|
||||||
|
_machines = await _storageService.getMachines();
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error deleting machine: $e');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recipe operations
|
||||||
|
Future<void> addRecipe(Recipe recipe) async {
|
||||||
|
try {
|
||||||
|
await _storageService.saveRecipe(recipe);
|
||||||
|
// Reload recipes from storage to keep data consistent
|
||||||
|
_recipes = await _storageService.getRecipes();
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error adding recipe: $e');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateRecipe(Recipe recipe) async {
|
||||||
|
try {
|
||||||
|
await _storageService.saveRecipe(recipe);
|
||||||
|
// Reload recipes from storage to keep data consistent
|
||||||
|
_recipes = await _storageService.getRecipes();
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error updating recipe: $e');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteRecipe(String id) async {
|
||||||
|
try {
|
||||||
|
await _storageService.deleteRecipe(id);
|
||||||
|
// Reload recipes from storage to keep data consistent
|
||||||
|
_recipes = await _storageService.getRecipes();
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error deleting recipe: $e');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drink operations
|
||||||
|
Future<void> addDrink(Drink drink) async {
|
||||||
|
try {
|
||||||
|
await _storageService.saveDrink(drink);
|
||||||
|
// Reload drinks from storage to keep data consistent
|
||||||
|
_drinks = await _storageService.getDrinks();
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error adding drink: $e');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateDrink(Drink drink) async {
|
||||||
|
try {
|
||||||
|
await _storageService.saveDrink(drink);
|
||||||
|
// Reload drinks from storage to keep data consistent
|
||||||
|
_drinks = await _storageService.getDrinks();
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error updating drink: $e');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteDrink(String id) async {
|
||||||
|
try {
|
||||||
|
await _storageService.deleteDrink(id);
|
||||||
|
// Reload drinks from storage to keep data consistent
|
||||||
|
_drinks = await _storageService.getDrinks();
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error deleting drink: $e');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Journal operations
|
||||||
|
Future<void> addJournalEntry(JournalEntry entry) async {
|
||||||
|
try {
|
||||||
|
await _storageService.saveJournalEntry(entry);
|
||||||
|
// Reload journal entries from storage to keep data consistent
|
||||||
|
_journalEntries = await _storageService.getJournalEntries();
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error adding journal entry: $e');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateJournalEntry(JournalEntry entry) async {
|
||||||
|
try {
|
||||||
|
await _storageService.saveJournalEntry(entry);
|
||||||
|
// Reload journal entries from storage to keep data consistent
|
||||||
|
_journalEntries = await _storageService.getJournalEntries();
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error updating journal entry: $e');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteJournalEntry(String id) async {
|
||||||
|
try {
|
||||||
|
await _storageService.deleteJournalEntry(id);
|
||||||
|
// Reload journal entries from storage to keep data consistent
|
||||||
|
_journalEntries = await _storageService.getJournalEntries();
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error deleting journal entry: $e');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search functionality
|
||||||
|
List<dynamic> searchAll(String query) {
|
||||||
|
final lowerQuery = query.toLowerCase();
|
||||||
|
final results = <dynamic>[];
|
||||||
|
|
||||||
|
// Search beans
|
||||||
|
results.addAll(
|
||||||
|
_beans.where(
|
||||||
|
(bean) =>
|
||||||
|
bean.name.toLowerCase().contains(lowerQuery) ||
|
||||||
|
bean.varietal.toLowerCase().contains(lowerQuery) ||
|
||||||
|
bean.originCountry?.details.toLowerCase().contains(lowerQuery) ==
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Search machines
|
||||||
|
results.addAll(
|
||||||
|
_machines.where(
|
||||||
|
(machine) =>
|
||||||
|
machine.manufacturer.toLowerCase().contains(lowerQuery) ||
|
||||||
|
machine.model.toLowerCase().contains(lowerQuery) ||
|
||||||
|
machine.details.toLowerCase().contains(lowerQuery),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Search recipes
|
||||||
|
results.addAll(
|
||||||
|
_recipes.where(
|
||||||
|
(recipe) =>
|
||||||
|
recipe.name.toLowerCase().contains(lowerQuery) ||
|
||||||
|
recipe.instructions.toLowerCase().contains(lowerQuery) ||
|
||||||
|
recipe.notes?.toLowerCase().contains(lowerQuery) == true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Search drinks
|
||||||
|
results.addAll(
|
||||||
|
_drinks.where(
|
||||||
|
(drink) =>
|
||||||
|
drink.name.toLowerCase().contains(lowerQuery) ||
|
||||||
|
drink.details.toLowerCase().contains(lowerQuery) ||
|
||||||
|
drink.notes.toLowerCase().contains(lowerQuery),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear all data
|
||||||
|
Future<void> clearAllData() async {
|
||||||
|
await _storageService.clearAllData();
|
||||||
|
_beans.clear();
|
||||||
|
_machines.clear();
|
||||||
|
_recipes.clear();
|
||||||
|
_drinks.clear();
|
||||||
|
_journalEntries.clear();
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear and reseed data for testing
|
||||||
|
Future<void> clearAndReseedData() async {
|
||||||
|
_setLoading(true);
|
||||||
|
try {
|
||||||
|
await _dataSeedingService.clearAndReseedData();
|
||||||
|
await _loadData();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error reseeding data: $e');
|
||||||
|
} finally {
|
||||||
|
_setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Catalog browsing operations
|
||||||
|
Future<List<Bean>> getAllAvailableBeans() async {
|
||||||
|
try {
|
||||||
|
return await _storageService.getAllAvailableBeans();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error loading available beans: $e');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Machine>> getAllAvailableMachines() async {
|
||||||
|
try {
|
||||||
|
return await _storageService.getAllAvailableMachines();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error loading available machines: $e');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Recipe>> getAllAvailableRecipes() async {
|
||||||
|
try {
|
||||||
|
return await _storageService.getAllAvailableRecipes();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error loading available recipes: $e');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
237
lib/screens/beans_screen.dart
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import '../providers/app_state.dart';
|
||||||
|
import '../models/bean.dart';
|
||||||
|
import '../components/bean_dialog.dart';
|
||||||
|
import '../components/searchable_selection.dart';
|
||||||
|
|
||||||
|
class BeansScreen extends StatelessWidget {
|
||||||
|
const BeansScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Consumer<AppState>(
|
||||||
|
builder: (context, appState, child) {
|
||||||
|
if (appState.isLoading) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
body: appState.beans.isEmpty
|
||||||
|
? const Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.coffee, size: 64, color: Colors.grey),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
Text('No beans in your collection yet'),
|
||||||
|
Text('Tap the + button to browse and add beans'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: ListView.builder(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
itemCount: appState.beans.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final bean = appState.beans[index];
|
||||||
|
return _buildBeanCard(context, bean);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
onPressed: () => _showAddBeanDialog(context),
|
||||||
|
child: const Icon(Icons.add),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildBeanCard(BuildContext context, Bean bean) {
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.only(bottom: 16),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
bean.name,
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (bean.preferred) const Icon(Icons.star, color: Colors.amber),
|
||||||
|
PopupMenuButton(
|
||||||
|
itemBuilder: (context) => [
|
||||||
|
const PopupMenuItem(
|
||||||
|
value: 'edit',
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.edit),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text('Edit'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const PopupMenuItem(
|
||||||
|
value: 'delete',
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.delete, color: Colors.red),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text('Delete', style: TextStyle(color: Colors.red)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onSelected: (value) {
|
||||||
|
if (value == 'edit') {
|
||||||
|
_showEditBeanDialog(context, bean);
|
||||||
|
} else if (value == 'delete') {
|
||||||
|
_showDeleteBeanDialog(context, bean);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text('Varietal: ${bean.varietal}'),
|
||||||
|
Text('Roast Level: ${bean.roastLevel.name}'),
|
||||||
|
Text('Processing: ${bean.processingMethod}'),
|
||||||
|
Text('Roasted: ${_formatDate(bean.roastDate)}'),
|
||||||
|
Text('Origin: ${bean.origin}'),
|
||||||
|
Text('Roaster: ${bean.roaster}'),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Wrap(
|
||||||
|
spacing: 4,
|
||||||
|
children: bean.flavorNotes
|
||||||
|
.map(
|
||||||
|
(note) => Chip(
|
||||||
|
label: Text(
|
||||||
|
note.name,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
backgroundColor: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.primary.withAlpha(51),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatDate(DateTime date) {
|
||||||
|
return '${date.day}/${date.month}/${date.year}';
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showAddBeanDialog(BuildContext context) {
|
||||||
|
_showBeanCatalog(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showBeanCatalog(BuildContext context) async {
|
||||||
|
final appState = context.read<AppState>();
|
||||||
|
|
||||||
|
// Get all available beans from catalog
|
||||||
|
final availableBeans = await appState.getAllAvailableBeans();
|
||||||
|
|
||||||
|
if (availableBeans.isEmpty) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('No beans available in catalog')),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => SearchableSelection<Bean>(
|
||||||
|
items: availableBeans,
|
||||||
|
title: 'Browse Bean Catalog',
|
||||||
|
displayText: (bean) => '${bean.name} - ${bean.origin}',
|
||||||
|
searchHint: 'Search by name, varietal, or origin...',
|
||||||
|
onItemSelected: (bean) async {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
|
||||||
|
// Check if bean is already in user's collection
|
||||||
|
if (appState.beans.any((b) => b.id == bean.id)) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('${bean.name} is already in your collection')),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add bean to user's collection
|
||||||
|
try {
|
||||||
|
await appState.addBean(bean);
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('${bean.name} added to your collection!')),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('Error adding bean: $e')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onAddCustom: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => const BeanDialog(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showEditBeanDialog(BuildContext context, Bean bean) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => BeanDialog(bean: bean),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showDeleteBeanDialog(BuildContext context, Bean bean) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text('Delete Bean'),
|
||||||
|
content: Text('Are you sure you want to delete "${bean.name}"?'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
try {
|
||||||
|
await Provider.of<AppState>(context, listen: false)
|
||||||
|
.deleteBean(bean.id);
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('Bean deleted successfully!')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('Error deleting bean: $e')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
|
||||||
|
child: const Text('Delete'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||