A senior-level Flutter portfolio application demonstrating Clean Architecture,
hybrid on-device/cloud AI routing, and cost-conscious engineering.
| Home | Editor | AI Actions |
|---|---|---|
![]() |
![]() |
![]() |
| Insights β Mood Tracker | Insights β Topic Distribution | AI Highlights |
|---|---|---|
![]() |
![]() |
![]() |
Three engineering decisions separate NoteAI from a typical Flutter CRUD app:
1. Selective Intelligence β A routing engine (AIOrchestrator) that decides in real time whether a task should run on-device (free, private) or in the cloud (powerful, tracked). This is not a feature flag β it is a decision tree that evaluates prompt complexity, task type, and monthly budget before every AI call.
2. The Budget Guardian β A financial protection system baked into the data layer. Every cloud AI call is intercepted by UsageRepositoryImpl, costed using TokenCalculator, and recorded atomically in Isar. At $8.00 the user sees a warning chip. At $10.00 the app enforces local-only mode β automatically, without any user action required. The guard lives in AIOrchestrator.route(), not in UI button handlers, so it is impossible to bypass by adding a new screen.
3. Strict layer boundaries β Domain, Data, and Presentation layers have zero cross-layer imports. The domain layer has no Flutter or infrastructure dependencies. Every AI action is a standalone use case β adding a new AI feature means adding a new file, not modifying existing ones.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PRESENTATION LAYER β
β β
β HomeScreen EditorScreen InsightsScreen FoldersScreen β
β SettingsScreen (GoRouter + fl_chart + flutter_animate) β
β β
β Riverpod Providers (@riverpod code generation) β
β notesNotifierProvider β aiOrchestratorProvider β
β summarizeNotifier insightsNotifier settingsProvider β
βββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββ
β ref.watch / ref.read
βββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββ
β DOMAIN LAYER β
β (zero Flutter / infrastructure imports) β
β β
β Entities: Note, AiUsage, InsightsData, AiResult β
β Use Cases: SummarizeNote, ExtractTasks, ChangeTone, β
β ExpandIdea, FixGrammar, Translate, FormatText, β
β GenerateInsights, GetNotes, SaveNote, DeleteNote β
β β
β βββββββββββββββββββββββββββββββββ β
β β AIOrchestrator β The Brain β
β β β β
β β route(prompt, task) β β
β β 1. canUseCloudAI? (budget) β β
β β 2. isEligibleForLocal? β β
β β (task type + char length) β β
β β 3. recordUsage after cloud β β
β ββββββββ¬βββββββββββββββ¬ββββββββββ β
β β β β
β Local? β β Cloud? β
β (free, $0) β β (tracked, budgeted) β
ββββββββββββββββββββΌβββββββββββββββΌββββββββββββββββββββββββββββββ
β β delegates
ββββββββββββββββββββΌβββββββββββββββΌββββββββββββββββββββββββββββββ
β DATA LAYER β
β β
β ββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββ β
β β GemmaDataSourceImpl β β GeminiRemoteDataSource β β
β β β β β β
β β Primary: Gemma Nano β β Routes via Vercel proxy β β
β β (Android AICore) β β Gemini 1.5 Flash (simple) β β
β β Fallback: extractive β β Gemini 1.5 Pro (complex) β β
β β summarization (Dart) β β Uses TokenCalculator for β β
β ββββββββββββββββββββββββββββ β cost estimation β β
β βββββββββββββ¬βββββββββββββββββ β
β β β
β ββββββββββββββββββββββββββββββββββββββββββββΌβββββββββββββββ β
β β UsageRepositoryImpl (Budget Guardian) β β
β β β β
β β isBudgetExceeded() β reads Isar, compares $10 cap β β
β β recordApiCall() β Read-Modify-Write (atomic) β β
β β getCurrentMonthUsage() β AiUsageModel.toEntity() β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Isar Local Database β β
β β β β
β β NoteModel @collection β β
β β βββ @Index() category (O(1) filter by category) β β
β β βββ @Index(type: value) updatedAt (sorted queries) β β
β β β β
β β AiUsageModel @collection β β
β β βββ month + year fields (monthly budget tracking) β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
User triggers AI action
β
βΌ
AIOrchestrator.route(prompt, task)
β
βΌ
usageRepository.isBudgetExceeded()?
β
YES ($10 reached) βββββββββββββββββββββββ _routeToLocalAi()
β β
β NO ($0β$9.99) β isAvailable()?
βΌ β
_isEligibleForLocalAi(task, prompt)? NO βββ Exception
β (UI shows budget-exceeded msg)
task β {summarize, extractTasks}
AND prompt.length < 500 chars
β
YES β NO
βΌ βΌ
_routeToLocalAi() _routeToCloudAi()
β β
isAvailable()? task β {generateInsights,
β changeTone}?
NO β β
β YES β NO (expand, fixGrammar,
βΌ βΌ translate, formatText)
throws Exception Gemini Pro βΌ
β β Gemini Flash
catch in route() ββββββ¬ββββββββ
β β
βΌ TokenCalculator
_routeToCloudAi() .calculateCost()
(graceful fallback) β
recordApiCall()
(Isar atomic write)
β
AiResult{text, model,
tokensUsed, costUsd}
| Layer | Technology | Why |
|---|---|---|
| UI Framework | Flutter 3.x + Material 3 | Cross-platform, Dynamic Color (Monet on Android 12+) |
| State Management | Riverpod 2.x (code gen @riverpod) |
Compile-safe providers, zero manual boilerplate |
| Local Database | Isar 3.x | Type-safe queries, native List<double> for embeddings, no migrations |
| Cloud AI | Gemini 1.5 Flash + Pro via Vercel proxy | Flash for cost, Pro for quality β routed by task type. Proxy keeps API key off the device |
| On-device AI | Gemma Nano (flutter_gemma, pending) | Zero cost, offline, private |
| Local AI Fallback | Extractive Dart algorithm | Zero dependencies, works on all platforms while Gemma matures |
| Charts | fl_chart 0.68 | Fully customizable LineChart + PieChart |
| Animations | flutter_animate 4.x | Declarative staggered animations, shimmer skeleton loading |
| Navigation | GoRouter 14.x | Declarative routing, deep-link ready |
| Theming | dynamic_color | Monet wallpaper-based color extraction |
| Preferences | shared_preferences | Persistent user settings (theme, budget cap, language) |
| Immutable data | freezed + json_serializable | Pattern-matching sealed classes, code-generated fromJson |
| Environment | flutter_dotenv | API keys in .env, never committed to source control |
- Create, edit, and organize notes with categories (Work, Ideas, Personal, Creative)
- Masonry grid layout with glassmorphic cards and staggered entrance animations
- Inline markdown formatting toolbar in the editor
| Action | Routes to | Notes |
|---|---|---|
| Summarize | Gemma Nano / Gemini Flash | Free for notes < 500 chars (extractive fallback) |
| Extract Tasks | Gemma Nano / Gemini Flash | Matches -, β’, β, β‘, numbered lists |
| Change Tone | Gemini Pro | Professional rewrite β always generative |
| Expand Idea | Gemini Flash | Turns a short note into full paragraphs |
| Fix Grammar | Gemini Flash | Spell-check and grammar correction |
| Translate | Gemini Flash | 10 languages: EN, ES, FR, DE, PT, IT, ZH, JA, AR, RU |
| Format Text | Gemini Flash | Cleans up and converts to Markdown |
- Mood trend line chart over 7 days (fl_chart
LineChartwithFlSpotdata) - Topic distribution donut chart (fl_chart
PieChartwithTopicSlicedomain entities) - AI-generated highlight cards: growth patterns and burnout alerts
- Goals detected in notes with status tracking (in-progress / completed / at-risk)
- Recommendations and weekly summary generated by Gemini Pro
- Real-time budget chip:
AI Budget: $X.XXX / $10.00with warning color at $8.00 - Skeleton shimmer loading state (
flutter_animate .shimmer()) while Gemini Pro responds
- Every insight analysis is persisted as a JSON snapshot with timestamp
- Chronological history lets users compare how their notes evolve over time
- Theme mode (light / dark / system)
- Configurable budget cap and warning threshold
- Default translation language
- Number of notes to include in insights analysis
- Default note category
- Confirm-before-delete toggle
- Export notes and clear AI history
- Hard cap: $10.00 USD/month on cloud AI (
AppConstants.monthlyBudgetLimitUsd) - Warning threshold: $8.00 (
AppConstants.budgetWarningThresholdUsd) - Automatic local-only mode when cap is reached β enforced in
AIOrchestrator, not UI - Atomic Read-Modify-Write in
UsageRepositoryImpl.recordApiCall()to prevent race conditions
lib/
βββ core/
β βββ constants/
β β βββ app_constants.dart # AppConstants + AiTask enum (budget limits, thresholds)
β β βββ api_constants.dart # Model IDs (gemini-1.5-flash, gemini-1.5-pro)
β βββ errors/ # Base Failure + Exception sealed classes
β βββ extensions/ # StringExtensions, ContextExtensions
β βββ utils/
β βββ token_calculator.dart # TokenCalculator (cost math, pure static)
β βββ network_info.dart # Connectivity checking
β
βββ domain/ # Pure Dart β zero framework dependencies
β βββ entities/
β β βββ note.dart # Note entity (id, title, content, embeddings)
β β βββ ai_usage.dart # AiUsage (monthly tokens, costUsd, isBudgetExceeded)
β β βββ insights_data.dart # InsightsData (moodHistory, topicDistribution, highlights,
β β # goals, recommendations, weeklySummary)
β βββ repositories/
β β βββ note_repository.dart # Abstract NoteRepository interface
β β βββ ai_repository.dart # Abstract AiRepository (processLocal, processCloud)
β β βββ usage_repository.dart # Abstract UsageRepository (isBudgetExceeded, record)
β βββ failures/
β β βββ failures.dart # Sealed Failure hierarchy (DatabaseFailure, AIFailureβ¦)
β βββ usecases/
β βββ ai_orchestrator.dart # AIOrchestrator β central routing engine
β βββ summarize_note_usecase.dart
β βββ extract_tasks_usecase.dart
β βββ change_tone_usecase.dart
β βββ expand_idea_usecase.dart
β βββ fix_grammar_usecase.dart
β βββ translate_usecase.dart
β βββ format_text_usecase.dart
β βββ generate_insights_usecase.dart
β βββ get_notes_usecase.dart
β βββ save_note_usecase.dart
β βββ delete_note_usecase.dart
β
βββ data/
β βββ datasources/
β β βββ local/
β β β βββ isar_datasource.dart # IsarDataSource (CRUD + usage queries)
β β β βββ gemma_datasource.dart # GemmaDataSourceImpl (on-device + fallback)
β β βββ remote/
β β βββ gemini_datasource.dart # GeminiRemoteDataSource (via Vercel proxy)
β βββ models/
β β βββ note_model.dart # @collection NoteModel with fromEntity/toEntity
β β βββ note_model.g.dart # Generated by isar_generator
β β βββ ai_usage_model.dart # @collection AiUsageModel
β β βββ ai_usage_model.g.dart
β βββ repositories/
β βββ note_repository_impl.dart # Concrete NoteRepository
β βββ ai_repository_impl.dart # Concrete AiRepository (delegates to sources)
β βββ usage_repository_impl.dart # Concrete UsageRepository (Budget Guardian)
β
βββ presentation/
βββ screens/
β βββ home_screen.dart # Masonry grid, CategoryFilterChip, AiMagicFab
β βββ editor_screen.dart # TextField, MarkdownToolbar, AiOverlayMenu (7 actions)
β βββ insights_screen.dart # Dashboard: MoodTrackerCard, TopicDonutChart, highlights
β βββ folders_screen.dart # Chronological history of insight snapshots
β βββ settings_screen.dart # User preferences (theme, AI, notes, data, about)
βββ widgets/
β βββ note_card.dart
β βββ category_filter_chip.dart
β βββ ai_magic_fab.dart
β βββ ai_overlay_menu.dart # 7 AI actions glassmorphic overlay
β βββ markdown_toolbar.dart
β βββ mood_tracker_card.dart
β βββ topic_donut_chart.dart
β βββ ai_highlight_card.dart
β βββ stat_card.dart
βββ providers/
β βββ notes_provider.dart # notesNotifierProvider, usageRepositoryProvider
β βββ notes_provider.g.dart
β βββ ai_provider.dart # aiOrchestratorProvider, all action notifiers
β βββ ai_provider.g.dart
β βββ settings_provider.dart # settingsProvider (SharedPreferences-backed)
β βββ settings_provider.g.dart
βββ theme/
βββ app_theme.dart # Material 3 ThemeData with dynamic_color
βββ app_colors.dart # Seed color + semantic color tokens
- Flutter 3.x SDK (
flutter --version) - Android Studio or VS Code with the Flutter extension
- A Google AI Studio API key (get yours free)
- Android API 24+ recommended (for future Gemma Nano via Android AICore)
The app calls Gemini through a Vercel proxy. The backend repo is kept private to avoid exposing API keys. To run locally, deploy your own proxy or point
VERCEL_API_URLto a local server.
# 1. Clone the repository
git clone https://github.com/Hugo-Dev1/noteai.git
cd noteai/noteai
# 2. Configure environment variables
cp .env.example .env
# Edit .env:
# GEMINI_API_KEY=your_key_here
# VERCEL_API_URL=your_vercel_proxy_url
# VERCEL_API_SECRET=your_proxy_secret
# 3. Install dependencies
flutter pub get
# 4. Generate code (Riverpod providers + Isar schemas)
dart run build_runner build --delete-conflicting-outputs
# 5. Run the app
flutter runThe
.envfile is loaded byflutter_dotenvand declared as an asset inpubspec.yaml. It is listed in.gitignoreand must never be committed.
NoteAI is designed for near-zero monthly AI costs. Every cloud call is tracked in Isar and blocked when the $10 hard cap is reached.
| Scenario | Model Used | Estimated Cost |
|---|---|---|
| Summarize a short note (< 500 chars) | Extractive fallback (Dart) | $0.00 |
| Summarize a long note | Gemini 1.5 Flash | ~$0.00001 |
| Extract tasks from a note | Extractive fallback (Dart) | $0.00 |
| Change tone (professional) | Gemini 1.5 Flash | ~$0.00002 |
| Expand / Fix Grammar / Translate / Format | Gemini 1.5 Flash | ~$0.00001β$0.00003 |
| Generate insights (up to 20 notes) | Gemini 1.5 Pro | ~$0.001β$0.01 |
Pricing constants (sourced from TokenCalculator and AppConstants):
- Gemini 1.5 Flash: $0.075 / 1M input tokens, $0.30 / 1M output tokens
- Gemini 1.5 Pro: $1.25 / 1M input tokens, $5.00 / 1M output tokens
- Token estimation:
text.length / 4(standard 4-chars-per-token heuristic for English)
Realistic monthly estimate for daily use: < $1.00
flutter test200 tests β 200 passing β 0 failures across:
- Domain layer: use cases, AIOrchestrator routing logic
- Data layer: repository implementations, Budget Guardian
- Presentation: widget tests, provider state tests
All mocks are written by hand β no Mockito dependency.
CI runs on every push via .github/workflows/ci.yml:
- Security check (
.envnot committed) flutter analyze --fatal-infosflutter test
- Stable
flutter_gemmaintegration when Android AICore reaches broad availability - MediaPipe TextEmbedder for fully on-device semantic search (using stored
embeddingsfield inNoteModel) - Golden tests for all widgets
- Note export (PDF, Markdown)
- iPad / tablet responsive layout with adaptive navigation rail
NoteAI was built as a portfolio demonstration of senior-level Flutter engineering. Every major technical decision is documented with its rationale in DECISIONS.md.






