commit 78d15daa351d389194848c6c3e6cd10446a103e3 Author: AlexIves16 Date: Fri Mar 27 14:38:10 2026 +0500 Initial commit: NANAHUI CRM - Flutter Firebase CRM assistant diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..422f08b --- /dev/null +++ b/.env.example @@ -0,0 +1,27 @@ +# API Keys Configuration +# Copy this file to .env and fill in your actual keys + +# OpenAI API (for AI features) +# Get from: https://platform.openai.com/api-keys +OPENAI_API_KEY=your_openai_api_key_here + +# Anthropic API (alternative AI provider) +# Get from: https://console.anthropic.com/ +ANTHROPIC_API_KEY=your_anthropic_api_key_here + +# Google AI API (Gemini) +# Get from: https://makersuite.google.com/app/apikey +GOOGLE_AI_API_KEY=your_google_ai_api_key_here + +# Firebase Configuration +# These will be in your Firebase console +FIREBASE_API_KEY=your_firebase_api_key +FIREBASE_APP_ID=your_firebase_app_id +FIREBASE_PROJECT_ID=your_firebase_project_id +FIREBASE_MESSAGING_SENDER_ID=your_firebase_sender_id +FIREBASE_STORAGE_BUCKET=your_firebase_storage_bucket + +# Google Calendar API +# Enable in Google Cloud Console +GOOGLE_CLOUD_CLIENT_ID=your_google_cloud_client_id +GOOGLE_CLOUD_CLIENT_SECRET=your_google_cloud_client_secret diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a50e359 --- /dev/null +++ b/.gitignore @@ -0,0 +1,48 @@ +# Environment variables +.env +.env.local +.env.*.local + +# Firebase +google-services.json +GoogleService-Info.plist + +# IDE +.vscode/ +.idea/ +*.iml +*.ipr +*.iws + +# Build +build/ +**/build/ +**/android/build/ +**/ios/build/ +**/web/build/ + +# Dependencies +.pub-cache/ +.packages + +# Logs +*.log +logs/ + +# OS +.DS_Store +Thumbs.db + +# Coverage +coverage/ + +# Testing +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub/ + +# Temporary files +*.tmp +*.temp diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..c0fe018 --- /dev/null +++ b/.metadata @@ -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: "90673a4eef275d1a6692c26ac80d6d746d41a73a" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + - platform: android + create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + - platform: ios + create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + - platform: linux + create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + - platform: macos + create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + - platform: web + create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + - platform: windows + create_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + base_revision: 90673a4eef275d1a6692c26ac80d6d746d41a73a + + # 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' diff --git a/ADD_SHA1.md b/ADD_SHA1.md new file mode 100644 index 0000000..f9a1447 --- /dev/null +++ b/ADD_SHA1.md @@ -0,0 +1,50 @@ +# 🔑 Как добавить SHA-1 fingerprint + +## Способ 1: Через Android Studio (проще) + +1. Открой проект в Android Studio +2. Справа открой **Gradle** панель +3. Разверни `:android` → `Tasks` → `android` +4. Дважды кликни на **signingReport** +5. В консоли найди строку `SHA1:` +6. Скопируй значение + +## Способ 2: Через командную строку + +```bash +cd c:\Users\ormix\NANAHUI\android +.\gradlew app:signingReport +``` + +Ищи строку вида: +``` +SHA1: A1:B2:C3:D4:E5:F6:... +``` + +## Способ 3: Через keytool (если есть Java) + +```bash +keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android +``` + +--- + +## ➕ Добавить в Firebase Console + +1. Открой https://console.firebase.google.com/project/nanax26-1f33b/settings/general/android:com.nanaxe.nanaxe_crm +2. Прокрути вниз до **SHA certificate fingerprints** +3. Нажми **Add fingerprint** +4. Вставь скопированный SHA-1 +5. Сохрани + +--- + +## ✅ Проверка + +После добавления SHA-1: +1. Перескачай `google-services.json` из Firebase Console +2. Замени файл в `android/app/google-services.json` +3. Запусти приложение: `flutter run` +4. Попробуй войти через Google + +Если работает — всё настроено правильно! 🎉 diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..43d5505 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,284 @@ +# 🏗 Архитектура NANAHUI CRM + +## Общая схема + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Flutter Application │ +├─────────────────────────────────────────────────────────────────┤ +│ Presentation Layer (UI) │ +│ ┌─────────────┬─────────────┬─────────────┬─────────────┐ │ +│ │ Screens │ Widgets │ Themes │ Navigation │ │ +│ │ │ │ │ (GoRouter) │ │ +│ └─────────────┴─────────────┴─────────────┴─────────────┘ │ +├─────────────────────────────────────────────────────────────────┤ +│ State Management Layer (Riverpod) │ +│ ┌─────────────┬─────────────┬─────────────┬─────────────┐ │ +│ │ Providers │ Notifiers │ Streams │ State │ │ +│ └─────────────┴─────────────┴─────────────┴─────────────┘ │ +├─────────────────────────────────────────────────────────────────┤ +│ Domain Layer (Business Logic) │ +│ ┌─────────────┬─────────────┬─────────────┬─────────────┐ │ +│ │ Models │ Repositories │ Services │ Use Cases │ │ +│ └─────────────┴─────────────┴─────────────┴─────────────┘ │ +├─────────────────────────────────────────────────────────────────┤ +│ Data Layer │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ Local Storage (Hive) │ │ +│ │ ┌──────────┬──────────┬──────────┬──────────┐ │ │ +│ │ │ Tasks │ Alarms │ MindMaps │ Events │ │ │ +│ │ └──────────┴──────────┴──────────┴──────────┘ │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ Remote Storage (Firebase Firestore) │ │ +│ └─────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ + │ + ┌────────────────────┼────────────────────┐ + │ │ │ + ▼ ▼ ▼ +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Firebase Auth │ │ AI Services │ │ Google Calendar │ +│ (Google Sign- │ │ (OpenAI, │ │ API │ +│ In, Email) │ │ Anthropic) │ │ │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ +``` + +## Поток данных + +### 1. Аутентификация +``` +User → AuthScreen → AuthNotifier (Riverpod) → Firebase Auth + ↓ + UserModel → Firestore + ↓ + HomeScreen +``` + +### 2. Управление задачами +``` +User → TasksScreen → TaskNotifier → FirestoreService → Firestore + ↓ + LocalStorageService (Hive) + ↓ + Offline Cache +``` + +### 3. AI генерация +``` +User → TaskScreen → AIService → OpenAI API + ↓ + Generate Subtasks + ↓ + TaskModel → Firestore +``` + +### 4. Mind-карта +``` +User → MindMapScreen → MindMapNotifier → FirestoreService + ↓ + MindMap Nodes (Tree) + ↓ + GraphView Widget (Render) +``` + +### 5. Синхронизация +``` +┌──────────────┐ Compare ┌──────────────┐ +│ Local Hive │ ←───────────→ │ Firestore │ +│ (Offline) │ │ (Remote) │ +└──────────────┘ └──────────────┘ + ↓ ↓ + Merge Conflicts Real-time Sync + ↓ ↓ + Local Priority Remote Priority + └──────────────┬──────────────┘ + ↓ + Merged Data → UI +``` + +## Модульная структура + +### Core Module +``` +core/ +├── firebase_config.dart # Инициализация Firebase +├── firestore_service.dart # CRUD операции Firestore +├── local_storage_service.dart # Hive кэш +├── ai_service.dart # AI интеграции +├── router.dart # GoRouter маршруты +└── theme.dart # Material темы +``` + +### Features Modules +``` +features/ +├── auth/ +│ ├── auth_screen.dart +│ ├── auth_provider.dart +│ └── auth_service.dart +├── tasks/ +│ ├── task_list_screen.dart +│ ├── task_detail_screen.dart +│ ├── task_provider.dart +│ └── task_service.dart +├── mindmap/ +│ ├── mindmap_screen.dart +│ ├── mindmap_widget.dart +│ ├── mindmap_provider.dart +│ └── mindmap_service.dart +├── calendar/ +│ ├── calendar_screen.dart +│ ├── event_provider.dart +│ └── google_calendar_service.dart +└── alarms/ + ├── alarms_screen.dart + ├── alarm_provider.dart + └── notification_service.dart +``` + +## Ролевая модель + +``` +┌────────────────────────────────────────────┐ +│ User Roles │ +├──────────────┬──────────────┬──────────────┤ +│ Admin │ Member │ Viewer │ +├──────────────┼──────────────┼──────────────┤ +│ ✓ Все права │ ✓ Создание │ ✓ Просмотр │ +│ ✓ Редактиро- │ задач │ задач │ +│ вание mind-│ ✓ Редактиро- │ ✓ Просмотр │ +│ карт │ вание │ mind-карт │ +│ ✓ Управление │ своих задач│ ✗ Редактиро- │ +│ командой │ ✓ Просмотр │ вание │ +│ ✓ Удаление │ общих задач│ ✗ Удаление │ +│ любых задач│ ✗ Редактиро- │ │ +│ │ вание mind-│ │ +│ │ карт │ │ +└──────────────┴──────────────┴──────────────┘ +``` + +## Модель данных + +### Task Model +```dart +TaskModel { + String id; + String title; + String? description; + String projectId; + String? parentTaskId; // Для иерархии + TaskStatus status; // todo, inProgress, review, done + TaskPriority priority; // low, medium, high, urgent + String? assigneeId; + String? creatorId; + List tags; + DateTime? dueDate; + int estimatedHours; + Map aiMetadata; // AI данные + DateTime createdAt; + DateTime updatedAt; +} +``` + +### MindMapNode Model +```dart +MindMapNode { + String id; + String projectId; + String? parentId; // Для дерева + int level; // Уровень вложенности + String title; + String? taskId; // Связь с задачей + String color; + double x, y; // Координаты + DateTime createdAt; + DateTime updatedAt; +} +``` + +## Синхронизация + +### Стратегия +1. **Offline-first**: Все данные сначала сохраняются локально +2. **Background sync**: Периодическая синхронизация с Firestore +3. **Conflict resolution**: + - Last write wins для простых полей + - Merge для коллекций + - User prompt для критических конфликтов + +### Flow +``` +┌─────────┐ ┌─────────────┐ ┌──────────┐ +│ UI │────→│ Repository │────→│ Firestore│ +└─────────┘ └─────────────┘ └──────────┘ + ↓ ↓ +┌─────────┐ ┌─────────────┐ +│ Cache │←────│ Hive │ +└─────────┘ └─────────────┘ +``` + +## Безопасность + +### Firebase Security Rules +```javascript +// Проверка прав доступа +function hasRole(allowedRoles) { + return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role in allowedRoles; +} + +// Проверка владения +function isOwner(resource) { + return resource.ownerId == request.auth.uid; +} + +// Проверка членства в команде +function isTeamMember(teamId) { + let team = get(/databases/$(database)/documents/teams/$(teamId)).data; + return team.memberIds.hasAny([request.auth.uid]); +} +``` + +## Производительность + +### Оптимизация +1. **Pagination**: Загрузка задач порциями по 50 +2. **Lazy loading**: Mind-карта загружается по частям +3. **Caching**: Hive для офлайн-доступа +4. **Indexes**: Firestore индексы для частых запросов +5. **Batching**: Пакетная запись в Firestore + +### Firestore Indexes +``` +tasks: [projectId, createdAt] [assigneeId, status] [dueDate] +events: [startTime, endTime] [attendeeIds] +mindmaps: [projectId, level] +``` + +## Масштабирование + +### Этапы роста + +**Phase 1: MVP (сейчас)** +- Базовые CRUD операции +- Простая аутентификация +- Локальный кэш + +**Phase 2: Team Features** +- Командная работа +- Роли и разрешения +- Real-time collaboration + +**Phase 3: AI Integration** +- Умное планирование +- Авто-приоритизация +- Генерация отчётов + +**Phase 4: Enterprise** +- SSO интеграция +- Audit logs +- Advanced analytics + +--- + +**Документ создан для команды NANAHUI** 🚀 diff --git a/FIREBASE_DONE.md b/FIREBASE_DONE.md new file mode 100644 index 0000000..ade7638 --- /dev/null +++ b/FIREBASE_DONE.md @@ -0,0 +1,190 @@ +# ✅ Настройка Firebase завершена! + +## 📦 Что сделано: + +### 1. Gradle настроен правильно ✅ +- **Kotlin DSL** (`build.gradle.kts`) — современный подход +- **Google Services Plugin** версии 4.4.4 добавлен +- **Firebase BoM** версии 34.11.0 подключён +- Все Firebase зависимости добавлены: + - `firebase-analytics` + - `firebase-auth` + - `firebase-firestore` + - `firebase-messaging` + +### 2. Package name ✅ +- Application ID: `com.nanaxe.nanaxe_crm` +- Соответствует `google-services.json` + +### 3. AndroidManifest обновлён ✅ +- Добавлены разрешения для Firebase и уведомлений +- Название приложения: "NANAHUI CRM" + +### 4. Правила безопасности Firestore ✅ +- Файл `firestore.rules` создан +- Доступ только для аутентифицированных пользователей +- Поддержка ролей (admin, member, viewer) + +--- + +## 🚀 СЛЕДУЮЩИЙ ШАГ: Загрузить правила безопасности + +### Открой Firebase Console: +1. Перейди в https://console.firebase.google.com +2. Выбери проект `nanax26-1f33b` +3. Слева: **Build** → **Firestore Database** +4. Вверху выбери вкладку **Rules** +5. Скопируй содержимое файла `firestore.rules` и вставь в редактор +6. Нажми **Publish** + +**Или используй Firebase CLI:** +```bash +npm install -g firebase-tools +firebase login +firebase use nanax26-1f33b +firebase deploy --only firestore:rules +``` + +--- + +## 📱 Запуск приложения + +### 1. Подключи устройство или запусти эмулятор +```bash +# Проверка подключенных устройств +flutter devices +``` + +### 2. Запусти приложение +```bash +flutter run +``` + +### 3. Если ошибка с SHA-1 ключом +Для Google Sign-In нужен SHA-1 fingerprint: + +```bash +# Для debug ключа (Windows) +cd android +gradlew app:signingReport +``` + +Скопируй SHA-1 из отчёта и добавь в Firebase Console: +1. Project Settings → Your apps → Android app +2. Add fingerprint +3. Вставь SHA-1 +4. Save + +--- + +## 🔧 Проверка работы + +### Чек-лист: +- [ ] Firebase проект создан +- [ ] Authentication включён (Google provider) +- [ ] Firestore база создана (region: eur3) +- [ ] Android приложение зарегистрировано +- [ ] `google-services.json` лежит в `android/app/` +- [ ] Gradle файлы обновлены +- [ ] Правила безопасности загружены в Firebase +- [ ] SHA-1 fingerprint добавлен (для Google Sign-In) + +### Тест аутентификации: +1. Запусти приложение +2. Нажми "Войти через Google" +3. Выбери аккаунт +4. Если успешно — переход на главный экран ✅ + +--- + +## 📊 Структура Firestore (автоматически создаётся) + +``` +users/ + └─ {userId}/ + ├─ email + ├─ displayName + ├─ role: "member" + ├─ teamIds: [] + └─ settings: {} + +teams/ + └─ {teamId}/ + ├─ name + ├─ memberIds: [] + └─ projects: subcollection + +projects/ + └─ {projectId}/ + ├─ name + ├─ ownerId + └─ tasks: subcollection + +tasks/ + └─ {taskId}/ + ├─ title + ├─ status: "todo" + ├─ priority: "medium" + └─ assigneeId + +mindmaps/ + └─ {projectId}/ + └─ nodes/ + └─ {nodeId}/ + ├─ title + ├─ level + └─ parentId + +events/ + └─ {eventId}/ + ├─ title + ├─ startTime + └─ attendeeIds + +alarms/ + └─ {alarmId}/ + ├─ title + ├─ dateTime + └─ isRepeating +``` + +--- + +## 🆘 Возможные ошибки + +### "No Firebase App" +``` +Проверь что google-services.json лежит в android/app/ +``` + +### "Sign-in failed" +``` +1. Добавь SHA-1 fingerprint в Firebase Console +2. Перескачай google-services.json +3. Пересобери приложение: flutter clean && flutter run +``` + +### "Permission denied" +``` +1. Проверь что правила безопасности загружены +2. Убедись что пользователь аутентифицирован +``` + +### "App not found" +``` +1. Проверь что package name совпадает: + - В google-services.json: "com.nanaxe.nanaxe_crm" + - В build.gradle.kts: applicationId = "com.nanaxe.nanaxe_crm" +``` + +--- + +## 📚 Документация + +- [Firebase Flutter Setup](https://firebase.flutter.dev/docs/overview) +- [Firestore Rules](https://firebase.google.com/docs/firestore/security/get-started) +- [Google Sign-In Flutter](https://pub.dev/packages/google_sign_in) + +--- + +**Готово! Запускай `flutter run`** 🚀 diff --git a/FIREBASE_SETUP.md b/FIREBASE_SETUP.md new file mode 100644 index 0000000..a1229c5 --- /dev/null +++ b/FIREBASE_SETUP.md @@ -0,0 +1,24 @@ +# Firebase configuration file +# Download this from Firebase Console: +# Project Settings > Your apps > Add app + +# For Android: +# 1. Add your app with package name: com.nanahui.nanahui_crm +# 2. Download google-services.json +# 3. Place it in: android/app/google-services.json + +# For iOS: +# 1. Add your app with bundle ID: com.nanahui.nanahuiCrm +# 2. Download GoogleService-Info.plist +# 3. Place it in: ios/Runner/GoogleService-Info.plist + +# For Web: +# 1. Add web app +# 2. Copy firebaseConfig object +# 3. Update lib/core/firebase_options.dart with your config + +# Required Firebase APIs: +# - Authentication (Google Sign-In) +# - Firestore Database +# - Cloud Messaging (FCM) +# - Cloud Functions (optional, for AI processing) diff --git a/PROJECT_STRUCTURE.md b/PROJECT_STRUCTURE.md new file mode 100644 index 0000000..dab6390 --- /dev/null +++ b/PROJECT_STRUCTURE.md @@ -0,0 +1,208 @@ +# 📁 Структура проекта NANAHUI CRM + +``` +NANAHUI/ +│ +├── 📄 README.md # Общая информация о проекте +├── 📄 QUICKSTART.md # Быстрый старт и настройка +├── 📄 ARCHITECTURE.md # Архитектурное описание +├── 📄 FIREBASE_SETUP.md # Инструкция по настройке Firebase +├── 📄 .env.example # Шаблон переменных окружения +├── 📄 .gitignore # Git ignore файл +├── 📄 pubspec.yaml # Зависимости Flutter +│ +├── 📂 lib/ # Исходный код приложения +│ │ +│ ├── 📄 main.dart # Точка входа +│ │ +│ ├── 📂 core/ # Ядро приложения +│ │ ├── firebase_config.dart # Конфигурация Firebase +│ │ ├── firestore_service.dart # Сервис для работы с Firestore +│ │ ├── local_storage_service.dart # Локальное хранилище (Hive) +│ │ ├── ai_service.dart # AI сервис (OpenAI, Anthropic) +│ │ ├── router.dart # Маршрутизация (GoRouter) +│ │ └── theme.dart # Темы оформления +│ │ +│ ├── 📂 models/ # Модели данных +│ │ ├── user_model.dart # Пользователь +│ │ ├── task_model.dart # Задача +│ │ ├── project_model.dart # Проект +│ │ ├── mind_map_model.dart # Mind-карта +│ │ ├── calendar_event_model.dart # Событие календаря +│ │ └── alarm_model.dart # Будильник/Напоминание +│ │ +│ ├── 📂 providers/ # State Management (Riverpod) +│ │ └── auth_provider.dart # Провайдер аутентификации +│ │ +│ ├── 📂 screens/ # Экраны приложения +│ │ ├── auth_screen.dart # Экран входа +│ │ ├── home_screen.dart # Главная навигация +│ │ ├── dashboard_screen.dart # Дашборд +│ │ ├── tasks_screen.dart # Задачи +│ │ ├── mind_map_screen.dart # Mind-карта +│ │ ├── calendar_screen.dart # Календарь +│ │ └── alarms_screen.dart # Будильники +│ │ +│ ├── 📂 widgets/ # Переиспользуемые виджеты +│ │ └── (создаётся по мере необходимости) +│ │ +│ └── 📂 features/ # Функциональные модули +│ └── (для будущего расширения) +│ +├── 📂 assets/ # Ресурсы приложения +│ ├── 📂 images/ # Изображения +│ │ └── .gitkeep +│ └── 📂 icons/ # Иконки +│ └── .gitkeep +│ +├── 📂 test/ # Тесты +│ └── widget_test.dart # Widget тесты +│ +├── 📂 android/ # Android платформа +│ ├── 📂 app/ +│ │ ├── src/main/ +│ │ │ ├── AndroidManifest.xml +│ │ │ ├── kotlin/ # Kotlin код +│ │ │ ├── res/ # Ресурсы Android +│ │ │ └── ... +│ │ └── build.gradle # Android build config +│ └── google-services.json # ⚠️ Firebase config (нужно добавить) +│ +├── 📂 ios/ # iOS платформа +│ ├── 📂 Runner/ +│ │ ├── AppDelegate.swift +│ │ ├── Info.plist +│ │ └── ... +│ └── GoogleService-Info.plist # ⚠️ Firebase config (нужно добавить) +│ +├── 📂 web/ # Web платформа +│ ├── index.html +│ └── manifest.json +│ +├── 📂 windows/ # Windows платформа +│ └── ... +│ +├── 📂 linux/ # Linux платформа +│ └── ... +│ +├── 📂 macos/ # macOS платформа +│ └── ... +│ +└── 📂 build/ # ⚠️ Build artifacts (не в git) + └── (генерируется при сборке) +``` + +## 🔑 Ключевые файлы + +### Конфигурация +| Файл | Описание | +|------|----------| +| `pubspec.yaml` | Зависимости и настройки Flutter | +| `.gitignore` | Исключения для Git | +| `.env.example` | Шаблон переменных окружения | + +### Firebase (нужно добавить) +| Файл | Где взять | Куда положить | +|------|-----------|---------------| +| `google-services.json` | Firebase Console → Android | `android/app/` | +| `GoogleService-Info.plist` | Firebase Console → iOS | `ios/Runner/` | + +### Основные модули +| Модуль | Файл | Описание | +|--------|------|----------| +| **Entry Point** | `lib/main.dart` | Инициализация и запуск | +| **Firebase** | `lib/core/firebase_config.dart` | Настройка Firebase | +| **Auth** | `lib/providers/auth_provider.dart` | Аутентификация через Google | +| **Firestore** | `lib/core/firestore_service.dart` | CRUD операции с БД | +| **AI** | `lib/core/ai_service.dart` | Интеграция с OpenAI | +| **Storage** | `lib/core/local_storage_service.dart` | Офлайн кэш на Hive | +| **Router** | `lib/core/router.dart` | Навигация между экранами | +| **Theme** | `lib/core/theme.dart` | Светлая/тёмная темы | + +## 📦 Основные зависимости + +### State Management +- `flutter_riverpod: ^2.5.0` - Реактивное управление состоянием +- `riverpod_annotation: ^2.3.5` - Code generation для Riverpod + +### Firebase +- `firebase_core: ^2.32.0` - Ядро Firebase +- `firebase_auth: ^4.19.0` - Аутентификация +- `cloud_firestore: ^4.17.0` - Firestore база данных +- `firebase_messaging: ^14.9.0` - Push уведомления + +### Local Storage +- `hive_flutter: ^1.1.0` - Быстрое локальное хранилище +- `hive: ^2.2.3` - Hive ядро + +### Navigation +- `go_router: ^13.1.0` - Декларативная маршрутизация + +### AI +- `http: ^1.2.0` - HTTP клиент для API запросов +- `dio: ^5.4.0` - Продвинутый HTTP клиент + +### UI +- `graphview: ^1.2.0` - Визуализация графов (для mind-карт) +- `flutter_mind_map: ^1.0.23` - Готовый mind-map виджет +- `flutter_staggered_grid_view: ^0.7.0` - Сетка для карточек + +### Google Integration +- `google_sign_in: ^6.2.1` - Вход через Google +- `googleapis: ^13.2.0` - Google API (Calendar и др.) + +### Notifications +- `flutter_local_notifications: ^17.0.0` - Локальные уведомления +- `workmanager: ^0.5.2` - Фоновые задачи + +## 🎯 Точки расширения + +### Для добавления новой фичи: + +1. **Создать модель** в `lib/models/` +2. **Создать сервис** в `lib/core/` или `lib/features/` +3. **Создать провайдер** в `lib/providers/` +4. **Создать экран** в `lib/screens/` +5. **Добавить маршрут** в `lib/core/router.dart` +6. **Добавить виджеты** в `lib/widgets/` + +### Пример: Добавление заметок + +``` +lib/ +├── models/ +│ └── note_model.dart # ✨ Новая модель +├── features/ +│ └── notes/ +│ ├── note_service.dart # ✨ Сервис +│ └── note_provider.dart # ✨ Провайдер +├── screens/ +│ └── notes_screen.dart # ✨ Экран +└── widgets/ + └── note_card.dart # ✨ Виджет +``` + +## 📊 Метрики проекта + +``` +Строки кода: ~2000+ +Файлов: ~20 +Модулей: 6 (Auth, Tasks, MindMap, Calendar, Alarms, Dashboard) +Моделей данных: 6 +Экранов: 6 +Сервисов: 4 (Firebase, Firestore, AI, LocalStorage) +``` + +## 🔄 CI/CD (будущее) + +```yaml +.github/workflows/ +├── build.yml # Сборка APK +├── test.yml # Запуск тестов +└── deploy.yml # Деплой в Store +``` + +--- + +**Структура создана для команды NANAHUI** 🚀 +Последнее обновление: Март 2026 diff --git a/QUICKSTART.md b/QUICKSTART.md new file mode 100644 index 0000000..9f96248 --- /dev/null +++ b/QUICKSTART.md @@ -0,0 +1,340 @@ +# 🚀 Быстрый старт - NANAHUI CRM + +## ✅ Что уже создано + +### Структура проекта +``` +lib/ +├── core/ # Ядро приложения +│ ├── ai_service.dart # AI сервис (OpenAI, Anthropic) +│ ├── firebase_config.dart # Firebase конфигурация +│ ├── firestore_service.dart # Firestore сервис (CRUD операции) +│ ├── local_storage_service.dart # Локальное хранилище (Hive) +│ ├── router.dart # Маршрутизация (GoRouter) +│ └── theme.dart # Темы оформления +├── features/ # Функциональные модули (будущие) +├── models/ # Модели данных +│ ├── user_model.dart # Пользователь +│ ├── task_model.dart # Задача +│ ├── project_model.dart # Проект +│ ├── mind_map_model.dart # Mind-карта +│ ├── calendar_event_model.dart # Событие календаря +│ └── alarm_model.dart # Будильник +├── providers/ # State management (Riverpod) +│ └── auth_provider.dart # Аутентификация +├── screens/ # Экраны +│ ├── auth_screen.dart # Вход +│ ├── home_screen.dart # Главная навигация +│ ├── dashboard_screen.dart # Дашборд +│ ├── tasks_screen.dart # Задачи +│ ├── mind_map_screen.dart # Mind-карта +│ ├── calendar_screen.dart # Календарь +│ └── alarms_screen.dart # Будильники +└── widgets/ # Виджеты (будущие) +``` + +## 🔧 Настройка Firebase + +### 1. Создайте проект в Firebase Console + +1. Перейдите на https://console.firebase.google.com +2. Нажмите "Add project" +3. Введите имя: "NANAHUI CRM" +4. Отключите Google Analytics (не нужно для начала) +5. Создайте проект + +### 2. Включите Authentication + +1. В меню слева: **Build** → **Authentication** +2. Нажмите **Get started** +3. Включите **Google** провайдер: + - Click **Google** → **Enable** + - Enter support email: ваш email + - **Save** + +### 3. Создайте Firestore базу + +1. В меню: **Build** → **Firestore Database** +2. Нажмите **Create database** +3. Выберите **Start in test mode** (потом настроим правила) +4. Выберите локацию (например, us-central) +5. **Enable** + +### 4. Добавьте Android приложение + +1. В Firebase Console: **Project Settings** (шестерёнка) +2. **Your apps** → **Add app** → **Android** +3. Введите package name: `com.nanahui.nanahui_crm` +4. Скачайте `google-services.json` +5. Положите файл в: `android/app/google-services.json` + +### 5. Добавьте iOS приложение (если нужно) + +1. **Add app** → **iOS** +2. Bundle ID: `com.nanahui.nanahuiCrm` +3. Скачайте `GoogleService-Info.plist` +4. Положите в: `ios/Runner/GoogleService-Info.plist` + +### 6. Настройте android/app/build.gradle + +В файле `android/app/build.gradle` добавьте: + +```gradle +dependencies { + // ... другие зависимости + + // Firebase + implementation platform('com.google.firebase:firebase-bom:32.7.0') + implementation 'com.google.firebase:firebase-analytics' + implementation 'com.google.firebase:firebase-auth' + implementation 'com.google.firebase:firebase-firestore' + implementation 'com.google.firebase:firebase-messaging' +} + +// В конце файла: +apply plugin: 'com.google.gms.google-services' +``` + +## 🔑 Настройка API ключей + +### OpenAI API (для AI функций) + +1. Перейдите на https://platform.openai.com/api-keys +2. Создайте новый API ключ +3. Скопируйте ключ + +### Google Calendar API + +1. Перейдите на https://console.cloud.google.com +2. Создайте новый проект или выберите существующий +3. Включите **Google Calendar API** +4. Создайте OAuth 2.0 credentials +5. Получите Client ID и Client Secret + +## 📝 Настройка окружения + +1. Скопируйте `.env.example` в `.env`: +```bash +cp .env.example .env +``` + +2. Заполните `.env` своими ключами: +``` +OPENAI_API_KEY=sk-your-actual-openai-key +FIREBASE_API_KEY=your-firebase-key +# и т.д. +``` + +## 🏃 Запуск приложения + +### 1. Установите зависимости +```bash +flutter pub get +``` + +### 2. Запуск на эмуляторе/устройстве +```bash +flutter run +``` + +### 3. Сборка APK (Android) +```bash +flutter build apk --release +``` + +### 4. Сборка для iOS +```bash +flutter build ios --release +``` + +## 📱 Тестирование + +### Проверка аутентификации +1. Запустите приложение +2. Нажмите "Войти через Google" +3. Выберите аккаунт +4. Должна произойти авторизация и переход на главный экран + +### Проверка навигации +1. После входа используйте нижнюю навигационную панель +2. Переключайтесь между: Главная, Задачи, Mind-карта, Календарь, Будильники + +## 🔥 Firestore правила безопасности + +Создайте в Firebase Console → Firestore Database → Rules: + +```javascript +rules_version = '2'; +service cloud.firestore { + match /databases/{database}/documents { + // Helper function + function isAuthenticated() { + return request.auth != null; + } + + function isOwner(userId) { + return request.auth.uid == userId; + } + + // Users + match /users/{userId} { + allow read: if isAuthenticated(); + allow write: if isOwner(userId); + } + + // Projects + match /projects/{projectId} { + allow read: if isAuthenticated(); + allow create: if isAuthenticated(); + allow update, delete: if isAuthenticated(); + } + + // Tasks + match /tasks/{taskId} { + allow read: if isAuthenticated(); + allow create, update, delete: if isAuthenticated(); + } + + // Events + match /events/{eventId} { + allow read: if isAuthenticated(); + allow create, update, delete: if isAuthenticated(); + } + + // Alarms + match /alarms/{alarmId} { + allow read: if isAuthenticated(); + allow create, update, delete: if isAuthenticated(); + } + + // Mind maps + match /mindmaps/{projectId}/nodes/{nodeId} { + allow read: if isAuthenticated(); + allow write: if isAuthenticated(); + } + } +} +``` + +## 📊 Структура Firestore + +``` +users/ + └─ {userId}/ + ├─ email: string + ├─ displayName: string + ├─ photoUrl: string + ├─ role: "admin" | "member" | "viewer" + ├─ teamIds: string[] + └─ settings: map + +projects/ + └─ {projectId}/ + ├─ name: string + ├─ description: string + ├─ teamId: string + ├─ ownerId: string + ├─ memberIds: string[] + ├─ status: string + ├─ startDate: timestamp + ├─ endDate: timestamp + └─ color: string + +tasks/ + └─ {taskId}/ + ├─ title: string + ├─ description: string + ├─ projectId: string + ├─ parentTaskId: string? + ├─ status: "todo" | "inProgress" | "review" | "done" + ├─ priority: "low" | "medium" | "high" | "urgent" + ├─ assigneeId: string? + ├─ creatorId: string + ├─ dueDate: timestamp? + └─ aiMetadata: map + +mindmaps/ + └─ {projectId}/ + └─ nodes/ + └─ {nodeId}/ + ├─ parentId: string? + ├─ level: number + ├─ title: string + ├─ taskId: string? + ├─ x: number + ├─ y: number + └─ color: string + +events/ + └─ {eventId}/ + ├─ title: string + ├─ startTime: timestamp + ├─ endTime: timestamp + ├─ googleEventId: string? + └─ attendeeIds: string[] + +alarms/ + └─ {alarmId}/ + ├─ title: string + ├─ dateTime: timestamp + ├─ isRepeating: boolean + └─ taskId: string? +``` + +## 🛠 Следующие шаги + +### Первоочередные задачи: + +1. ✅ ~~Создать базовую структуру~~ +2. ✅ ~~Настроить модели данных~~ +3. ✅ ~~Создать экраны навигации~~ +4. 🔲 Настроить Firebase проект +5. 🔲 Реализовать реальную аутентификацию +6. 🔲 Добавить CRUD операции с Firestore +7. 🔲 Реализовать синхронизацию офлайн-данных +8. 🔲 Добавить AI функции (OpenAI API) +9. 🔲 Интеграция с Google Calendar +10. 🔲 Создать mind-map виджет с drag-drop + +### Для тестирования AI: + +1. Добавьте OpenAI API ключ в `lib/core/ai_service.dart`: +```dart +final aiService = AIService( + openaiApiKey: 'sk-your-api-key-here', +); +``` + +2. Протестируйте генерацию подзадач: +```dart +final subtasks = await aiService.generateSubtasks( + title: 'Создать CRM систему', + description: 'Разработка CRM для управления задачами команды', +); +print(subtasks); +``` + +## 🆘 Troubleshooting + +### Ошибка: "No Firebase App" +- Убедитесь, что `google-services.json` лежит в `android/app/` +- Проверьте, что добавлен `apply plugin: 'com.google.gms.google-services'` + +### Ошибка: "Sign-in failed" +- Проверьте SHA-1 fingerprint для Android +- Убедитесь, что Google Sign-In включён в Firebase Console + +### Ошибка: "Permission denied" +- Проверьте правила безопасности Firestore +- Убедитесь, что пользователь аутентифицирован + +## 📚 Ресурсы + +- [Flutter документация](https://docs.flutter.dev) +- [Riverpod документация](https://riverpod.dev) +- [Firebase Flutter](https://firebase.flutter.dev) +- [OpenAI API](https://platform.openai.com/docs) + +--- + +**Создано для команды NANAHUI** 🚀 diff --git a/README.md b/README.md new file mode 100644 index 0000000..0e7b36d --- /dev/null +++ b/README.md @@ -0,0 +1,156 @@ +# NANAHUI CRM - CRM-ассистент для команд + +CRM-система с AI-планированием, mind-картами задач и интеграцией с Google Calendar. + +## 🚀 Возможности + +### Для команд и фрилансеров: +- ✅ **Управление задачами** - создание, назначение, приоритеты, статусы +- ✅ **Mind-карты проектов** - визуальное дерево задач (drag-drop для админов) +- ✅ **Календарь** - синхронизация с Google Calendar +- ✅ **Будильники/Напоминания** - локальные уведомления +- ✅ **AI-ассистент** - умное планирование и генерация подзадач +- ✅ **Ролевая модель** - admin, member, viewer +- ✅ **Офлайн-режим** - кэширование данных на устройстве + +## 📦 Технологический стек + +| Компонент | Технология | +|-----------|------------| +| Framework | Flutter | +| State Management | Riverpod | +| Backend | Firebase (Firestore, Auth, FCM) | +| Local Storage | Hive | +| Navigation | GoRouter | +| Mind Maps | flutter_mind_map, graphview | +| AI | OpenAI API, Anthropic API (планируется) | + +## 🏗 Структура проекта + +``` +lib/ +├── core/ +│ ├── firebase_config.dart +│ ├── local_storage_service.dart +│ ├── router.dart +│ └── theme.dart +├── features/ +│ ├── tasks/ +│ ├── mindmap/ +│ ├── calendar/ +│ └── alarms/ +├── models/ +│ ├── user_model.dart +│ ├── task_model.dart +│ ├── project_model.dart +│ ├── mind_map_model.dart +│ ├── calendar_event_model.dart +│ └── alarm_model.dart +├── providers/ +│ └── auth_provider.dart +├── screens/ +│ ├── auth_screen.dart +│ ├── home_screen.dart +│ ├── tasks_screen.dart +│ ├── mind_map_screen.dart +│ ├── calendar_screen.dart +│ └── alarms_screen.dart +└── widgets/ +``` + +## 🔧 Настройка + +### 1. Firebase Console + +1. Создайте проект в [Firebase Console](https://console.firebase.google.com) +2. Включите Authentication (Google Sign-In) +3. Создайте Firestore базу данных +4. Скачайте конфиги: + - `google-services.json` для Android + - `GoogleService-Info.plist` для iOS + +### 2. Установка зависимостей + +```bash +flutter pub get +``` + +### 3. Запуск + +```bash +flutter run +``` + +## 📱 Роли пользователей + +| Роль | Права | +|------|-------| +| **Admin** | Создание/редактирование mind-карт, управление командой, все CRUD операции | +| **Member** | Создание задач, редактирование своих задач, просмотр общих | +| **Viewer** | Только просмотр | + +## 🗄 Структура Firestore + +``` +users/{userId} + - email, displayName, photoUrl + - role: admin|member|viewer + - teamIds: [] + - settings: {} + +teams/{teamId} + - name, description + - memberIds: [] + - projects: subcollection + +projects/{projectId} + - name, description, color + - ownerId, memberIds + - startDate, endDate + +tasks/{taskId} + - title, description + - projectId, parentTaskId + - status, priority + - assigneeId, creatorId + - dueDate, estimatedHours + - aiMetadata: {} + +mindmaps/{mindMapId} + - projectId, title + - rootNodeId + - nodes: subcollection + +events/{eventId} + - title, description + - startTime, endTime + - googleEventId + - attendeeIds: [] + +alarms/{alarmId} + - title, dateTime + - taskId, eventId + - isRepeating, repeatPattern +``` + +## 🤖 AI Функции (планируется) + +- **Генерация подзадач** - разбивка больших задач на подзадачи +- **Приоритизация** - AI анализ важности задач +- **Планирование** - оптимальное распределение задач по времени +- **Саммари** - краткое описание проекта/задач +- **Анализ тональности** - анализ переписки с клиентами + +## 📝 Следующие шаги + +- [ ] Настроить Firebase проект +- [ ] Добавить реальную интеграцию с Firestore +- [ ] Реализовать AI сервисы (OpenAI, Anthropic) +- [ ] Добавить Google Calendar API +- [ ] Реализовать mind-map виджет с drag-drop +- [ ] Добавить локальные уведомления +- [ ] Реализовать синхронизацию офлайн-данных + +## 📄 Лицензия + +Internal use only (для команды NANAHUI) diff --git a/SETUP_COMPLETE.md b/SETUP_COMPLETE.md new file mode 100644 index 0000000..a336584 --- /dev/null +++ b/SETUP_COMPLETE.md @@ -0,0 +1,224 @@ +# ✅ НАСТРОЙКА ЗАВЕРШЕНА! + +## 📊 Что сделано: + +### Firebase +- ✅ Проект создан: `nanax26-1f33b` +- ✅ Firestore база в регионе `eur3` +- ✅ Authentication включён (Google Provider) +- ✅ Android приложение зарегистрировано +- ✅ `google-services.json` добавлен +- ✅ Firebase BoM 34.11.0 подключён +- ✅ Все Firebase зависимости добавлены + +### Android +- ✅ Kotlin DSL (`build.gradle.kts`) +- ✅ Package: `com.nanaxe.nanaxe_crm` +- ✅ Google Services Plugin 4.4.4 +- ✅ SHA-1 получен: `29:A6:36:C1:74:29:43:CE:2E:59:B8:28:46:FF:56:40:2B:9B:92:45` + +### Flutter +- ✅ Зависимости установлены +- ✅ Код готов (3 info warning только о print()) +- ✅ Структура проекта создана +- ✅ 6 экранов готовы +- ✅ Модели данных созданы +- ✅ Сервисы (Firebase, AI, Storage) готовы + +--- + +## 🔑 СЛЕДУЮЩИЕ ШАГИ: + +### 1. Добавь SHA-1 в Firebase Console + +**Сейчас же сделай это:** + +1. Открой: https://console.firebase.google.com/project/nanax26-1f33b/settings/general/android:com.nanaxe.nanaxe_crm +2. Нажми **Add fingerprint** в разделе "SHA certificate fingerprints" +3. Вставь: `29:A6:36:C1:74:29:43:CE:2E:59:B8:28:46:FF:56:40:2B:9B:92:45` +4. Сохрани +5. Перескачай `google-services.json` +6. Замени файл в `c:\Users\ormix\NANAHUI\google-services.json` + +### 2. Загрузи правила безопасности Firestore + +1. Открой: https://console.firebase.google.com/project/nanax26-1f33b/firestore/rules +2. Открой файл `c:\Users\ormix\NANAHUI\firestore.rules` +3. Скопируй всё содержимое +4. Вставь в редактор на сайте (замени текущие правила `allow read, write: if false;`) +5. Нажми **Publish** + +### 3. Запусти приложение + +```bash +cd c:\Users\ormix\NANAHUI +flutter run +``` + +--- + +## 📱 Проверка работы + +### Тест 1: Аутентификация +1. Запусти приложение +2. Нажми "Войти через Google" +3. Выбери аккаунт +4. ✅ Должна произойти авторизация и переход на главный экран + +### Тест 2: Навигация +1. После входа используй нижнюю панель +2. Переключайся между: Главная, Задачи, Mind-карта, Календарь, Будильники +3. ✅ Все экраны должны открываться + +### Тест 3: Firestore (после загрузки правил) +1. Создай задачу через консоль отладки +2. Проверь что она появилась в Firestore Console +3. ✅ Данные должны сохраняться + +--- + +## 📁 Структура проекта + +``` +NANAHUI/ +├── 📄 README.md # Общая информация +├── 📄 QUICKSTART.md # Быстрый старт +├── 📄 ARCHITECTURE.md # Архитектура +├── 📄 FIREBASE_SETUP.md # Настройка Firebase +├── 📄 FIREBASE_DONE.md # Итоги настройки +├── 📄 ADD_SHA1.md # Как добавить SHA-1 +├── 📄 PROJECT_STRUCTURE.md # Структура проекта +├── 📄 SETUP_COMPLETE.md # Этот файл +│ +├── 📂 lib/ +│ ├── 📄 main.dart # Точка входа +│ ├── 📂 core/ # Сервисы +│ │ ├── firebase_config.dart +│ │ ├── firestore_service.dart +│ │ ├── local_storage_service.dart +│ │ ├── ai_service.dart +│ │ ├── router.dart +│ │ └── theme.dart +│ ├── 📂 models/ # Модели данных +│ │ ├── user_model.dart +│ │ ├── task_model.dart +│ │ ├── project_model.dart +│ │ ├── mind_map_model.dart +│ │ ├── calendar_event_model.dart +│ │ └── alarm_model.dart +│ ├── 📂 providers/ # State management +│ │ └── auth_provider.dart +│ └── 📂 screens/ # Экраны +│ ├── auth_screen.dart +│ ├── home_screen.dart +│ ├── dashboard_screen.dart +│ ├── tasks_screen.dart +│ ├── mind_map_screen.dart +│ ├── calendar_screen.dart +│ └── alarms_screen.dart +│ +├── 📂 android/ +│ ├── 📂 app/ +│ │ ├── build.gradle.kts # ✅ Настроен +│ │ └── src/main/AndroidManifest.xml # ✅ Разрешения +│ └── google-services.json # ✅ Firebase config +│ +└── firestore.rules # ✅ Правила безопасности +``` + +--- + +## 🔧 Конфигурация + +### Firebase Project +- **Project ID**: `nanax26-1f33b` +- **Package**: `com.nanaxe.nanaxe_crm` +- **Region**: `eur3` (Europe) + +### SHA-1 Fingerprint +``` +29:A6:36:C1:74:29:43:CE:2E:59:B8:28:46:FF:56:40:2B:9B:92:45 +``` + +### Firebase Dependencies +```kotlin +implementation(platform("com.google.firebase:firebase-bom:34.11.0")) +implementation("com.google.firebase:firebase-analytics") +implementation("com.google.firebase:firebase-auth") +implementation("com.google.firebase:firebase-firestore") +implementation("com.google.firebase:firebase-messaging") +``` + +--- + +## 🆘 Если что-то не работает + +### Ошибка: "Sign-in failed" +``` +1. Проверь что SHA-1 добавлен в Firebase Console +2. Перескачай google-services.json +3. flutter clean && flutter run +``` + +### Ошибка: "Permission denied" +``` +1. Загрузи правила безопасности из firestore.rules +2. Проверь что пользователь аутентифицирован +``` + +### Ошибка: "No Firebase App" +``` +1. Проверь что google-services.json лежит в android/app/ +2. Проверь что package name совпадает +``` + +### Ошибка: "App not found" +``` +1. Проверь что package name в google-services.json совпадает с build.gradle.kts +2. Должно быть: com.nanaxe.nanaxe_crm +``` + +--- + +## 📚 Документация + +- [Firebase Flutter](https://firebase.flutter.dev) +- [Firestore Rules](https://firebase.google.com/docs/firestore/security/get-started) +- [Google Sign-In](https://pub.dev/packages/google_sign_in) +- [Flutter Docs](https://docs.flutter.dev) + +--- + +## 🎯 Что дальше? + +### Ближайшие задачи: +1. ✅ Настроить Firebase (ГОТОВО) +2. ✅ Получить SHA-1 (ГОТОВО) +3. ⏳ Добавить SHA-1 в Firebase Console +4. ⏳ Загрузить правила безопасности +5. ⏳ Протестировать аутентификацию +6. ⏳ Реализовать CRUD задач +7. ⏳ Добавить AI функции +8. ⏳ Интеграция с Google Calendar + +### Для разработки: +```bash +# Запуск с отладкой +flutter run --debug + +# Сборка APK +flutter build apk --release + +# Тесты +flutter test + +# Анализ кода +flutter analyze +``` + +--- + +**Готово! Добавь SHA-1 и загружай правила! 🚀** + +Создано для команды NANAHUI +Март 2026 diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/analysis_options.yaml @@ -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 diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..be3943c --- /dev/null +++ b/android/.gitignore @@ -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 diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts new file mode 100644 index 0000000..e9f67bf --- /dev/null +++ b/android/app/build.gradle.kts @@ -0,0 +1,62 @@ +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") + id("com.google.gms.google-services") +} + +android { + namespace = "com.nanahui.nanahui_crm" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.nanaxe.nanaxe_crm" + // 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 = "../.." +} + +dependencies { + // Import the Firebase BoM + implementation(platform("com.google.firebase:firebase-bom:34.11.0")) + + // Firebase Analytics + implementation("com.google.firebase:firebase-analytics") + + // Firebase Auth + implementation("com.google.firebase:firebase-auth") + + // Firebase Firestore + implementation("com.google.firebase:firebase-firestore") + + // Firebase Messaging + implementation("com.google.firebase:firebase-messaging") +} diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..39490f0 --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/com/nanahui/nanahui_crm/MainActivity.kt b/android/app/src/main/kotlin/com/nanahui/nanahui_crm/MainActivity.kt new file mode 100644 index 0000000..d3ae154 --- /dev/null +++ b/android/app/src/main/kotlin/com/nanahui/nanahui_crm/MainActivity.kt @@ -0,0 +1,5 @@ +package com.nanahui.nanahui_crm + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 0000000..538d81e --- /dev/null +++ b/android/build.gradle.kts @@ -0,0 +1,28 @@ +plugins { + id("com.google.gms.google-services") version "4.4.4" apply false +} + +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("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..fbee1d8 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e4ef43f --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -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.14-all.zip diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts new file mode 100644 index 0000000..ca7fe06 --- /dev/null +++ b/android/settings.gradle.kts @@ -0,0 +1,26 @@ +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.11.1" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false +} + +include(":app") diff --git a/assets/icons/.gitkeep b/assets/icons/.gitkeep new file mode 100644 index 0000000..3b44cf3 --- /dev/null +++ b/assets/icons/.gitkeep @@ -0,0 +1 @@ +.gitkeep diff --git a/assets/images/.gitkeep b/assets/images/.gitkeep new file mode 100644 index 0000000..9a8a416 --- /dev/null +++ b/assets/images/.gitkeep @@ -0,0 +1 @@ +.gitkeep diff --git a/firestore.rules b/firestore.rules new file mode 100644 index 0000000..9138657 --- /dev/null +++ b/firestore.rules @@ -0,0 +1,94 @@ +rules_version = '2'; + +service cloud.firestore { + match /databases/{database}/documents { + + // Helper functions + function isAuthenticated() { + return request.auth != null; + } + + function isOwner(userId) { + return request.auth.uid == userId; + } + + function isTeamMember(teamId) { + let team = get(/databases/$(database)/documents/teams/$(teamId)).data; + return team.memberIds.hasAny([request.auth.uid]); + } + + // Users - пользователи могут читать и обновлять свой профиль + match /users/{userId} { + allow read: if isAuthenticated(); + allow create: if isOwner(userId); + allow update, delete: if isOwner(userId); + } + + // Teams - команды + match /teams/{teamId} { + allow read: if isAuthenticated(); + allow create: if isAuthenticated(); + allow update: if isAuthenticated() && isTeamMember(teamId); + allow delete: if isAuthenticated() && isTeamMember(teamId); + + // Подколлекции команд + match /projects/{projectId} { + allow read: if isAuthenticated(); + allow create: if isAuthenticated(); + allow update, delete: if isAuthenticated(); + } + } + + // Projects - проекты + match /projects/{projectId} { + allow read: if isAuthenticated(); + allow create: if isAuthenticated(); + allow update, delete: if isAuthenticated(); + } + + // Tasks - задачи + match /tasks/{taskId} { + allow read: if isAuthenticated(); + allow create: if isAuthenticated(); + allow update: if isAuthenticated(); + allow delete: if isAuthenticated(); + } + + // Mind Maps - карты проектов + match /mindmaps/{projectId} { + allow read: if isAuthenticated(); + allow create: if isAuthenticated(); + allow update: if isAuthenticated(); + allow delete: if isAuthenticated(); + + match /nodes/{nodeId} { + allow read: if isAuthenticated(); + allow create: if isAuthenticated(); + allow update: if isAuthenticated(); + allow delete: if isAuthenticated(); + } + } + + // Events - события календаря + match /events/{eventId} { + allow read: if isAuthenticated(); + allow create: if isAuthenticated(); + allow update: if isAuthenticated(); + allow delete: if isAuthenticated(); + } + + // Alarms - будильники + match /alarms/{alarmId} { + allow read: if isAuthenticated(); + allow create: if isAuthenticated(); + allow update: if isAuthenticated(); + allow delete: if isAuthenticated(); + } + + // Settings - настройки + match /settings/{settingId} { + allow read: if isAuthenticated(); + allow write: if isAuthenticated(); + } + } +} diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/ios/.gitignore @@ -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 diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..391a902 --- /dev/null +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + + diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..33953ad --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,620 @@ +// !$*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 */; }; + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.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 = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 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 = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 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 = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* 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 = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 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 */, + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* 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 */, + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift 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 = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* 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 = 13.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.nanahui.nanahuiCrm; + 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.nanahui.nanahuiCrm.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.nanahui.nanahuiCrm.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.nanahui.nanahuiCrm.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 = 13.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 = 13.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.nanahui.nanahuiCrm; + 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.nanahui.nanahuiCrm; + 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 */; +} diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..e3773d4 --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..c30b367 --- /dev/null +++ b/ios/Runner/AppDelegate.swift @@ -0,0 +1,16 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -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" + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..0467bf1 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -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" + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -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. \ No newline at end of file diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist new file mode 100644 index 0000000..8e7fdc8 --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,70 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Nanahui Crm + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + nanahui_crm + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/ios/Runner/SceneDelegate.swift b/ios/Runner/SceneDelegate.swift new file mode 100644 index 0000000..b9ce8ea --- /dev/null +++ b/ios/Runner/SceneDelegate.swift @@ -0,0 +1,6 @@ +import Flutter +import UIKit + +class SceneDelegate: FlutterSceneDelegate { + +} diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/ios/RunnerTests/RunnerTests.swift @@ -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. + } + +} diff --git a/lib/core/ai_service.dart b/lib/core/ai_service.dart new file mode 100644 index 0000000..6ee29d7 --- /dev/null +++ b/lib/core/ai_service.dart @@ -0,0 +1,228 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import '../models/task_model.dart'; + +class AIService { + final String? openaiApiKey; + final String? anthropicApiKey; + final String? googleAiApiKey; + + AIService({ + this.openaiApiKey, + this.anthropicApiKey, + this.googleAiApiKey, + }); + + // Генерация подзадач из основной задачи + Future>> generateSubtasks({ + required String title, + required String description, + }) async { + if (openaiApiKey == null) { + return _mockGenerateSubtasks(title, description); + } + + try { + final response = await http.post( + Uri.parse('https://api.openai.com/v1/chat/completions'), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $openaiApiKey', + }, + body: jsonEncode({ + 'model': 'gpt-4o-mini', + 'messages': [ + { + 'role': 'system', + 'content': 'You are a task planning assistant. Break down tasks into logical subtasks.', + }, + { + 'role': 'user', + 'content': 'Break down this task into 3-7 subtasks:\n\nTitle: $title\nDescription: $description\n\nReturn ONLY a JSON array of subtasks with format: [{"title": "...", "estimatedHours": 0}]', + }, + ], + 'temperature': 0.7, + }), + ); + + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + final content = data['choices'][0]['message']['content']; + final List subtasks = jsonDecode(content); + return subtasks.cast>(); + } + } catch (e) { + print('AI Service Error: $e'); + } + + return _mockGenerateSubtasks(title, description); + } + + // AI приоритизация задач + Future prioritizeTask({ + required String title, + required String description, + DateTime? dueDate, + }) async { + if (openaiApiKey == null) { + return _mockPrioritizeTask(dueDate); + } + + try { + final response = await http.post( + Uri.parse('https://api.openai.com/v1/chat/completions'), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $openaiApiKey', + }, + body: jsonEncode({ + 'model': 'gpt-4o-mini', + 'messages': [ + { + 'role': 'system', + 'content': 'You are a task prioritization assistant. Determine task priority: low, medium, high, or urgent.', + }, + { + 'role': 'user', + 'content': 'What priority should this task have?\n\nTitle: $title\nDescription: $description\nDue: ${dueDate ?? "Not specified"}\n\nReply with ONE word: low, medium, high, or urgent', + }, + ], + 'temperature': 0.3, + }), + ); + + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + final content = data['choices'][0]['message']['content'].trim().toLowerCase(); + + switch (content) { + case 'urgent': return TaskPriority.urgent; + case 'high': return TaskPriority.high; + case 'low': return TaskPriority.low; + default: return TaskPriority.medium; + } + } + } catch (e) { + print('AI Service Error: $e'); + } + + return _mockPrioritizeTask(dueDate); + } + + // Умное планирование - распределение задач по времени + Future>> smartSchedule({ + required List tasks, + required DateTime startDate, + required DateTime endDate, + required int availableHoursPerDay, + }) async { + // Mock implementation для начала + final schedule = >[]; + var currentDate = startDate; + var remainingHours = availableHoursPerDay; + + for (final task in tasks) { + if (currentDate.isAfter(endDate)) break; + + if (task.estimatedHours <= remainingHours) { + schedule.add({ + 'taskId': task.id, + 'date': currentDate, + 'hours': task.estimatedHours, + }); + remainingHours -= task.estimatedHours; + } else { + // Переходим на следующий день + currentDate = currentDate.add(const Duration(days: 1)); + remainingHours = availableHoursPerDay; + + if (currentDate.isAfter(endDate)) break; + + schedule.add({ + 'taskId': task.id, + 'date': currentDate, + 'hours': task.estimatedHours > remainingHours + ? remainingHours + : task.estimatedHours, + }); + remainingHours -= task.estimatedHours > remainingHours + ? remainingHours + : task.estimatedHours; + } + } + + return schedule; + } + + // Генерация описания задачи + Future generateTaskDescription({ + required String title, + String? context, + }) async { + if (openaiApiKey == null) { + return 'Описание для задачи: $title'; + } + + try { + final response = await http.post( + Uri.parse('https://api.openai.com/v1/chat/completions'), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $openaiApiKey', + }, + body: jsonEncode({ + 'model': 'gpt-4o-mini', + 'messages': [ + { + 'role': 'system', + 'content': 'You are a task description writer. Write clear, concise task descriptions.', + }, + { + 'role': 'user', + 'content': 'Write a brief description for this task:\n\nTitle: $title\nContext: ${context ?? "No additional context"}\n\nKeep it under 100 words.', + }, + ], + 'temperature': 0.5, + }), + ); + + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + return data['choices'][0]['message']['content']; + } + } catch (e) { + print('AI Service Error: $e'); + } + + return 'Описание для задачи: $title'; + } + + // Mock данные для тестирования + List> _mockGenerateSubtasks(String title, String description) { + return [ + {'title': 'Анализ требований', 'estimatedHours': 2}, + {'title': 'Проектирование', 'estimatedHours': 3}, + {'title': 'Реализация', 'estimatedHours': 8}, + {'title': 'Тестирование', 'estimatedHours': 3}, + {'title': 'Документирование', 'estimatedHours': 2}, + ]; + } + + TaskPriority _mockPrioritizeTask(DateTime? dueDate) { + if (dueDate == null) return TaskPriority.medium; + + final daysUntilDue = dueDate.difference(DateTime.now()).inDays; + + if (daysUntilDue <= 1) return TaskPriority.urgent; + if (daysUntilDue <= 3) return TaskPriority.high; + if (daysUntilDue <= 7) return TaskPriority.medium; + return TaskPriority.low; + } +} + +final aiService = AIService( + // API ключи лучше хранить в .env файле или Firebase Remote Config + openaiApiKey: null, // Добавить ключ здесь или через env + anthropicApiKey: null, + googleAiApiKey: null, +); diff --git a/lib/core/firebase_config.dart b/lib/core/firebase_config.dart new file mode 100644 index 0000000..08e4967 --- /dev/null +++ b/lib/core/firebase_config.dart @@ -0,0 +1,14 @@ +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/foundation.dart'; + +class FirebaseConfig { + static Future initialize() async { + await Firebase.initializeApp(); + debugPrint('Firebase initialized successfully'); + } + + static Future setupFirestore() async { + // Настройки Firestore для лучшей производительности + // Будут применены после инициализации + } +} diff --git a/lib/core/firestore_service.dart b/lib/core/firestore_service.dart new file mode 100644 index 0000000..3ce3c76 --- /dev/null +++ b/lib/core/firestore_service.dart @@ -0,0 +1,201 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import '../models/task_model.dart'; +import '../models/project_model.dart'; +import '../models/mind_map_model.dart'; +import '../models/calendar_event_model.dart'; +import '../models/alarm_model.dart'; +import '../models/user_model.dart'; + +class FirestoreService { + final FirebaseFirestore _db = FirebaseFirestore.instance; + + // === USERS === + Future createUser(UserModel user) async { + await _db.collection('users').doc(user.id).set(user.toFirestore()); + } + + Future getUser(String userId) async { + final doc = await _db.collection('users').doc(userId).get(); + return doc.exists ? UserModel.fromFirestore(doc) : null; + } + + Future updateUser(UserModel user) async { + await _db.collection('users').doc(user.id).update(user.toFirestore()); + } + + // === PROJECTS === + Future createProject(ProjectModel project) async { + await _db.collection('projects').doc(project.id).set(project.toFirestore()); + } + + Future> getProjectsByTeam(String teamId) async { + final snapshot = await _db + .collection('projects') + .where('teamId', isEqualTo: teamId) + .orderBy('createdAt', descending: true) + .get(); + + return snapshot.docs.map((doc) => ProjectModel.fromFirestore(doc)).toList(); + } + + Future> getProjectsByUser(String userId) async { + final snapshot = await _db + .collection('projects') + .where('memberIds', arrayContains: userId) + .get(); + + return snapshot.docs.map((doc) => ProjectModel.fromFirestore(doc)).toList(); + } + + // === TASKS === + Future createTask(TaskModel task) async { + await _db.collection('tasks').doc(task.id).set(task.toFirestore()); + } + + Future> getTasksByProject(String projectId) async { + final snapshot = await _db + .collection('tasks') + .where('projectId', isEqualTo: projectId) + .orderBy('createdAt', descending: true) + .get(); + + return snapshot.docs.map((doc) => TaskModel.fromFirestore(doc)).toList(); + } + + Future> getTasksByAssignee(String assigneeId) async { + final snapshot = await _db + .collection('tasks') + .where('assigneeId', isEqualTo: assigneeId) + .where('status', whereIn: ['todo', 'inProgress', 'review']) + .get(); + + return snapshot.docs.map((doc) => TaskModel.fromFirestore(doc)).toList(); + } + + Future updateTask(TaskModel task) async { + await _db.collection('tasks').doc(task.id).update(task.toFirestore()); + } + + Future deleteTask(String taskId) async { + await _db.collection('tasks').doc(taskId).delete(); + } + + // Задачи с поддержкой иерархии (для mind-map) + Future> getTaskHierarchy(String projectId) async { + final snapshot = await _db + .collection('tasks') + .where('projectId', isEqualTo: projectId) + .get(); + + return snapshot.docs.map((doc) => TaskModel.fromFirestore(doc)).toList(); + } + + // === MIND MAPS === + Future createMindMapNode(MindMapNode node) async { + await _db.collection('mindmaps').doc(node.projectId).collection('nodes').doc(node.id).set(node.toFirestore()); + } + + Future> getMindMapNodes(String projectId) async { + final snapshot = await _db + .collection('mindmaps') + .doc(projectId) + .collection('nodes') + .orderBy('level') + .get(); + + return snapshot.docs.map((doc) => MindMapNode.fromFirestore(doc)).toList(); + } + + Future updateMindMapNode(MindMapNode node) async { + await _db + .collection('mindmaps') + .doc(node.projectId) + .collection('nodes') + .doc(node.id) + .update(node.toFirestore()); + } + + // === CALENDAR EVENTS === + Future createEvent(CalendarEventModel event) async { + await _db.collection('events').doc(event.id).set(event.toFirestore()); + } + + Future> getEventsByDateRange({ + required DateTime start, + required DateTime end, + String? userId, + }) async { + Query query = _db + .collection('events') + .where('startTime', isLessThanOrEqualTo: Timestamp.fromDate(end)) + .where('endTime', isGreaterThanOrEqualTo: Timestamp.fromDate(start)); + + if (userId != null) { + query = query.where('attendeeIds', arrayContains: userId); + } + + final snapshot = await query.get(); + return snapshot.docs.map((doc) => CalendarEventModel.fromFirestore(doc)).toList(); + } + + Future updateEvent(CalendarEventModel event) async { + await _db.collection('events').doc(event.id).update(event.toFirestore()); + } + + Future deleteEvent(String eventId) async { + await _db.collection('events').doc(eventId).delete(); + } + + // === ALARMS === + Future createAlarm(AlarmModel alarm) async { + await _db.collection('alarms').doc(alarm.id).set(alarm.toFirestore()); + } + + Future> getAlarmsByUser(String userId) async { + final snapshot = await _db + .collection('alarms') + .where('isEnabled', isEqualTo: true) + .orderBy('dateTime') + .get(); + + return snapshot.docs.map((doc) => AlarmModel.fromFirestore(doc)).toList(); + } + + Future updateAlarm(AlarmModel alarm) async { + await _db.collection('alarms').doc(alarm.id).update(alarm.toFirestore()); + } + + Future deleteAlarm(String alarmId) async { + await _db.collection('alarms').doc(alarmId).delete(); + } + + // === BATCH OPERATIONS === + Future syncTasks(List tasks) async { + final batch = _db.batch(); + + for (final task in tasks) { + batch.set(_db.collection('tasks').doc(task.id), task.toFirestore()); + } + + await batch.commit(); + } + + // === STREAMS (для реального времени) === + Stream> watchTasks(String projectId) { + return _db + .collection('tasks') + .where('projectId', isEqualTo: projectId) + .snapshots() + .map((snapshot) => snapshot.docs.map((doc) => TaskModel.fromFirestore(doc)).toList()); + } + + Stream> watchEvents(String userId) { + return _db + .collection('events') + .where('attendeeIds', arrayContains: userId) + .snapshots() + .map((snapshot) => snapshot.docs.map((doc) => CalendarEventModel.fromFirestore(doc)).toList()); + } +} + +final firestoreService = FirestoreService(); diff --git a/lib/core/local_storage_service.dart b/lib/core/local_storage_service.dart new file mode 100644 index 0000000..a943ff2 --- /dev/null +++ b/lib/core/local_storage_service.dart @@ -0,0 +1,138 @@ +import 'package:hive_flutter/hive_flutter.dart'; +import '../models/task_model.dart'; +import '../models/alarm_model.dart'; +import '../models/mind_map_model.dart'; +import '../models/calendar_event_model.dart'; + +class LocalStorageService { + static const String _tasksBoxName = 'tasks'; + static const String _alarmsBoxName = 'alarms'; + static const String _mindMapsBoxName = 'mindMaps'; + static const String _eventsBoxName = 'events'; + static const String _settingsBoxName = 'settings'; + + late Box _tasksBox; + late Box _alarmsBox; + late Box _mindMapsBox; + late Box _eventsBox; + late Box _settingsBox; + + Future initialize() async { + await Hive.initFlutter(); + + _tasksBox = await Hive.openBox(_tasksBoxName); + _alarmsBox = await Hive.openBox(_alarmsBoxName); + _mindMapsBox = await Hive.openBox(_mindMapsBoxName); + _eventsBox = await Hive.openBox(_eventsBoxName); + _settingsBox = await Hive.openBox(_settingsBoxName); + } + + // Tasks + Future saveTask(TaskModel task) async { + await _tasksBox.put(task.id, task.toFirestore()); + } + + Future saveTasks(List tasks) async { + for (final task in tasks) { + await _tasksBox.put(task.id, task.toFirestore()); + } + } + + List getTasks() { + return _tasksBox.values.map((data) { + return TaskModel.fromFirestore( + _createMockDocument(data as Map), + ); + }).toList(); + } + + Future deleteTask(String taskId) async { + await _tasksBox.delete(taskId); + } + + // Alarms + Future saveAlarm(AlarmModel alarm) async { + await _alarmsBox.put(alarm.id, alarm.toFirestore()); + } + + List getAlarms() { + return _alarmsBox.values.map((data) { + return AlarmModel.fromFirestore( + _createMockDocument(data as Map), + ); + }).toList(); + } + + Future deleteAlarm(String alarmId) async { + await _alarmsBox.delete(alarmId); + } + + // Mind Maps + Future saveMindMapNodes(List nodes) async { + for (final node in nodes) { + await _mindMapsBox.put(node.id, node.toFirestore()); + } + } + + List getMindMapNodes() { + return _mindMapsBox.values.map((data) { + return MindMapNode.fromFirestore( + _createMockDocument(data as Map), + ); + }).toList(); + } + + // Events + Future saveEvent(CalendarEventModel event) async { + await _eventsBox.put(event.id, event.toFirestore()); + } + + List getEvents() { + return _eventsBox.values.map((data) { + return CalendarEventModel.fromFirestore( + _createMockDocument(data as Map), + ); + }).toList(); + } + + // Settings + Future saveSetting(String key, Object? value) async { + await _settingsBox.put(key, value); + } + + T? getSetting(String key, {T? defaultValue}) { + return _settingsBox.get(key, defaultValue: defaultValue) as T?; + } + + // Clear all local data + Future clearAll() async { + await _tasksBox.clear(); + await _alarmsBox.clear(); + await _mindMapsBox.clear(); + await _eventsBox.clear(); + await _settingsBox.clear(); + } + + // Sync with Firebase (merge remote and local) + Future syncWithRemote(List remoteTasks) async { + final localTasks = getTasks(); + final localMap = {for (var t in localTasks) t.id: t}; + final remoteMap = {for (var t in remoteTasks) t.id: t}; + + // Merge: remote takes precedence + final merged = {...localMap, ...remoteMap}; + + await _tasksBox.clear(); + for (final task in merged.values) { + await _tasksBox.put(task.id, task.toFirestore()); + } + } + + // Helper method to create mock DocumentSnapshot + dynamic _createMockDocument(Map data) { + // Простая эмуляция для конвертации + return data; + } +} + +final localStorageService = LocalStorageService(); diff --git a/lib/core/router.dart b/lib/core/router.dart new file mode 100644 index 0000000..0840d0c --- /dev/null +++ b/lib/core/router.dart @@ -0,0 +1,47 @@ +import 'package:go_router/go_router.dart'; +import '../screens/auth_screen.dart'; +import '../screens/home_screen.dart'; +import '../screens/tasks_screen.dart'; +import '../screens/mind_map_screen.dart'; +import '../screens/calendar_screen.dart'; +import '../screens/alarms_screen.dart'; + +class AppRouter { + static final GoRouter router = GoRouter( + initialLocation: '/auth', + routes: [ + GoRoute( + path: '/auth', + name: 'auth', + builder: (context, state) => const AuthScreen(), + ), + GoRoute( + path: '/home', + name: 'home', + builder: (context, state) => const HomeScreen(), + routes: [ + GoRoute( + path: 'tasks', + name: 'tasks', + builder: (context, state) => const TasksScreen(), + ), + GoRoute( + path: 'mindmap', + name: 'mindmap', + builder: (context, state) => const MindMapScreen(), + ), + GoRoute( + path: 'calendar', + name: 'calendar', + builder: (context, state) => const CalendarScreen(), + ), + GoRoute( + path: 'alarms', + name: 'alarms', + builder: (context, state) => const AlarmsScreen(), + ), + ], + ), + ], + ); +} diff --git a/lib/core/theme.dart b/lib/core/theme.dart new file mode 100644 index 0000000..1ef9f72 --- /dev/null +++ b/lib/core/theme.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; + +class AppTheme { + static ThemeData get lightTheme { + return ThemeData( + useMaterial3: true, + colorScheme: ColorScheme.fromSeed( + seedColor: Colors.blue, + brightness: Brightness.light, + ), + appBarTheme: const AppBarTheme( + centerTitle: true, + elevation: 0, + ), + cardTheme: CardThemeData( + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + floatingActionButtonTheme: const FloatingActionButtonThemeData( + elevation: 4, + ), + ); + } + + static ThemeData get darkTheme { + return ThemeData( + useMaterial3: true, + colorScheme: ColorScheme.fromSeed( + seedColor: Colors.blue, + brightness: Brightness.dark, + ), + appBarTheme: const AppBarTheme( + centerTitle: true, + elevation: 0, + ), + cardTheme: CardThemeData( + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + floatingActionButtonTheme: const FloatingActionButtonThemeData( + elevation: 4, + ), + ); + } +} diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..0b48b48 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'core/firebase_config.dart'; +import 'core/local_storage_service.dart'; +import 'core/theme.dart'; +import 'core/router.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + // Инициализация Firebase + await FirebaseConfig.initialize(); + + // Инициализация локального хранилища + await localStorageService.initialize(); + + runApp( + const ProviderScope( + child: NanahuiCrmApp(), + ), + ); +} + +class NanahuiCrmApp extends StatelessWidget { + const NanahuiCrmApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp.router( + title: 'NANAHUI CRM', + debugShowCheckedModeBanner: false, + theme: AppTheme.lightTheme, + darkTheme: AppTheme.darkTheme, + themeMode: ThemeMode.system, + routerConfig: AppRouter.router, + ); + } +} diff --git a/lib/models/alarm_model.dart b/lib/models/alarm_model.dart new file mode 100644 index 0000000..ff7394c --- /dev/null +++ b/lib/models/alarm_model.dart @@ -0,0 +1,132 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; + +class AlarmModel { + final String id; + final String title; + final String? taskId; + final String? eventId; + final DateTime dateTime; + final bool isRepeating; + final String repeatPattern; // daily, weekly, monthly, custom + final List repeatDays; // For custom (e.g., [1, 3, 5] for Mon, Wed, Fri) + final bool isEnabled; + final String sound; + final int snoozeMinutes; + final Map metadata; + final DateTime createdAt; + final DateTime updatedAt; + + AlarmModel({ + required this.id, + required this.title, + this.taskId, + this.eventId, + required this.dateTime, + this.isRepeating = false, + this.repeatPattern = 'daily', + this.repeatDays = const [], + this.isEnabled = true, + this.sound = 'default', + this.snoozeMinutes = 5, + this.metadata = const {}, + required this.createdAt, + required this.updatedAt, + }); + + factory AlarmModel.fromFirestore(DocumentSnapshot doc) { + final data = doc.data() as Map; + return AlarmModel( + id: doc.id, + title: data['title'] ?? '', + taskId: data['taskId'], + eventId: data['eventId'], + dateTime: (data['dateTime'] as Timestamp).toDate(), + isRepeating: data['isRepeating'] ?? false, + repeatPattern: data['repeatPattern'] ?? 'daily', + repeatDays: List.from(data['repeatDays'] ?? []), + isEnabled: data['isEnabled'] ?? true, + sound: data['sound'] ?? 'default', + snoozeMinutes: data['snoozeMinutes'] ?? 5, + metadata: data['metadata'] ?? {}, + createdAt: (data['createdAt'] as Timestamp).toDate(), + updatedAt: (data['updatedAt'] as Timestamp).toDate(), + ); + } + + Map toFirestore() { + return { + 'title': title, + 'taskId': taskId, + 'eventId': eventId, + 'dateTime': Timestamp.fromDate(dateTime), + 'isRepeating': isRepeating, + 'repeatPattern': repeatPattern, + 'repeatDays': repeatDays, + 'isEnabled': isEnabled, + 'sound': sound, + 'snoozeMinutes': snoozeMinutes, + 'metadata': metadata, + 'createdAt': Timestamp.fromDate(createdAt), + 'updatedAt': Timestamp.fromDate(updatedAt), + }; + } + + AlarmModel copyWith({ + String? id, + String? title, + String? taskId, + String? eventId, + DateTime? dateTime, + bool? isRepeating, + String? repeatPattern, + List? repeatDays, + bool? isEnabled, + String? sound, + int? snoozeMinutes, + Map? metadata, + DateTime? createdAt, + DateTime? updatedAt, + }) { + return AlarmModel( + id: id ?? this.id, + title: title ?? this.title, + taskId: taskId ?? this.taskId, + eventId: eventId ?? this.eventId, + dateTime: dateTime ?? this.dateTime, + isRepeating: isRepeating ?? this.isRepeating, + repeatPattern: repeatPattern ?? this.repeatPattern, + repeatDays: repeatDays ?? this.repeatDays, + isEnabled: isEnabled ?? this.isEnabled, + sound: sound ?? this.sound, + snoozeMinutes: snoozeMinutes ?? this.snoozeMinutes, + metadata: metadata ?? this.metadata, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + DateTime get nextOccurrence { + if (!isRepeating) return dateTime; + + final now = DateTime.now(); + if (dateTime.isAfter(now)) return dateTime; + + switch (repeatPattern) { + case 'daily': + return dateTime.add(const Duration(days: 1)); + case 'weekly': + return dateTime.add(const Duration(days: 7)); + case 'monthly': + return DateTime(dateTime.year, dateTime.month + 1, dateTime.day); + case 'custom': + // Find next weekday in repeatDays + var next = dateTime.add(const Duration(days: 1)); + while (!repeatDays.contains(next.weekday)) { + next = next.add(const Duration(days: 1)); + } + return DateTime(next.year, next.month, next.day, dateTime.hour, dateTime.minute); + default: + return dateTime; + } + } +} diff --git a/lib/models/calendar_event_model.dart b/lib/models/calendar_event_model.dart new file mode 100644 index 0000000..a1f98d2 --- /dev/null +++ b/lib/models/calendar_event_model.dart @@ -0,0 +1,133 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; + +class CalendarEventModel { + final String id; + final String title; + final String? description; + final String location; + final DateTime startTime; + final DateTime endTime; + final bool isAllDay; + final String? googleEventId; + final String? taskId; + final List attendeeIds; + final List attendeeEmails; + final String color; + final String? recurringRuleId; + final Map reminders; + final String status; // confirmed, tentative, cancelled + final String createdBy; + final DateTime createdAt; + final DateTime updatedAt; + + CalendarEventModel({ + required this.id, + required this.title, + this.description, + this.location = '', + required this.startTime, + required this.endTime, + this.isAllDay = false, + this.googleEventId, + this.taskId, + this.attendeeIds = const [], + this.attendeeEmails = const [], + this.color = '#2196F3', + this.recurringRuleId, + this.reminders = const {}, + this.status = 'confirmed', + required this.createdBy, + required this.createdAt, + required this.updatedAt, + }); + + factory CalendarEventModel.fromFirestore(DocumentSnapshot doc) { + final data = doc.data() as Map; + return CalendarEventModel( + id: doc.id, + title: data['title'] ?? '', + description: data['description'], + location: data['location'] ?? '', + startTime: (data['startTime'] as Timestamp).toDate(), + endTime: (data['endTime'] as Timestamp).toDate(), + isAllDay: data['isAllDay'] ?? false, + googleEventId: data['googleEventId'], + taskId: data['taskId'], + attendeeIds: List.from(data['attendeeIds'] ?? []), + attendeeEmails: List.from(data['attendeeEmails'] ?? []), + color: data['color'] ?? '#2196F3', + recurringRuleId: data['recurringRuleId'], + reminders: data['reminders'] ?? {}, + status: data['status'] ?? 'confirmed', + createdBy: data['createdBy'] ?? '', + createdAt: (data['createdAt'] as Timestamp).toDate(), + updatedAt: (data['updatedAt'] as Timestamp).toDate(), + ); + } + + Map toFirestore() { + return { + 'title': title, + 'description': description, + 'location': location, + 'startTime': Timestamp.fromDate(startTime), + 'endTime': Timestamp.fromDate(endTime), + 'isAllDay': isAllDay, + 'googleEventId': googleEventId, + 'taskId': taskId, + 'attendeeIds': attendeeIds, + 'attendeeEmails': attendeeEmails, + 'color': color, + 'recurringRuleId': recurringRuleId, + 'reminders': reminders, + 'status': status, + 'createdBy': createdBy, + 'createdAt': Timestamp.fromDate(createdAt), + 'updatedAt': Timestamp.fromDate(updatedAt), + }; + } + + CalendarEventModel copyWith({ + String? id, + String? title, + String? description, + String? location, + DateTime? startTime, + DateTime? endTime, + bool? isAllDay, + String? googleEventId, + String? taskId, + List? attendeeIds, + List? attendeeEmails, + String? color, + String? recurringRuleId, + Map? reminders, + String? status, + String? createdBy, + DateTime? createdAt, + DateTime? updatedAt, + }) { + return CalendarEventModel( + id: id ?? this.id, + title: title ?? this.title, + description: description ?? this.description, + location: location ?? this.location, + startTime: startTime ?? this.startTime, + endTime: endTime ?? this.endTime, + isAllDay: isAllDay ?? this.isAllDay, + googleEventId: googleEventId ?? this.googleEventId, + taskId: taskId ?? this.taskId, + attendeeIds: attendeeIds ?? this.attendeeIds, + attendeeEmails: attendeeEmails ?? this.attendeeEmails, + color: color ?? this.color, + recurringRuleId: recurringRuleId ?? this.recurringRuleId, + reminders: reminders ?? this.reminders, + status: status ?? this.status, + createdBy: createdBy ?? this.createdBy, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + Duration get duration => endTime.difference(startTime); +} diff --git a/lib/models/mind_map_model.dart b/lib/models/mind_map_model.dart new file mode 100644 index 0000000..99f0576 --- /dev/null +++ b/lib/models/mind_map_model.dart @@ -0,0 +1,165 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; + +class MindMapNode { + final String id; + final String projectId; + final String? parentId; + final int level; + final String title; + final String? description; + final String? taskId; // Link to task + final String color; + final String icon; + final Map metadata; + final double x; + final double y; + final DateTime createdAt; + final DateTime updatedAt; + + MindMapNode({ + required this.id, + required this.projectId, + this.parentId, + required this.level, + required this.title, + this.description, + this.taskId, + this.color = '#2196F3', + this.icon = 'folder', + this.metadata = const {}, + this.x = 0, + this.y = 0, + required this.createdAt, + required this.updatedAt, + }); + + factory MindMapNode.fromFirestore(DocumentSnapshot doc) { + final data = doc.data() as Map; + return MindMapNode( + id: doc.id, + projectId: data['projectId'] ?? '', + parentId: data['parentId'], + level: data['level'] ?? 0, + title: data['title'] ?? '', + description: data['description'], + taskId: data['taskId'], + color: data['color'] ?? '#2196F3', + icon: data['icon'] ?? 'folder', + metadata: data['metadata'] ?? {}, + x: (data['x'] ?? 0).toDouble(), + y: (data['y'] ?? 0).toDouble(), + createdAt: (data['createdAt'] as Timestamp).toDate(), + updatedAt: (data['updatedAt'] as Timestamp).toDate(), + ); + } + + Map toFirestore() { + return { + 'projectId': projectId, + 'parentId': parentId, + 'level': level, + 'title': title, + 'description': description, + 'taskId': taskId, + 'color': color, + 'icon': icon, + 'metadata': metadata, + 'x': x, + 'y': y, + 'createdAt': Timestamp.fromDate(createdAt), + 'updatedAt': Timestamp.fromDate(updatedAt), + }; + } + + MindMapNode copyWith({ + String? id, + String? projectId, + String? parentId, + int? level, + String? title, + String? description, + String? taskId, + String? color, + String? icon, + Map? metadata, + double? x, + double? y, + DateTime? createdAt, + DateTime? updatedAt, + }) { + return MindMapNode( + id: id ?? this.id, + projectId: projectId ?? this.projectId, + parentId: parentId ?? this.parentId, + level: level ?? this.level, + title: title ?? this.title, + description: description ?? this.description, + taskId: taskId ?? this.taskId, + color: color ?? this.color, + icon: icon ?? this.icon, + metadata: metadata ?? this.metadata, + x: x ?? this.x, + y: y ?? this.y, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } +} + +class MindMapModel { + final String id; + final String projectId; + final String title; + final String description; + final String rootNodeId; + final List nodes; + final Map layout; + final String createdBy; + final DateTime createdAt; + final DateTime updatedAt; + + MindMapModel({ + required this.id, + required this.projectId, + required this.title, + required this.description, + required this.rootNodeId, + required this.nodes, + required this.layout, + required this.createdBy, + required this.createdAt, + required this.updatedAt, + }); + + factory MindMapModel.fromFirestore({ + required String id, + required Map data, + required List nodes, + }) { + return MindMapModel( + id: id, + projectId: data['projectId'] ?? '', + title: data['title'] ?? '', + description: data['description'] ?? '', + rootNodeId: data['rootNodeId'] ?? '', + nodes: nodes, + layout: data['layout'] ?? {}, + createdBy: data['createdBy'] ?? '', + createdAt: (data['createdAt'] as Timestamp).toDate(), + updatedAt: (data['updatedAt'] as Timestamp).toDate(), + ); + } + + Map toFirestore() { + return { + 'projectId': projectId, + 'title': title, + 'description': description, + 'rootNodeId': rootNodeId, + 'layout': layout, + 'createdBy': createdBy, + 'createdAt': Timestamp.fromDate(createdAt), + 'updatedAt': Timestamp.fromDate(updatedAt), + }; + } +} diff --git a/lib/models/project_model.dart b/lib/models/project_model.dart new file mode 100644 index 0000000..3434245 --- /dev/null +++ b/lib/models/project_model.dart @@ -0,0 +1,117 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; + +class ProjectModel { + final String id; + final String name; + final String description; + final String teamId; + final String ownerId; + final List memberIds; + final String status; + final DateTime startDate; + final DateTime? endDate; + final String color; + final String icon; + final Map settings; + final DateTime createdAt; + final DateTime updatedAt; + + ProjectModel({ + required this.id, + required this.name, + required this.description, + required this.teamId, + required this.ownerId, + this.memberIds = const [], + this.status = 'active', + required this.startDate, + this.endDate, + this.color = '#1976D2', + this.icon = 'project', + this.settings = const {}, + required this.createdAt, + required this.updatedAt, + }); + + factory ProjectModel.fromFirestore(DocumentSnapshot doc) { + final data = doc.data() as Map; + return ProjectModel( + id: doc.id, + name: data['name'] ?? '', + description: data['description'] ?? '', + teamId: data['teamId'] ?? '', + ownerId: data['ownerId'] ?? '', + memberIds: List.from(data['memberIds'] ?? []), + status: data['status'] ?? 'active', + startDate: (data['startDate'] as Timestamp).toDate(), + endDate: data['endDate'] != null + ? (data['endDate'] as Timestamp).toDate() + : null, + color: data['color'] ?? '#1976D2', + icon: data['icon'] ?? 'project', + settings: data['settings'] ?? {}, + createdAt: (data['createdAt'] as Timestamp).toDate(), + updatedAt: (data['updatedAt'] as Timestamp).toDate(), + ); + } + + Map toFirestore() { + return { + 'name': name, + 'description': description, + 'teamId': teamId, + 'ownerId': ownerId, + 'memberIds': memberIds, + 'status': status, + 'startDate': Timestamp.fromDate(startDate), + 'endDate': endDate != null ? Timestamp.fromDate(endDate!) : null, + 'color': color, + 'icon': icon, + 'settings': settings, + 'createdAt': Timestamp.fromDate(createdAt), + 'updatedAt': Timestamp.fromDate(updatedAt), + }; + } + + ProjectModel copyWith({ + String? id, + String? name, + String? description, + String? teamId, + String? ownerId, + List? memberIds, + String? status, + DateTime? startDate, + DateTime? endDate, + String? color, + String? icon, + Map? settings, + DateTime? createdAt, + DateTime? updatedAt, + }) { + return ProjectModel( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + teamId: teamId ?? this.teamId, + ownerId: ownerId ?? this.ownerId, + memberIds: memberIds ?? this.memberIds, + status: status ?? this.status, + startDate: startDate ?? this.startDate, + endDate: endDate ?? this.endDate, + color: color ?? this.color, + icon: icon ?? this.icon, + settings: settings ?? this.settings, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + bool isMember(String userId) { + return ownerId == userId || memberIds.contains(userId); + } + + bool isOwner(String userId) { + return ownerId == userId; + } +} diff --git a/lib/models/task_model.dart b/lib/models/task_model.dart new file mode 100644 index 0000000..c762933 --- /dev/null +++ b/lib/models/task_model.dart @@ -0,0 +1,165 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; + +enum TaskStatus { + todo, + inProgress, + review, + done, + archived, +} + +enum TaskPriority { + low, + medium, + high, + urgent, +} + +class TaskModel { + final String id; + final String title; + final String? description; + final String projectId; + final String? parentTaskId; // For mind-map hierarchy + final TaskStatus status; + final TaskPriority priority; + final String? assigneeId; + final String? creatorId; + final List tags; + final DateTime? dueDate; + final DateTime? startDate; + final DateTime? completedDate; + final int estimatedHours; + final int spentHours; + final Map aiMetadata; // AI-generated suggestions + final DateTime createdAt; + final DateTime updatedAt; + + TaskModel({ + required this.id, + required this.title, + this.description, + required this.projectId, + this.parentTaskId, + this.status = TaskStatus.todo, + this.priority = TaskPriority.medium, + this.assigneeId, + this.creatorId, + this.tags = const [], + this.dueDate, + this.startDate, + this.completedDate, + this.estimatedHours = 0, + this.spentHours = 0, + this.aiMetadata = const {}, + required this.createdAt, + required this.updatedAt, + }); + + factory TaskModel.fromFirestore(DocumentSnapshot doc) { + final data = doc.data() as Map; + return TaskModel( + id: doc.id, + title: data['title'] ?? '', + description: data['description'], + projectId: data['projectId'] ?? '', + parentTaskId: data['parentTaskId'], + status: TaskStatus.values.firstWhere( + (e) => e.name == data['status'], + orElse: () => TaskStatus.todo, + ), + priority: TaskPriority.values.firstWhere( + (e) => e.name == data['priority'], + orElse: () => TaskPriority.medium, + ), + assigneeId: data['assigneeId'], + creatorId: data['creatorId'], + tags: List.from(data['tags'] ?? []), + dueDate: data['dueDate'] != null + ? (data['dueDate'] as Timestamp).toDate() + : null, + startDate: data['startDate'] != null + ? (data['startDate'] as Timestamp).toDate() + : null, + completedDate: data['completedDate'] != null + ? (data['completedDate'] as Timestamp).toDate() + : null, + estimatedHours: data['estimatedHours'] ?? 0, + spentHours: data['spentHours'] ?? 0, + aiMetadata: data['aiMetadata'] ?? {}, + createdAt: (data['createdAt'] as Timestamp).toDate(), + updatedAt: (data['updatedAt'] as Timestamp).toDate(), + ); + } + + Map toFirestore() { + return { + 'title': title, + 'description': description, + 'projectId': projectId, + 'parentTaskId': parentTaskId, + 'status': status.name, + 'priority': priority.name, + 'assigneeId': assigneeId, + 'creatorId': creatorId, + 'tags': tags, + 'dueDate': dueDate != null ? Timestamp.fromDate(dueDate!) : null, + 'startDate': startDate != null ? Timestamp.fromDate(startDate!) : null, + 'completedDate': completedDate != null + ? Timestamp.fromDate(completedDate!) + : null, + 'estimatedHours': estimatedHours, + 'spentHours': spentHours, + 'aiMetadata': aiMetadata, + 'createdAt': Timestamp.fromDate(createdAt), + 'updatedAt': Timestamp.fromDate(updatedAt), + }; + } + + TaskModel copyWith({ + String? id, + String? title, + String? description, + String? projectId, + String? parentTaskId, + TaskStatus? status, + TaskPriority? priority, + String? assigneeId, + String? creatorId, + List? tags, + DateTime? dueDate, + DateTime? startDate, + DateTime? completedDate, + int? estimatedHours, + int? spentHours, + Map? aiMetadata, + DateTime? createdAt, + DateTime? updatedAt, + }) { + return TaskModel( + id: id ?? this.id, + title: title ?? this.title, + description: description ?? this.description, + projectId: projectId ?? this.projectId, + parentTaskId: parentTaskId ?? this.parentTaskId, + status: status ?? this.status, + priority: priority ?? this.priority, + assigneeId: assigneeId ?? this.assigneeId, + creatorId: creatorId ?? this.creatorId, + tags: tags ?? this.tags, + dueDate: dueDate ?? this.dueDate, + startDate: startDate ?? this.startDate, + completedDate: completedDate ?? this.completedDate, + estimatedHours: estimatedHours ?? this.estimatedHours, + spentHours: spentHours ?? this.spentHours, + aiMetadata: aiMetadata ?? this.aiMetadata, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + bool get isOverdue { + if (dueDate == null) return false; + return DateTime.now().isAfter(dueDate!) && status != TaskStatus.done; + } +} diff --git a/lib/models/user_model.dart b/lib/models/user_model.dart new file mode 100644 index 0000000..f32ee4d --- /dev/null +++ b/lib/models/user_model.dart @@ -0,0 +1,90 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; + +enum UserRole { + admin, + member, + viewer, +} + +class UserModel { + final String id; + final String email; + final String? displayName; + final String? photoUrl; + final UserRole role; + final List teamIds; + final DateTime createdAt; + final DateTime updatedAt; + final Map settings; + + UserModel({ + required this.id, + required this.email, + this.displayName, + this.photoUrl, + this.role = UserRole.member, + this.teamIds = const [], + required this.createdAt, + required this.updatedAt, + this.settings = const {}, + }); + + factory UserModel.fromFirestore(DocumentSnapshot doc) { + final data = doc.data() as Map; + return UserModel( + id: doc.id, + email: data['email'] ?? '', + displayName: data['displayName'], + photoUrl: data['photoUrl'], + role: UserRole.values.firstWhere( + (e) => e.name == data['role'], + orElse: () => UserRole.member, + ), + teamIds: List.from(data['teamIds'] ?? []), + createdAt: (data['createdAt'] as Timestamp).toDate(), + updatedAt: (data['updatedAt'] as Timestamp).toDate(), + settings: data['settings'] ?? {}, + ); + } + + Map toFirestore() { + return { + 'email': email, + 'displayName': displayName, + 'photoUrl': photoUrl, + 'role': role.name, + 'teamIds': teamIds, + 'createdAt': Timestamp.fromDate(createdAt), + 'updatedAt': Timestamp.fromDate(updatedAt), + 'settings': settings, + }; + } + + UserModel copyWith({ + String? id, + String? email, + String? displayName, + String? photoUrl, + UserRole? role, + List? teamIds, + DateTime? createdAt, + DateTime? updatedAt, + Map? settings, + }) { + return UserModel( + id: id ?? this.id, + email: email ?? this.email, + displayName: displayName ?? this.displayName, + photoUrl: photoUrl ?? this.photoUrl, + role: role ?? this.role, + teamIds: teamIds ?? this.teamIds, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + settings: settings ?? this.settings, + ); + } + + bool get isAdmin => role == UserRole.admin; + bool get isMember => role == UserRole.member; + bool get isViewer => role == UserRole.viewer; +} diff --git a/lib/providers/auth_provider.dart b/lib/providers/auth_provider.dart new file mode 100644 index 0000000..2eef7b7 --- /dev/null +++ b/lib/providers/auth_provider.dart @@ -0,0 +1,84 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:google_sign_in/google_sign_in.dart'; +import '../models/user_model.dart'; + +class AuthNotifier extends StateNotifier> { + final FirebaseAuth _auth; + final GoogleSignIn _googleSignIn; + + AuthNotifier(this._auth, this._googleSignIn) : super(const AsyncValue.data(null)) { + // Слушаем изменения состояния аутентификации + _auth.authStateChanges().listen((user) { + if (user != null) { + state = AsyncValue.data(UserModel( + id: user.uid, + email: user.email ?? '', + displayName: user.displayName, + photoUrl: user.photoURL, + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + )); + } else { + state = const AsyncValue.data(null); + } + }); + } + + Future signInWithGoogle() async { + try { + state = const AsyncValue.loading(); + + final googleUser = await _googleSignIn.signIn(); + if (googleUser == null) { + state = const AsyncValue.data(null); + return; + } + + final googleAuth = await googleUser.authentication; + final credential = GoogleAuthProvider.credential( + accessToken: googleAuth.accessToken, + idToken: googleAuth.idToken, + ); + + final userCredential = await _auth.signInWithCredential(credential); + + // Здесь можно создать/обновить пользователя в Firestore + state = AsyncValue.data(UserModel( + id: userCredential.user!.uid, + email: userCredential.user!.email ?? '', + displayName: userCredential.user!.displayName, + photoUrl: userCredential.user!.photoURL, + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + )); + } catch (e, st) { + state = AsyncValue.error(e, st); + } + } + + Future signOut() async { + try { + await _googleSignIn.signOut(); + await _auth.signOut(); + state = const AsyncValue.data(null); + } catch (e, st) { + state = AsyncValue.error(e, st); + } + } + + User? get currentUser => _auth.currentUser; + bool get isLoggedIn => _auth.currentUser != null; +} + +final authProvider = StateNotifierProvider>((ref) { + return AuthNotifier(FirebaseAuth.instance, GoogleSignIn()); +}); + +final authStateProvider = Provider((ref) { + return ref.read(authProvider.notifier); +}); + +final isAuthenticatedProvider = Provider((ref) { + return ref.watch(authProvider).value != null; +}); diff --git a/lib/screens/alarms_screen.dart b/lib/screens/alarms_screen.dart new file mode 100644 index 0000000..717709f --- /dev/null +++ b/lib/screens/alarms_screen.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; + +class AlarmsScreen extends StatelessWidget { + const AlarmsScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Будильники'), + ), + body: ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: 5, + itemBuilder: (context, index) { + return _AlarmCard(index: index); + }, + ), + floatingActionButton: FloatingActionButton.extended( + onPressed: () {}, + icon: const Icon(Icons.add), + label: const Text('Новый будильник'), + ), + ); + } +} + +class _AlarmCard extends StatelessWidget { + final int index; + + const _AlarmCard({required this.index}); + + @override + Widget build(BuildContext context) { + return Card( + margin: const EdgeInsets.only(bottom: 12), + child: ListTile( + leading: CircleAvatar( + backgroundColor: index % 2 == 0 ? Colors.blue : Colors.grey, + child: const Icon(Icons.alarm, color: Colors.white), + ), + title: Text('Будильник #${index + 1}'), + subtitle: Text('${8 + index}:00'), + trailing: Switch( + value: index % 2 == 0, + onChanged: (value) {}, + ), + ), + ); + } +} diff --git a/lib/screens/auth_screen.dart b/lib/screens/auth_screen.dart new file mode 100644 index 0000000..56775b0 --- /dev/null +++ b/lib/screens/auth_screen.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import '../providers/auth_provider.dart'; + +class AuthScreen extends ConsumerWidget { + const AuthScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final authState = ref.watch(authProvider); + + return Scaffold( + body: SafeArea( + child: Center( + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.business_center, + size: 120, + color: Colors.blue, + ), + const SizedBox(height: 32), + const Text( + 'NANAHUI CRM', + style: TextStyle( + fontSize: 32, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + const Text( + 'CRM-ассистент для команд', + style: TextStyle( + fontSize: 16, + color: Colors.grey, + ), + ), + const SizedBox(height: 48), + authState.when( + data: (user) { + if (user != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + context.go('/home'); + }); + } + return const SizedBox.shrink(); + }, + loading: () => const CircularProgressIndicator(), + error: (error, stack) => Text( + 'Error: $error', + style: const TextStyle(color: Colors.red), + ), + ), + const SizedBox(height: 24), + ElevatedButton.icon( + onPressed: () { + ref.read(authProvider.notifier).signInWithGoogle(); + }, + icon: const Icon(Icons.login), + label: const Text('Войти через Google'), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 16, + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/screens/calendar_screen.dart b/lib/screens/calendar_screen.dart new file mode 100644 index 0000000..da960f6 --- /dev/null +++ b/lib/screens/calendar_screen.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; + +class CalendarScreen extends StatelessWidget { + const CalendarScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Календарь'), + actions: [ + IconButton( + icon: const Icon(Icons.sync), + onPressed: () { + // Синхронизация с Google Calendar + }, + ), + ], + ), + body: Column( + children: [ + _buildCalendarHeader(), + Expanded( + child: ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: 5, + itemBuilder: (context, index) { + return _CalendarEventCard(index: index); + }, + ), + ), + ], + ), + floatingActionButton: FloatingActionButton( + onPressed: () {}, + child: const Icon(Icons.add), + ), + ); + } + + Widget _buildCalendarHeader() { + return Container( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: const Icon(Icons.chevron_left), + onPressed: () {}, + ), + const Text( + 'Март 2026', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + IconButton( + icon: const Icon(Icons.chevron_right), + onPressed: () {}, + ), + ], + ), + ); + } +} + +class _CalendarEventCard extends StatelessWidget { + final int index; + + const _CalendarEventCard({required this.index}); + + @override + Widget build(BuildContext context) { + return Card( + margin: const EdgeInsets.only(bottom: 12), + child: ListTile( + leading: Container( + width: 4, + height: 40, + decoration: BoxDecoration( + color: _getEventColor(index), + borderRadius: BorderRadius.circular(2), + ), + ), + title: Text('Событие #${index + 1}'), + subtitle: Text('10:00 - 11:00'), + trailing: const Icon(Icons.more_vert), + ), + ); + } + + Color _getEventColor(int index) { + final colors = [Colors.blue, Colors.green, Colors.orange, Colors.purple]; + return colors[index % colors.length]; + } +} diff --git a/lib/screens/dashboard_screen.dart b/lib/screens/dashboard_screen.dart new file mode 100644 index 0000000..b4d4b74 --- /dev/null +++ b/lib/screens/dashboard_screen.dart @@ -0,0 +1,233 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +class DashboardScreen extends StatelessWidget { + const DashboardScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('NANAHUI CRM'), + actions: [ + IconButton( + icon: const Icon(Icons.settings), + onPressed: () {}, + ), + ], + ), + body: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildWelcomeCard(), + const SizedBox(height: 24), + _buildQuickStats(), + const SizedBox(height: 24), + _buildQuickActions(context), + ], + ), + ), + ); + } + + Widget _buildWelcomeCard() { + return Card( + child: Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Добро пожаловать!', + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + const Text( + 'Управляйте задачами команды и планируйте работу с помощью AI', + style: TextStyle(color: Colors.grey), + ), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: OutlinedButton.icon( + onPressed: () {}, + icon: const Icon(Icons.add), + label: const Text('Новая задача'), + ), + ), + const SizedBox(width: 12), + Expanded( + child: OutlinedButton.icon( + onPressed: () {}, + icon: const Icon(Icons.auto_awesome), + label: const Text('AI План'), + ), + ), + ], + ), + ], + ), + ), + ); + } + + Widget _buildQuickStats() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Быстрая статистика', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 12), + Row( + children: [ + Expanded( + child: _StatCard( + icon: Icons.task, + label: 'Задачи', + value: '12', + color: Colors.blue, + ), + ), + const SizedBox(width: 12), + Expanded( + child: _StatCard( + icon: Icons.pending_actions, + label: 'В работе', + value: '5', + color: Colors.orange, + ), + ), + const SizedBox(width: 12), + Expanded( + child: _StatCard( + icon: Icons.check_circle, + label: 'Готово', + value: '48', + color: Colors.green, + ), + ), + ], + ), + ], + ); + } + + Widget _buildQuickActions(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Быстрые действия', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 12), + Wrap( + spacing: 12, + runSpacing: 12, + children: [ + _ActionChip( + icon: Icons.calendar_today, + label: 'Планирование', + onTap: () => context.go('/home/calendar'), + ), + _ActionChip( + icon: Icons.account_tree, + label: 'Mind-карта', + onTap: () => context.go('/home/mindmap'), + ), + _ActionChip( + icon: Icons.insights, + label: 'Аналитика', + onTap: () {}, + ), + _ActionChip( + icon: Icons.people, + label: 'Команда', + onTap: () {}, + ), + ], + ), + ], + ); + } +} + +class _StatCard extends StatelessWidget { + final IconData icon; + final String label; + final String value; + final Color color; + + const _StatCard({ + required this.icon, + required this.label, + required this.value, + required this.color, + }); + + @override + Widget build(BuildContext context) { + return Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + Icon(icon, color: color, size: 32), + const SizedBox(height: 8), + Text( + value, + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + Text( + label, + style: const TextStyle(color: Colors.grey, fontSize: 12), + ), + ], + ), + ), + ); + } +} + +class _ActionChip extends StatelessWidget { + final IconData icon; + final String label; + final VoidCallback onTap; + + const _ActionChip({ + required this.icon, + required this.label, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(12), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainerHighest, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 20), + const SizedBox(width: 8), + Text(label), + ], + ), + ), + ); + } +} diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart new file mode 100644 index 0000000..4692ee7 --- /dev/null +++ b/lib/screens/home_screen.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'dashboard_screen.dart'; +import 'tasks_screen.dart'; +import 'mind_map_screen.dart'; +import 'calendar_screen.dart'; +import 'alarms_screen.dart'; + +class HomeScreen extends StatefulWidget { + const HomeScreen({super.key}); + + @override + State createState() => _HomeScreenState(); +} + +class _HomeScreenState extends State { + int _selectedIndex = 0; + + final List _screens = [ + const DashboardScreen(), + const TasksScreen(), + const MindMapScreen(), + const CalendarScreen(), + const AlarmsScreen(), + ]; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: _screens[_selectedIndex], + bottomNavigationBar: NavigationBar( + selectedIndex: _selectedIndex, + onDestinationSelected: (index) { + setState(() { + _selectedIndex = index; + }); + }, + destinations: const [ + NavigationDestination( + icon: Icon(Icons.dashboard_outlined), + selectedIcon: Icon(Icons.dashboard), + label: 'Главная', + ), + NavigationDestination( + icon: Icon(Icons.task_outlined), + selectedIcon: Icon(Icons.task), + label: 'Задачи', + ), + NavigationDestination( + icon: Icon(Icons.account_tree_outlined), + selectedIcon: Icon(Icons.account_tree), + label: 'Mind-карта', + ), + NavigationDestination( + icon: Icon(Icons.calendar_today_outlined), + selectedIcon: Icon(Icons.calendar_today), + label: 'Календарь', + ), + NavigationDestination( + icon: Icon(Icons.alarm_outlined), + selectedIcon: Icon(Icons.alarm), + label: 'Будильники', + ), + ], + ), + ); + } +} diff --git a/lib/screens/mind_map_screen.dart b/lib/screens/mind_map_screen.dart new file mode 100644 index 0000000..366ea0f --- /dev/null +++ b/lib/screens/mind_map_screen.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; + +class MindMapScreen extends StatelessWidget { + const MindMapScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Mind-карта'), + actions: [ + IconButton( + icon: const Icon(Icons.edit), + onPressed: () { + // Edit mode - только для админов + }, + ), + IconButton( + icon: const Icon(Icons.download), + onPressed: () {}, + ), + ], + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.account_tree, + size: 100, + color: Colors.grey, + ), + const SizedBox(height: 24), + const Text( + 'Mind-карта проекта', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + const Text( + 'Создайте структуру задач в виде дерева', + style: TextStyle(color: Colors.grey), + ), + const SizedBox(height: 32), + ElevatedButton.icon( + onPressed: () {}, + icon: const Icon(Icons.add), + label: const Text('Создать mind-карту'), + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () { + // AI генерация mind-карты + }, + child: const Icon(Icons.auto_awesome), + ), + ); + } +} diff --git a/lib/screens/tasks_screen.dart b/lib/screens/tasks_screen.dart new file mode 100644 index 0000000..4e100dd --- /dev/null +++ b/lib/screens/tasks_screen.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; + +class TasksScreen extends StatelessWidget { + const TasksScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Задачи'), + actions: [ + IconButton( + icon: const Icon(Icons.filter_list), + onPressed: () {}, + ), + IconButton( + icon: const Icon(Icons.search), + onPressed: () {}, + ), + ], + ), + body: ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: 10, + itemBuilder: (context, index) { + return _TaskCard(index: index); + }, + ), + floatingActionButton: FloatingActionButton.extended( + onPressed: () {}, + icon: const Icon(Icons.add), + label: const Text('Новая задача'), + ), + ); + } +} + +class _TaskCard extends StatelessWidget { + final int index; + + const _TaskCard({required this.index}); + + @override + Widget build(BuildContext context) { + return Card( + margin: const EdgeInsets.only(bottom: 12), + child: ListTile( + leading: CircleAvatar( + backgroundColor: _getPriorityColor(index), + child: Text('${index + 1}'), + ), + title: Text('Задача #${index + 1}'), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 4), + Text('Описание задачи...'), + const SizedBox(height: 4), + Row( + children: [ + Icon(Icons.calendar_today, size: 12, color: Colors.grey), + const SizedBox(width: 4), + Text( + 'До: ${DateTime.now().add(Duration(days: index)).toString().split(' ')[0]}', + style: const TextStyle(fontSize: 12, color: Colors.grey), + ), + ], + ), + ], + ), + trailing: PopupMenuButton( + itemBuilder: (context) => [ + const PopupMenuItem(value: 'edit', child: Text('Редактировать')), + const PopupMenuItem(value: 'delete', child: Text('Удалить')), + ], + ), + isThreeLine: true, + ), + ); + } + + Color _getPriorityColor(int index) { + final colors = [Colors.red, Colors.orange, Colors.blue, Colors.green]; + return colors[index % colors.length]; + } +} diff --git a/linux/.gitignore b/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt new file mode 100644 index 0000000..9c9c07d --- /dev/null +++ b/linux/CMakeLists.txt @@ -0,0 +1,128 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "nanahui_crm") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.nanahui.nanahui_crm") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/linux/flutter/CMakeLists.txt b/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..7299b5c --- /dev/null +++ b/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,19 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); + file_selector_plugin_register_with_registrar(file_selector_linux_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); +} diff --git a/linux/flutter/generated_plugin_registrant.h b/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..786ff5c --- /dev/null +++ b/linux/flutter/generated_plugins.cmake @@ -0,0 +1,25 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + file_selector_linux + url_launcher_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/linux/runner/CMakeLists.txt b/linux/runner/CMakeLists.txt new file mode 100644 index 0000000..e97dabc --- /dev/null +++ b/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/linux/runner/main.cc b/linux/runner/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/linux/runner/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/linux/runner/my_application.cc b/linux/runner/my_application.cc new file mode 100644 index 0000000..96a44c7 --- /dev/null +++ b/linux/runner/my_application.cc @@ -0,0 +1,148 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Called when first Flutter frame received. +static void first_frame_cb(MyApplication* self, FlView* view) { + gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view))); +} + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "nanahui_crm"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "nanahui_crm"); + } + + gtk_window_set_default_size(window, 1280, 720); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments( + project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + GdkRGBA background_color; + // Background defaults to black, override it here if necessary, e.g. #00000000 + // for transparent. + gdk_rgba_parse(&background_color, "#000000"); + fl_view_set_background_color(view, &background_color); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + // Show the window when Flutter renders. + // Requires the view to be realized so we can start rendering. + g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), + self); + gtk_widget_realize(GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, + gchar*** arguments, + int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = + my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, "flags", + G_APPLICATION_NON_UNIQUE, nullptr)); +} diff --git a/linux/runner/my_application.h b/linux/runner/my_application.h new file mode 100644 index 0000000..db16367 --- /dev/null +++ b/linux/runner/my_application.h @@ -0,0 +1,21 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, + my_application, + MY, + APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/macos/.gitignore b/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/Flutter-Release.xcconfig b/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..8afda97 --- /dev/null +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,28 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import cloud_firestore +import file_selector_macos +import firebase_auth +import firebase_core +import firebase_messaging +import flutter_local_notifications +import google_sign_in_ios +import shared_preferences_foundation +import url_launcher_macos + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin")) + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) + FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) + FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) + FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin")) + FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) + FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) +} diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..1e80fa9 --- /dev/null +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,705 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* nanahui_crm.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "nanahui_crm.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* nanahui_crm.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* nanahui_crm.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.nanahui.nanahuiCrm.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/nanahui_crm.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/nanahui_crm"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.nanahui.nanahuiCrm.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/nanahui_crm.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/nanahui_crm"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.nanahui.nanahuiCrm.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/nanahui_crm.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/nanahui_crm"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..23e5877 --- /dev/null +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..b3c1761 --- /dev/null +++ b/macos/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/macos/Runner/Base.lproj/MainMenu.xib b/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..1f84d8d --- /dev/null +++ b/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = nanahui_crm + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.nanahui.nanahuiCrm + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2026 com.nanahui. All rights reserved. diff --git a/macos/Runner/Configs/Debug.xcconfig b/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Release.xcconfig b/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Warnings.xcconfig b/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/macos/RunnerTests/RunnerTests.swift b/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..61f3bd1 --- /dev/null +++ b/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +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. + } + +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..8212753 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,1362 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _discoveryapis_commons: + dependency: transitive + description: + name: _discoveryapis_commons + sha256: "113c4100b90a5b70a983541782431b82168b3cae166ab130649c36eb3559d498" + url: "https://pub.dev" + source: hosted + version: "1.0.7" + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + url: "https://pub.dev" + source: hosted + version: "67.0.0" + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: "37a42d06068e2fe3deddb2da079a8c4d105f241225ba27b7122b37e9865fd8f7" + url: "https://pub.dev" + source: hosted + version: "1.3.35" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + url: "https://pub.dev" + source: hosted + version: "6.4.1" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + build: + dependency: transitive + description: + name: build + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 + url: "https://pub.dev" + source: hosted + version: "4.1.1" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" + url: "https://pub.dev" + source: hosted + version: "2.4.13" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 + url: "https://pub.dev" + source: hosted + version: "7.3.2" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: "0730c18c770d05636a8f945c32a4d7d81cb6e0f0148c8db4ad12e7748f7e49af" + url: "https://pub.dev" + source: hosted + version: "8.12.5" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + cloud_firestore: + dependency: "direct main" + description: + name: cloud_firestore + sha256: a0f161b92610e078b4962d7e6ebeb66dc9cce0ada3514aeee442f68165d78185 + url: "https://pub.dev" + source: hosted + version: "4.17.5" + cloud_firestore_platform_interface: + dependency: transitive + description: + name: cloud_firestore_platform_interface + sha256: "6a55b319f8d33c307396b9104512e8130a61904528ab7bd8b5402678fca54b81" + url: "https://pub.dev" + source: hosted + version: "6.2.5" + cloud_firestore_web: + dependency: transitive + description: + name: cloud_firestore_web + sha256: "89dfa1304d3da48b3039abbb2865e3d30896ef858e569a16804a99f4362283a9" + url: "https://pub.dev" + source: hosted + version: "3.12.5" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" + url: "https://pub.dev" + source: hosted + version: "4.11.1" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" + url: "https://pub.dev" + source: hosted + version: "0.3.5+2" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" + url: "https://pub.dev" + source: hosted + version: "1.0.9" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" + url: "https://pub.dev" + source: hosted + version: "2.3.6" + dbus: + dependency: transitive + description: + name: dbus + sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270 + url: "https://pub.dev" + source: hosted + version: "0.7.12" + dio: + dependency: "direct main" + description: + name: dio + sha256: aff32c08f92787a557dd5c0145ac91536481831a01b4648136373cddb0e64f8c + url: "https://pub.dev" + source: hosted + version: "5.9.2" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "2f9e64323a7c3c7ef69567d5c800424a11f8337b8b228bad02524c9fb3c1f340" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0" + url: "https://pub.dev" + source: hosted + version: "0.9.4" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" + url: "https://pub.dev" + source: hosted + version: "0.9.5" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" + url: "https://pub.dev" + source: hosted + version: "2.7.0" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd" + url: "https://pub.dev" + source: hosted + version: "0.9.3+5" + firebase_auth: + dependency: "direct main" + description: + name: firebase_auth + sha256: cfc2d970829202eca09e2896f0a5aa7c87302817ecc0bdfa954f026046bf10ba + url: "https://pub.dev" + source: hosted + version: "4.20.0" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + sha256: a0270e1db3b2098a14cb2a2342b3cd2e7e458e0c391b1f64f6f78b14296ec093 + url: "https://pub.dev" + source: hosted + version: "7.3.0" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + sha256: "64e067e763c6378b7e774e872f0f59f6812885e43020e25cde08f42e9459837b" + url: "https://pub.dev" + source: hosted + version: "5.12.0" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: "26de145bb9688a90962faec6f838247377b0b0d32cc0abecd9a4e43525fc856c" + url: "https://pub.dev" + source: hosted + version: "2.32.0" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: "8bcfad6d7033f5ea951d15b867622a824b13812178bfec0c779b9d81de011bbb" + url: "https://pub.dev" + source: hosted + version: "5.4.2" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: "362e52457ed2b7b180964769c1e04d1e0ea0259fdf7025fdfedd019d4ae2bd88" + url: "https://pub.dev" + source: hosted + version: "2.17.5" + firebase_messaging: + dependency: "direct main" + description: + name: firebase_messaging + sha256: a1662cc95d9750a324ad9df349b873360af6f11414902021f130c68ec02267c4 + url: "https://pub.dev" + source: hosted + version: "14.9.4" + firebase_messaging_platform_interface: + dependency: transitive + description: + name: firebase_messaging_platform_interface + sha256: "87c4a922cb6f811cfb7a889bdbb3622702443c52a0271636cbc90d813ceac147" + url: "https://pub.dev" + source: hosted + version: "4.5.37" + firebase_messaging_web: + dependency: transitive + description: + name: firebase_messaging_web + sha256: "0d34dca01a7b103ed7f20138bffbb28eb0e61a677bf9e78a028a932e2c7322d5" + url: "https://pub.dev" + source: hosted + version: "3.8.7" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + sha256: "674173fd3c9eda9d4c8528da2ce0ea69f161577495a9cc835a2a4ecd7eadeb35" + url: "https://pub.dev" + source: hosted + version: "17.2.4" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + sha256: c49bd06165cad9beeb79090b18cd1eb0296f4bf4b23b84426e37dd7c027fc3af + url: "https://pub.dev" + source: hosted + version: "4.0.1" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + sha256: "85f8d07fe708c1bdcf45037f2c0109753b26ae077e9d9e899d55971711a4ea66" + url: "https://pub.dev" + source: hosted + version: "7.2.0" + flutter_mind_map: + dependency: "direct main" + description: + name: flutter_mind_map + sha256: d6c242c0c76682abcf35969882c575ca85b42aaa32b901e266bcbc0d9ad11911 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "38d1c268de9097ff59cf0e844ac38759fc78f76836d37edad06fa21e182055a0" + url: "https://pub.dev" + source: hosted + version: "2.0.34" + flutter_riverpod: + dependency: "direct main" + description: + name: flutter_riverpod + sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1" + url: "https://pub.dev" + source: hosted + version: "2.6.1" + flutter_staggered_grid_view: + dependency: "direct main" + description: + name: flutter_staggered_grid_view + sha256: "19e7abb550c96fbfeb546b23f3ff356ee7c59a019a651f8f102a4ba9b7349395" + url: "https://pub.dev" + source: hosted + version: "0.7.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + go_router: + dependency: "direct main" + description: + name: go_router + sha256: b465e99ce64ba75e61c8c0ce3d87b66d8ac07f0b35d0a7e0263fcfc10f99e836 + url: "https://pub.dev" + source: hosted + version: "13.2.5" + google_identity_services_web: + dependency: transitive + description: + name: google_identity_services_web + sha256: "5d187c46dc59e02646e10fe82665fc3884a9b71bc1c90c2b8b749316d33ee454" + url: "https://pub.dev" + source: hosted + version: "0.3.3+1" + google_sign_in: + dependency: "direct main" + description: + name: google_sign_in + sha256: d0a2c3bcb06e607bb11e4daca48bd4b6120f0bbc4015ccebbe757d24ea60ed2a + url: "https://pub.dev" + source: hosted + version: "6.3.0" + google_sign_in_android: + dependency: transitive + description: + name: google_sign_in_android + sha256: d5e23c56a4b84b6427552f1cf3f98f716db3b1d1a647f16b96dbb5b93afa2805 + url: "https://pub.dev" + source: hosted + version: "6.2.1" + google_sign_in_ios: + dependency: transitive + description: + name: google_sign_in_ios + sha256: "102005f498ce18442e7158f6791033bbc15ad2dcc0afa4cf4752e2722a516c96" + url: "https://pub.dev" + source: hosted + version: "5.9.0" + google_sign_in_platform_interface: + dependency: transitive + description: + name: google_sign_in_platform_interface + sha256: "5f6f79cf139c197261adb6ac024577518ae48fdff8e53205c5373b5f6430a8aa" + url: "https://pub.dev" + source: hosted + version: "2.5.0" + google_sign_in_web: + dependency: transitive + description: + name: google_sign_in_web + sha256: "460547beb4962b7623ac0fb8122d6b8268c951cf0b646dd150d60498430e4ded" + url: "https://pub.dev" + source: hosted + version: "0.12.4+4" + googleapis: + dependency: "direct main" + description: + name: googleapis + sha256: "864f222aed3f2ff00b816c675edf00a39e2aaf373d728d8abec30b37bee1a81c" + url: "https://pub.dev" + source: hosted + version: "13.2.0" + googleapis_auth: + dependency: "direct main" + description: + name: googleapis_auth + sha256: befd71383a955535060acde8792e7efc11d2fccd03dd1d3ec434e85b68775938 + url: "https://pub.dev" + source: hosted + version: "1.6.0" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + graphview: + dependency: "direct main" + description: + name: graphview + sha256: a683865b36172cc8c470f45f6d34bb901ba5b4e80fc345f6a0021abc57fa8f4c + url: "https://pub.dev" + source: hosted + version: "1.5.1" + hive: + dependency: "direct main" + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + hive_flutter: + dependency: "direct main" + description: + name: hive_flutter + sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc + url: "https://pub.dev" + source: hosted + version: "1.1.0" + hive_generator: + dependency: "direct dev" + description: + name: hive_generator + sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + hooks: + dependency: transitive + description: + name: hooks + sha256: e79ed1e8e1929bc6ecb6ec85f0cb519c887aa5b423705ded0d0f2d9226def388 + url: "https://pub.dev" + source: hosted + version: "1.0.2" + http: + dependency: "direct main" + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "9eae0cbd672549dacc18df855c2a23782afe4854ada5190b7d63b30ee0b0d3fd" + url: "https://pub.dev" + source: hosted + version: "0.8.13+15" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588 + url: "https://pub.dev" + source: hosted + version: "0.8.13+6" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" + url: "https://pub.dev" + source: hosted + version: "0.2.2" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91" + url: "https://pub.dev" + source: hosted + version: "0.2.2+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c" + url: "https://pub.dev" + source: hosted + version: "2.11.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae + url: "https://pub.dev" + source: hosted + version: "0.2.2" + intl: + dependency: "direct main" + description: + name: intl + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + url: "https://pub.dev" + source: hosted + version: "0.19.0" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 + url: "https://pub.dev" + source: hosted + version: "4.11.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + url: "https://pub.dev" + source: hosted + version: "5.1.1" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" + url: "https://pub.dev" + source: hosted + version: "0.12.18" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + native_toolchain_c: + dependency: transitive + description: + name: native_toolchain_c + sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572" + url: "https://pub.dev" + source: hosted + version: "0.17.6" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" + url: "https://pub.dev" + source: hosted + version: "9.3.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_drawing: + dependency: transitive + description: + name: path_drawing + sha256: bbb1934c0cbb03091af082a6389ca2080345291ef07a5fa6d6e078ba8682f977 + url: "https://pub.dev" + source: hosted + version: "1.0.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "149441ca6e4f38193b2e004c0ca6376a3d11f51fa5a77552d8bd4d2b0c0912ba" + url: "https://pub.dev" + source: hosted + version: "2.2.23" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849" + url: "https://pub.dev" + source: hosted + version: "11.4.0" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc + url: "https://pub.dev" + source: hosted + version: "12.1.0" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 + url: "https://pub.dev" + source: hosted + version: "9.4.7" + permission_handler_html: + dependency: transitive + description: + name: permission_handler_html + sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" + url: "https://pub.dev" + source: hosted + version: "0.1.3+5" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 + url: "https://pub.dev" + source: hosted + version: "4.3.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + url: "https://pub.dev" + source: hosted + version: "0.2.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" + url: "https://pub.dev" + source: hosted + version: "7.0.2" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + riverpod: + dependency: transitive + description: + name: riverpod + sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959" + url: "https://pub.dev" + source: hosted + version: "2.6.1" + riverpod_annotation: + dependency: "direct main" + description: + name: riverpod_annotation + sha256: e14b0bf45b71326654e2705d462f21b958f987087be850afd60578fcd502d1b8 + url: "https://pub.dev" + source: hosted + version: "2.6.1" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf + url: "https://pub.dev" + source: hosted + version: "2.5.5" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53 + url: "https://pub.dev" + source: hosted + version: "2.4.23" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" + url: "https://pub.dev" + source: hosted + version: "1.3.5" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + state_notifier: + dependency: transitive + description: + name: state_notifier + sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb + url: "https://pub.dev" + source: hosted + version: "1.0.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + syncfusion_flutter_core: + dependency: transitive + description: + name: syncfusion_flutter_core + sha256: e17dcc7a1d0701e84d0a83c0040503cdcc6c72e44db0d733ab4c706dd5b8b9f8 + url: "https://pub.dev" + source: hosted + version: "25.2.7" + syncfusion_flutter_gauges: + dependency: "direct main" + description: + name: syncfusion_flutter_gauges + sha256: b9fdecf3dd9de516cbb3a3daad35663bea30a145229bf0cbf64b61b8411358aa + url: "https://pub.dev" + source: hosted + version: "25.2.7" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" + url: "https://pub.dev" + source: hosted + version: "0.7.9" + timezone: + dependency: "direct main" + description: + name: timezone + sha256: "2236ec079a174ce07434e89fcd3fcda430025eb7692244139a9cf54fdcf1fc7d" + url: "https://pub.dev" + source: hosted + version: "0.9.4" + timing: + dependency: transitive + description: + name: timing + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "3bb000251e55d4a209aa0e2e563309dc9bb2befea2295fd0cec1f51760aac572" + url: "https://pub.dev" + source: hosted + version: "6.3.29" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" + url: "https://pub.dev" + source: hosted + version: "6.4.1" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + url: "https://pub.dev" + source: hosted + version: "3.2.5" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f + url: "https://pub.dev" + source: hosted + version: "2.4.2" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" + source: hosted + version: "3.1.5" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" + url: "https://pub.dev" + source: hosted + version: "4.5.3" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + url: "https://pub.dev" + source: hosted + version: "15.0.2" + watcher: + dependency: transitive + description: + name: watcher + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + web: + dependency: transitive + description: + name: web + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + workmanager: + dependency: "direct main" + description: + name: workmanager + sha256: ed13530cccd28c5c9959ad42d657cd0666274ca74c56dea0ca183ddd527d3a00 + url: "https://pub.dev" + source: hosted + version: "0.5.2" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" +sdks: + dart: ">=3.10.3 <4.0.0" + flutter: ">=3.38.4" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..0a022ef --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,74 @@ +name: nanahui_crm +description: CRM-assistant for freelancers and teams with AI planning and mind maps +publish_to: 'none' +version: 0.1.0 + +environment: + sdk: ^3.6.0 + +dependencies: + flutter: + sdk: flutter + + # State Management + flutter_riverpod: ^2.5.0 + riverpod_annotation: ^2.3.5 + + # Firebase + firebase_core: ^2.32.0 + firebase_auth: ^4.19.0 + cloud_firestore: ^4.17.0 + firebase_messaging: ^14.9.0 + + # Google Sign-In + google_sign_in: ^6.2.1 + + # Local Storage (Offline cache) + hive_flutter: ^1.1.0 + hive: ^2.2.3 + + # Google Calendar + googleapis: ^13.2.0 + googleapis_auth: ^1.6.0 + + # AI Services + http: ^1.2.0 + dio: ^5.4.0 + + # UI Components + cupertino_icons: ^1.0.8 + flutter_staggered_grid_view: ^0.7.0 + syncfusion_flutter_gauges: ^25.1.38 + graphview: ^1.2.0 + flutter_mind_map: ^1.0.23 + + # Utilities + uuid: ^4.3.3 + intl: ^0.19.0 + path_provider: ^2.1.2 + shared_preferences: ^2.2.2 + url_launcher: ^6.2.4 + image_picker: ^1.0.7 + permission_handler: ^11.3.0 + + # Notifications & Alarms + flutter_local_notifications: ^17.0.0 + timezone: ^0.9.2 + workmanager: ^0.5.2 + + # Navigation + go_router: ^13.1.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + hive_generator: ^2.0.1 + build_runner: ^2.4.8 + +flutter: + uses-material-design: true + + assets: + - assets/images/ + - assets/icons/ diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000..ede2a52 --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,8 @@ +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Placeholder test', (WidgetTester tester) async { + // TODO: Add real tests + expect(true, isTrue); + }); +} diff --git a/web/favicon.png b/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/web/favicon.png differ diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/web/icons/Icon-192.png differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/web/icons/Icon-512.png differ diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/web/icons/Icon-maskable-192.png differ diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/web/icons/Icon-maskable-512.png differ diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..42bfe02 --- /dev/null +++ b/web/index.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + nanahui_crm + + + + + + + diff --git a/web/manifest.json b/web/manifest.json new file mode 100644 index 0000000..4c53a4a --- /dev/null +++ b/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "nanahui_crm", + "short_name": "nanahui_crm", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/windows/.gitignore b/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt new file mode 100644 index 0000000..b177bdd --- /dev/null +++ b/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(nanahui_crm LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "nanahui_crm") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..df2a17e --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,29 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include +#include +#include +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + CloudFirestorePluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("CloudFirestorePluginCApi")); + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); + FirebaseAuthPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi")); + FirebaseCorePluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/windows/flutter/generated_plugin_registrant.h b/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..a3d8d3f --- /dev/null +++ b/windows/flutter/generated_plugins.cmake @@ -0,0 +1,29 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + cloud_firestore + file_selector_windows + firebase_auth + firebase_core + permission_handler_windows + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc new file mode 100644 index 0000000..e401de8 --- /dev/null +++ b/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.nanahui" "\0" + VALUE "FileDescription", "nanahui_crm" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "nanahui_crm" "\0" + VALUE "LegalCopyright", "Copyright (C) 2026 com.nanahui. All rights reserved." "\0" + VALUE "OriginalFilename", "nanahui_crm.exe" "\0" + VALUE "ProductName", "nanahui_crm" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/windows/runner/flutter_window.h b/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp new file mode 100644 index 0000000..8cbb1d5 --- /dev/null +++ b/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"nanahui_crm", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/windows/runner/resource.h b/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/windows/runner/resources/app_icon.ico differ diff --git a/windows/runner/runner.exe.manifest b/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..153653e --- /dev/null +++ b/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/windows/runner/utils.cpp b/windows/runner/utils.cpp new file mode 100644 index 0000000..3a0b465 --- /dev/null +++ b/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/windows/runner/utils.h b/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/windows/runner/win32_window.h b/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_