Files
energymedia_content_manager/.github/copilot-instructions.md
2026-01-10 21:12:17 -08:00

9.2 KiB

EnergyMedia Content Manager - AI Coding Agent Instructions

Project Overview

EnergyMedia Content Manager is a Flutter web application for managing multimedia content (videos, posters, categories) for the EnergyMedia platform. Single-organization focused system with video upload, categorization, playback, and analytics dashboard.

Tech Stack: Flutter 3.1.4+, Supabase (backend/auth/storage), Provider (state), GoRouter (navigation), PlutoGrid (tables), Video Players (appinio_video_player/video_player)

Architecture & Key Patterns

Dual Supabase Clients

  • supabase (default): Standard auth schema (public.users) for authentication ONLY
  • supabaseML: Custom media_library schema for all media content management
  • Critical: Always use supabaseML for media data, supabase for auth only
  • Organization Filter: ALL queries to media_files MUST filter by organization_fk = 17
  • See lib/main.dart and lib/helpers/globals.dart

State Management (Provider)

All providers declared in lib/main.dart:

  • UserState: Auth state and current user
  • VisualStateProvider: Theme/visual preferences (light/dark mode)
  • VideosProvider: Media files CRUD, upload/download, metadata management
  • Pattern: Use context.read<T>() for one-time actions, context.watch<T>() for reactive UI

Navigation Structure

/login → /dashboard (stats: reproducciones, videos, categorías)
         └── Sidemenu:
             ├── Dashboard (default)
             ├── Gestor de Videos (PlutoGrid con CRUD)
             └── Configuración (placeholder - work in progress)

Database Schema & Critical Rules

Media Library Schema (media_library)

Tables:

  • media_files: Main video records (file_name, title, file_url, storage_path, metadata_json, media_category_fk, organization_fk)
  • media_categories: Video categories (category_name, category_description)
  • media_posters: Poster/thumbnail associations (media_file_id, poster_file_id)
  • View: vw_media_files_with_posters - Complete media info with category and poster

Organization Filter Rule

CRITICAL: ALL operations on media_files MUST include organization_fk = 17 filter:

// CORRECT - filtered by organization
final response = await supabaseML
  .from('media_files')
  .select()
  .eq('organization_fk', 17);

// WRONG - missing organization filter
final response = await supabaseML
  .from('media_files') // ❌ Returns all organizations!
  .select();

Always: Insert/update operations must set organization_fk: 17

metadata_json Structure

Standard fields in media_files.metadata_json:

{
  "uploaded_at": "2026-01-10T10:30:00Z",
  "reproducciones": 150,
  "categorias": ["tutorial", "energía"],
  "original_file_name": "video_original.mp4",
  "duration_seconds": 320,
  "resolution": "1920x1080",
  "last_viewed_at": "2026-01-10T12:00:00Z"
}

Supabase Storage

Bucket: energymedia

  • energymedia/videos/ - Video files
  • energymedia/imagenes/ - Poster/thumbnail images

Responsive Design Standards

Breakpoints: Mobile ≤800px, Tablet 801-1200px, Desktop >1200px

Common pattern:

final isMobile = MediaQuery.of(context).size.width <= 800;
// Desktop: PlutoGrid tables with video thumbnails
// Mobile: Card-based lists with posters

