From 5ff8e8b672266dbc7b6144f5f44582ecd89304f1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Feb 2026 13:53:27 +0000 Subject: [PATCH 1/2] Initial plan From 4807d6cc1a29af88a5ba3fd205644035624cb535 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Feb 2026 13:57:03 +0000 Subject: [PATCH 2/2] feat: establish clean architecture folder structure and core files Co-authored-by: Muhammad-Bilal-03 <248140505+Muhammad-Bilal-03@users.noreply.github.com> --- .github/workflows/ci.yml | 32 ++++ ADR/001-hybrid-architecture.md | 81 +++++++++ README.md | 157 +++++++++++++++++- lib/app.dart | 63 +++++++ lib/core/constants/app_constants.dart | 14 ++ lib/core/errors/failures.dart | 24 +++ lib/core/theme/app_theme.dart | 91 ++++++++++ lib/core/utils/extensions.dart | 43 +++++ lib/features/chat/data/.gitkeep | 0 lib/features/chat/domain/.gitkeep | 0 lib/features/chat/presentation/.gitkeep | 0 lib/features/notes/data/datasources/.gitkeep | 0 lib/features/notes/data/models/.gitkeep | 0 lib/features/notes/data/repositories/.gitkeep | 0 lib/features/notes/domain/entities/.gitkeep | 0 .../notes/domain/repositories/.gitkeep | 0 lib/features/notes/domain/usecases/.gitkeep | 0 .../notes/presentation/providers/.gitkeep | 0 .../notes/presentation/screens/.gitkeep | 0 .../notes/presentation/widgets/.gitkeep | 0 lib/features/search/data/.gitkeep | 0 lib/features/search/domain/.gitkeep | 0 lib/features/search/presentation/.gitkeep | 0 lib/features/voice/data/.gitkeep | 0 lib/features/voice/domain/.gitkeep | 0 lib/features/voice/presentation/.gitkeep | 0 lib/main.dart | 129 ++------------ lib/shared/providers/.gitkeep | 0 lib/shared/widgets/.gitkeep | 0 pubspec.yaml | 99 +++-------- test/widget_test.dart | 26 ++- 31 files changed, 552 insertions(+), 207 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 ADR/001-hybrid-architecture.md create mode 100644 lib/app.dart create mode 100644 lib/core/constants/app_constants.dart create mode 100644 lib/core/errors/failures.dart create mode 100644 lib/core/theme/app_theme.dart create mode 100644 lib/core/utils/extensions.dart create mode 100644 lib/features/chat/data/.gitkeep create mode 100644 lib/features/chat/domain/.gitkeep create mode 100644 lib/features/chat/presentation/.gitkeep create mode 100644 lib/features/notes/data/datasources/.gitkeep create mode 100644 lib/features/notes/data/models/.gitkeep create mode 100644 lib/features/notes/data/repositories/.gitkeep create mode 100644 lib/features/notes/domain/entities/.gitkeep create mode 100644 lib/features/notes/domain/repositories/.gitkeep create mode 100644 lib/features/notes/domain/usecases/.gitkeep create mode 100644 lib/features/notes/presentation/providers/.gitkeep create mode 100644 lib/features/notes/presentation/screens/.gitkeep create mode 100644 lib/features/notes/presentation/widgets/.gitkeep create mode 100644 lib/features/search/data/.gitkeep create mode 100644 lib/features/search/domain/.gitkeep create mode 100644 lib/features/search/presentation/.gitkeep create mode 100644 lib/features/voice/data/.gitkeep create mode 100644 lib/features/voice/domain/.gitkeep create mode 100644 lib/features/voice/presentation/.gitkeep create mode 100644 lib/shared/providers/.gitkeep create mode 100644 lib/shared/widgets/.gitkeep diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..fdfd420 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.38.4' + channel: 'stable' + + - name: Install dependencies + run: flutter pub get + + - name: Verify formatting + run: dart format --output=none --set-exit-if-changed . + + - name: Analyze code + run: flutter analyze + + - name: Run tests + run: flutter test diff --git a/ADR/001-hybrid-architecture.md b/ADR/001-hybrid-architecture.md new file mode 100644 index 0000000..79658a2 --- /dev/null +++ b/ADR/001-hybrid-architecture.md @@ -0,0 +1,81 @@ +# ADR 001: Use Hybrid (Local-First + Cloud Sync) Architecture + +**Status:** Accepted + +**Date:** 2026-02-11 + +**Context:** + +When building Second Brain, we needed to choose between three architectural approaches: + +1. **Pure Local**: All data stored locally with no cloud integration +2. **Pure Cloud**: All data stored in the cloud, requiring constant internet connection +3. **Hybrid (Local-First + Cloud Sync)**: Local storage with optional cloud synchronization + +Key considerations: +- User experience and app responsiveness +- Offline capability requirements +- Cross-device synchronization needs +- Privacy and data ownership concerns +- Demonstration of technical skills +- Scalability and future features (RAG, embeddings) + +**Decision:** + +We will implement a **Hybrid (Local-First + Cloud Sync)** architecture using: +- **Isar DB** for local storage (fast, NoSQL, embedded database) +- **Supabase** with **pgvector** for optional cloud sync and vector embeddings + +**Rationale:** + +1. **Best User Experience** + - Instant app response with local data access + - No loading spinners for basic operations + - Works perfectly offline + +2. **Optional Cloud Sync** + - Users can enable sync for cross-device access + - Privacy-focused: users control their data + - Graceful degradation when offline + +3. **Technical Excellence** + - Demonstrates proficiency with both local and cloud databases + - Showcases sync conflict resolution skills + - Enables advanced features (pgvector for semantic search) + +4. **Future-Proof** + - Scalable for RAG features (embeddings need vector DB) + - Supports collaborative features in future + - Easy to add cloud-only features later + +**Consequences:** + +### Positive +- ✅ Superior user experience with instant local operations +- ✅ Works offline by default +- ✅ Demonstrates advanced technical skills +- ✅ Enables semantic search with vector embeddings +- ✅ Users maintain data ownership + +### Negative +- ❌ Increased complexity in implementation +- ❌ Need to handle sync conflicts +- ❌ More testing scenarios (online/offline states) +- ❌ Requires maintaining two database schemas + +### Mitigation +- Use proven sync patterns (CRDTs or timestamp-based) +- Implement comprehensive error handling +- Create thorough test coverage for sync scenarios +- Document sync behavior clearly for users + +**Alternatives Considered:** + +1. **Pure Local** — Simpler but no cross-device sync +2. **Pure Cloud** — Poor offline experience, slower operations +3. **Firebase** — Considered but Supabase chosen for pgvector support + +**Related Decisions:** +- Technology choice: Isar DB for local storage +- Technology choice: Supabase + pgvector for cloud +- Will require: Sync strategy ADR in future (Phase 4) diff --git a/README.md b/README.md index 01ce25e..7d3c65c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,155 @@ -# second_brain -🧠 RAG-Powered Note Taking App — Chat with your notes using AI +# 🧠 Second Brain + +**RAG-Powered Note Taking App — Chat with your notes using AI** + +[![Flutter](https://img.shields.io/badge/Flutter-3.38.4-02569B?logo=flutter)](https://flutter.dev) +[![Dart](https://img.shields.io/badge/Dart-3.10.3-0175C2?logo=dart)](https://dart.dev) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) +[![CI](https://github.com/Muhammad-Bilal-03/second_brain/actions/workflows/ci.yml/badge.svg)](https://github.com/Muhammad-Bilal-03/second_brain/actions/workflows/ci.yml) + +## 📖 Overview + +Second Brain is a local-first, AI-powered note-taking application that lets you "chat" with your notes using Retrieval Augmented Generation (RAG). Built with Flutter for cross-platform support, it combines the power of local storage with optional cloud sync for a seamless note-taking experience. + +## ✨ Features (Planned) + +- 📝 **Notes CRUD** — Create, read, update, and delete notes with a clean interface +- 🔍 **Semantic Search** — Find notes using natural language queries powered by embeddings +- 💬 **RAG Chat** — Have conversations with your notes using AI (Google Gemini) +- 🎤 **Voice-to-Note** — Convert speech to text for quick note capture +- ☁️ **Cloud Sync** — Optional synchronization with Supabase (pgvector for embeddings) +- 🌓 **Dark Mode** — Beautiful Material 3 theme with light and dark modes + +## 🛠️ Tech Stack + +| Category | Technology | +|----------|-----------| +| **Framework** | Flutter 3.38.4 | +| **Language** | Dart 3.10.3 | +| **State Management** | Riverpod 2.6+ | +| **Local Database** | Isar DB 4.0 | +| **AI Framework** | LangChain.dart | +| **LLM** | Google Gemini API | +| **Cloud Backend** | Supabase (pgvector) | + +## 🏗️ Architecture + +Second Brain follows **Clean Architecture** principles combined with **MVVM** pattern in a feature-based folder structure: + +``` +lib/ +├── app.dart # App root widget +├── main.dart # Entry point +├── core/ # Core utilities +│ ├── constants/ # App constants +│ ├── errors/ # Error handling +│ ├── theme/ # App theme +│ └── utils/ # Extensions & helpers +├── features/ # Feature modules +│ ├── notes/ # Notes feature +│ │ ├── data/ # Data sources & models +│ │ ├── domain/ # Entities & use cases +│ │ └── presentation/ # UI & state +│ ├── chat/ # AI chat feature +│ ├── search/ # Semantic search +│ └── voice/ # Voice input +└── shared/ # Shared widgets & providers +``` + +### Key Architecture Decisions + +- **Local-First**: All data stored locally in Isar DB for instant access +- **Optional Cloud Sync**: Supabase integration for cross-device synchronization +- **Clean Separation**: Domain logic isolated from UI and data layers +- **Testable**: Architecture enables comprehensive unit and integration testing + +## 🚀 Getting Started + +### Prerequisites + +- Flutter SDK 3.38.4 or higher +- Dart SDK 3.10.3 or higher +- Android Studio / VS Code with Flutter extensions +- Git + +### Installation + +1. **Clone the repository** + ```bash + git clone https://github.com/Muhammad-Bilal-03/second_brain.git + cd second_brain + ``` + +2. **Install dependencies** + ```bash + flutter pub get + ``` + +3. **Run the app** + ```bash + flutter run + ``` + +### Running Tests + +```bash +flutter test +``` + +### Code Generation + +For Riverpod and Isar code generation: + +```bash +dart run build_runner build --delete-conflicting-outputs +``` + +## 🗺️ Roadmap + +### Phase 1: Foundation (Notes CRUD) ⬅️ **Current** +- [x] Project setup with clean architecture +- [x] Core dependencies and folder structure +- [x] CI/CD with GitHub Actions +- [ ] Basic notes CRUD with Isar DB +- [ ] Material 3 UI with dark mode + +### Phase 2: Intelligence Layer (Embeddings + Vector Search) +- [ ] Text embedding generation +- [ ] Vector similarity search +- [ ] Semantic search UI + +### Phase 3: RAG Chat (LangChain.dart + Gemini) +- [ ] LangChain.dart integration +- [ ] Google Gemini API setup +- [ ] RAG pipeline implementation +- [ ] Chat UI with conversation history + +### Phase 4: Cloud Sync (Supabase + pgvector) +- [ ] Supabase backend setup +- [ ] pgvector for cloud embeddings +- [ ] Sync engine implementation +- [ ] Conflict resolution + +### Phase 5: Voice-to-Note +- [ ] Speech-to-text integration +- [ ] Voice recording UI +- [ ] Real-time transcription + +### Phase 6: Polish & Ship +- [ ] Performance optimization +- [ ] Comprehensive testing +- [ ] User documentation +- [ ] App store deployment + +## 📄 License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## 👨‍💻 Author + +**Muhammad Bilal** +- GitHub: [@Muhammad-Bilal-03](https://github.com/Muhammad-Bilal-03) + +--- + +*Built with ❤️ using Flutter* diff --git a/lib/app.dart b/lib/app.dart new file mode 100644 index 0000000..652b85d --- /dev/null +++ b/lib/app.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:second_brain/core/constants/app_constants.dart'; +import 'package:second_brain/core/theme/app_theme.dart'; + +class App extends ConsumerWidget { + const App({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return MaterialApp( + title: AppConstants.appName, + debugShowCheckedModeBanner: false, + theme: AppTheme.lightTheme, + darkTheme: AppTheme.darkTheme, + themeMode: ThemeMode.system, + home: const PlaceholderHome(), + ); + } +} + +/// Placeholder home screen +class PlaceholderHome extends StatelessWidget { + const PlaceholderHome({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('🧠 Second Brain'), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.psychology, + size: 100, + color: Theme.of(context).colorScheme.primary, + ), + const SizedBox(height: 24), + Text( + 'Welcome to Second Brain', + style: Theme.of(context).textTheme.headlineMedium, + ), + const SizedBox(height: 8), + Text( + 'Your AI-powered note-taking companion', + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), + ), + ), + const SizedBox(height: 32), + const Text( + 'Coming soon...', + style: TextStyle(fontStyle: FontStyle.italic), + ), + ], + ), + ), + ); + } +} diff --git a/lib/core/constants/app_constants.dart b/lib/core/constants/app_constants.dart new file mode 100644 index 0000000..7c8c7c6 --- /dev/null +++ b/lib/core/constants/app_constants.dart @@ -0,0 +1,14 @@ +class AppConstants { + // App Information + static const String appName = 'Second Brain'; + static const String appVersion = '0.1.0'; + + // Database + static const String dbName = 'second_brain_db'; + + // API + static const int apiTimeout = 30; + + // Private constructor to prevent instantiation + AppConstants._(); +} diff --git a/lib/core/errors/failures.dart b/lib/core/errors/failures.dart new file mode 100644 index 0000000..ee1832c --- /dev/null +++ b/lib/core/errors/failures.dart @@ -0,0 +1,24 @@ +/// Base class for all failures in the application +abstract class Failure { + final String message; + + const Failure(this.message); + + @override + String toString() => message; +} + +/// Failure when server communication fails +class ServerFailure extends Failure { + const ServerFailure([String message = 'Server error occurred']) : super(message); +} + +/// Failure when cache operations fail +class CacheFailure extends Failure { + const CacheFailure([String message = 'Cache error occurred']) : super(message); +} + +/// General failure for unexpected errors +class GeneralFailure extends Failure { + const GeneralFailure([String message = 'An unexpected error occurred']) : super(message); +} diff --git a/lib/core/theme/app_theme.dart b/lib/core/theme/app_theme.dart new file mode 100644 index 0000000..37171b3 --- /dev/null +++ b/lib/core/theme/app_theme.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; + +class AppTheme { + // Purple/Indigo color scheme for brain theme + static const Color primaryColor = Color(0xFF6B4EFF); + static const Color secondaryColor = Color(0xFF9D7FFF); + static const Color accentColor = Color(0xFFB794F6); + + // Light theme + static ThemeData get lightTheme { + return ThemeData( + useMaterial3: true, + colorScheme: ColorScheme.fromSeed( + seedColor: primaryColor, + brightness: Brightness.light, + ), + appBarTheme: const AppBarTheme( + centerTitle: true, + elevation: 0, + ), + cardTheme: CardTheme( + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + inputDecorationTheme: InputDecorationTheme( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + filled: true, + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ), + floatingActionButtonTheme: FloatingActionButtonThemeData( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + ), + ); + } + + // Dark theme + static ThemeData get darkTheme { + return ThemeData( + useMaterial3: true, + colorScheme: ColorScheme.fromSeed( + seedColor: primaryColor, + brightness: Brightness.dark, + ), + appBarTheme: const AppBarTheme( + centerTitle: true, + elevation: 0, + ), + cardTheme: CardTheme( + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + inputDecorationTheme: InputDecorationTheme( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + filled: true, + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ), + floatingActionButtonTheme: FloatingActionButtonThemeData( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + ), + ); + } + + // Private constructor to prevent instantiation + AppTheme._(); +} diff --git a/lib/core/utils/extensions.dart b/lib/core/utils/extensions.dart new file mode 100644 index 0000000..bf1e57b --- /dev/null +++ b/lib/core/utils/extensions.dart @@ -0,0 +1,43 @@ +import 'package:intl/intl.dart'; + +/// String extensions +extension StringExtensions on String { + /// Capitalize the first letter of the string + String capitalize() { + if (isEmpty) return this; + return '${this[0].toUpperCase()}${substring(1)}'; + } + + /// Check if string is not blank (not empty and not just whitespace) + bool get isNotBlank => trim().isNotEmpty; +} + +/// DateTime extensions +extension DateTimeExtensions on DateTime { + /// Format date as a readable string (e.g., "Jan 15, 2024") + String get formatted { + return DateFormat('MMM dd, yyyy').format(this); + } + + /// Get a human-readable time ago string (e.g., "2 hours ago") + String get timeAgo { + final now = DateTime.now(); + final difference = now.difference(this); + + if (difference.inDays > 365) { + final years = (difference.inDays / 365).floor(); + return '$years ${years == 1 ? 'year' : 'years'} ago'; + } else if (difference.inDays > 30) { + final months = (difference.inDays / 30).floor(); + return '$months ${months == 1 ? 'month' : 'months'} ago'; + } else if (difference.inDays > 0) { + return '${difference.inDays} ${difference.inDays == 1 ? 'day' : 'days'} ago'; + } else if (difference.inHours > 0) { + return '${difference.inHours} ${difference.inHours == 1 ? 'hour' : 'hours'} ago'; + } else if (difference.inMinutes > 0) { + return '${difference.inMinutes} ${difference.inMinutes == 1 ? 'minute' : 'minutes'} ago'; + } else { + return 'just now'; + } + } +} diff --git a/lib/features/chat/data/.gitkeep b/lib/features/chat/data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/chat/domain/.gitkeep b/lib/features/chat/domain/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/chat/presentation/.gitkeep b/lib/features/chat/presentation/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/notes/data/datasources/.gitkeep b/lib/features/notes/data/datasources/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/notes/data/models/.gitkeep b/lib/features/notes/data/models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/notes/data/repositories/.gitkeep b/lib/features/notes/data/repositories/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/notes/domain/entities/.gitkeep b/lib/features/notes/domain/entities/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/notes/domain/repositories/.gitkeep b/lib/features/notes/domain/repositories/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/notes/domain/usecases/.gitkeep b/lib/features/notes/domain/usecases/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/notes/presentation/providers/.gitkeep b/lib/features/notes/presentation/providers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/notes/presentation/screens/.gitkeep b/lib/features/notes/presentation/screens/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/notes/presentation/widgets/.gitkeep b/lib/features/notes/presentation/widgets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/search/data/.gitkeep b/lib/features/search/data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/search/domain/.gitkeep b/lib/features/search/domain/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/search/presentation/.gitkeep b/lib/features/search/presentation/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/voice/data/.gitkeep b/lib/features/voice/data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/voice/domain/.gitkeep b/lib/features/voice/domain/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/voice/presentation/.gitkeep b/lib/features/voice/presentation/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/main.dart b/lib/main.dart index 244a702..4733619 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,122 +1,15 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:second_brain/app.dart'; void main() { - runApp(const MyApp()); -} - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - // This widget is the root of your application. - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - // This is the theme of your application. - // - // TRY THIS: Try running your application with "flutter run". You'll see - // the application has a purple toolbar. Then, without quitting the app, - // try changing the seedColor in the colorScheme below to Colors.green - // and then invoke "hot reload" (save your changes or press the "hot - // reload" button in a Flutter-supported IDE, or press "r" if you used - // the command line to start the app). - // - // Notice that the counter didn't reset back to zero; the application - // state is not lost during the reload. To reset the state, use hot - // restart instead. - // - // This works for code too, not just values: Most code changes can be - // tested with just a hot reload. - colorScheme: .fromSeed(seedColor: Colors.deepPurple), - ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), - ); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } - - @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // TRY THIS: Try changing the color here to a specific color (to - // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar - // change color while the other colors stay the same. - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - // - // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" - // action in the IDE, or press "p" in the console), to see the - // wireframe for each widget. - mainAxisAlignment: .center, - children: [ - const Text('You have pushed the button this many times:'), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), - ); - } + // Ensure Flutter is initialized + WidgetsFlutterBinding.ensureInitialized(); + + // Run app with Riverpod + runApp( + const ProviderScope( + child: App(), + ), + ); } diff --git a/lib/shared/providers/.gitkeep b/lib/shared/providers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/shared/widgets/.gitkeep b/lib/shared/widgets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/pubspec.yaml b/pubspec.yaml index 1ae12fa..7a5d228 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,89 +1,42 @@ name: second_brain -description: "A new Flutter project." -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev - -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -# In Windows, build-name is used as the major, minor, and patch parts -# of the product and file versions while build-number is used as the build suffix. -version: 1.0.0+1 +description: "A RAG-Powered Note Taking App — Chat with your notes using AI" +publish_to: 'none' +version: 0.1.0+1 environment: sdk: ^3.10.3 -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions -# consider running `flutter pub upgrade --major-versions`. Alternatively, -# dependencies can be manually updated by changing the version numbers below to -# the latest version available on pub.dev. To see which dependencies have newer -# versions available, run `flutter pub outdated`. dependencies: flutter: sdk: flutter - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. + + # State Management + flutter_riverpod: ^2.6.1 + riverpod_annotation: ^2.6.1 + + # Local Database + isar: ^4.0.0-dev.14 + isar_flutter_libs: ^4.0.0-dev.14 + + # UI + google_fonts: ^6.2.1 + flutter_svg: ^2.0.17 + + # Utilities + uuid: ^4.5.1 + intl: ^0.19.0 + path_provider: ^2.1.5 cupertino_icons: ^1.0.8 dev_dependencies: flutter_test: sdk: flutter + flutter_lints: ^5.0.0 + + # Code Generation + riverpod_generator: ^2.6.3 + build_runner: ^2.4.14 + isar_generator: ^4.0.0-dev.14 - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. - flutter_lints: ^6.0.0 - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/to/resolution-aware-images - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/to/asset-from-package - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/to/font-from-package diff --git a/test/widget_test.dart b/test/widget_test.dart index 2beceda..2f4f12b 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -7,24 +7,22 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:second_brain/main.dart'; +import 'package:second_brain/app.dart'; void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { + testWidgets('App displays placeholder home screen', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); + await tester.pumpWidget(const ProviderScope(child: App())); - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); + // Verify that the app title is displayed + expect(find.text('🧠 Second Brain'), findsOneWidget); + + // Verify that welcome message is displayed + expect(find.text('Welcome to Second Brain'), findsOneWidget); + + // Verify that the brain icon is displayed + expect(find.byIcon(Icons.psychology), findsOneWidget); }); }