Key files:

  • Desktop layouts: lib/pages/videos/*_page.dart
  • Mobile adaptations: Check for conditional rendering in same files
  • Reference constant: mobileSize = 800 in lib/helpers/constants.dart

Critical Files & Workflows

Global State (lib/helpers/globals.dart)

  • currentUser: Authenticated user model (nullable)
  • supabaseML: Media Library-specific Supabase client (schema: media_library)
  • plutoGridScrollbarConfig(), plutoGridStyleConfig(): Consistent table styling
  • Always check currentUser != null before auth-dependent operations

Models (Domain-Driven)

  • Media models: lib/models/media/*.dart
  • Pattern: fromMap() for Supabase JSON deserialization
  • Key models: MediaFileModel, MediaCategoryModel, MediaPosterModel

Video Players

Libraries: appinio_video_player, video_player, chewie Usage patterns:

  • VideoPlayerLive: Full-screen player with controls (chewie-based)
  • VideoScreenNew: Embedded player (appinio-based)
  • VideoScreenThumbnail: Generate thumbnails from video
  • See lib/pages/widgets/video_player_*.dart

Theme & Styling

Color Scheme (EnergyMedia):

  • Primary Gradient: Cyan→Yellow (linear-gradient(135deg, #4EC9F5, #FFB733))
  • Accents: Purple (#6B2F8A), Cyan (#4EC9F5), Yellow (#FFB733), Red (#FF2D2D), Orange (#FF7A3D)
  • Dark Mode Backgrounds: #0B0B0D (main), #121214 (surface1), #1A1A1D (surface2)
  • Light Mode Backgrounds: #FFFFFF (main), #F7F7F7 (surface1), #EFEFEF (surface2)
  • Typography: Google Fonts Poppins (Regular 400, Bold 700)
  • Use AppTheme.of(context) for colors, not hardcoded values
  • See lib/theme/theme.dart

Development Commands

# Run (web dev)
flutter run -d chrome

# Build web (production)
flutter build web

# Dependencies
flutter pub get

# Clean build
flutter clean && flutter pub get

Common Tasks

Adding New Provider

  1. Create in lib/providers/
  2. Extend ChangeNotifier
  3. Register in lib/main.dart MultiProvider.providers
  4. Export in lib/providers/providers.dart if needed

Creating Responsive Pages

  1. Check screen width: MediaQuery.of(context).size.width
  2. Desktop: Use PlutoGrid for tables, full layouts
  3. Mobile: Use ListView with Cards, simplified forms
  4. Reference lib/pages/videos/gestor_videos_page.dart for pattern

Querying Media Data

// CORRECT - uses media_library schema + organization filter
final response = await supabaseML
  .from('media_files')
  .select()
  .eq('organization_fk', 17)
  .order('created_at_timestamp', ascending: false);

// WRONG - uses default schema
final response = await supabase
  .from('media_files') // ❌ Table not found!
  .select();

// WRONG - missing organization filter
final response = await supabaseML
  .from('media_files')
  .select(); // ❌ Returns data from ALL organizations!

Uploading Media Files

// 1. Upload video to storage
final videoPath = await supabaseML.storage
  .from('energymedia')
  .upload('videos/$fileName', videoBytes);

// 2. Insert record with organization filter
await supabaseML.from('media_files').insert({
  'file_name': fileName,
  'title': title,
  'file_url': publicUrl,
  'storage_path': 'videos/$fileName',
  'organization_fk': 17, // ⚠️ REQUIRED!
  'metadata_json': {
    'uploaded_at': DateTime.now().toIso8601String(),
    'reproducciones': 0,
    'original_file_name': originalName,
  }
});

Working with Video Thumbnails

// Generate thumbnail from video (VideoScreenThumbnail widget)
VideoScreenThumbnail(video: videoUrl)

// Display poster from media_posters
Image.network(posterUrl, fit: BoxFit.cover)

// Fallback: Show placeholder if no poster
if (posterUrl != null) 
  Image.network(posterUrl)
else 
  Icon(Icons.video_library, size: 48)

Project Conventions

Naming

  • Files: snake_case.dart (e.g., videos_provider.dart)
  • Classes: PascalCase (e.g., VideosProvider)
  • Private members: _underscorePrefixed (e.g., _selectedVideo)
  • Models suffix: *Model (e.g., MediaFileModel)

File Organization

lib/
  pages/          # Full pages
    videos/       # Video management pages
      gestor_videos_page.dart
      dashboard_page.dart
      widgets/    # Video-specific widgets
    widgets/      # Shared widgets (video players, etc.)
  providers/      # State management
    videos_provider.dart
  models/         # Data models
    media/        # Media domain models
  helpers/        # Utilities, globals, extensions
  services/       # External service integrations

Import Style

  • Absolute imports: import 'package:nethive_neo/...'
  • Barrel exports: Use lib/models/models.dart, lib/providers/providers.dart

Testing & Debugging

  • No formal tests yet - manual testing in browser/device
  • Dev mode: Hot reload enabled (Flutter devtools)
  • Check browser console for Supabase RPC errors
  • Useful: Flutter Inspector for widget tree debugging

Key Reference Documents

Known Quirks

  • PlutoGrid requires dependency_override for intl: ^0.19.0 compatibility
  • Always call initGlobals() in main before app initialization
  • GoRouter optionURLReflectsImperativeAPIs must be true for proper routing
  • Mobile forms use full-screen modals, not dialogs (better UX on small screens)
  • Video thumbnails: Use VideoScreenThumbnail widget or fallback to category/poster image