sojorn/sojorn_docs/features/PROFILE_WIDGETS.md
Patrick Britton 56a9dd032f feat: Add enhanced video moderation with frame extraction and implement placeholder UI methods
- Add VideoProcessor service to PostHandler for frame-based video moderation
- Implement multi-frame extraction and Azure OpenAI Vision analysis for video content
- Enhance VideoStitchingService with filters, speed control, and text overlays
- Add image upload dialogs for group avatar and banner in GroupCreationModal
- Implement navigation placeholders for mentions, hashtags, and URLs in sojornRichText
2026-02-17 13:32:58 -06:00

28 KiB

Profile Widgets System Documentation

🎨 Modular Profile Customization

Version: 3.0
Status: COMPLETED
Last Updated: February 17, 2026


🎯 Overview

The Profile Widgets system transforms user profiles from static displays into dynamic, personalized spaces. Inspired by MySpace but with modern design constraints, users can add, remove, and arrange various widgets to create unique profile expressions while maintaining platform consistency.

🏗️ Architecture

System Components

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   Widget Engine   │    │   Layout Manager │    │   Theme System   │
│                 │◄──►│                 │◄──►│                 │
│ • Widget Registry│    │ • Grid Layout    │    │ • Color Schemes  │
│ • Component Cache │    │ • Drag & Drop    │    │ • Font Options   │
│ • State Management│    │ • Persistence    │    │ • Style Rules    │
└─────────────────┘    └─────────────────┘    └─────────────────┘
         │                       │                       │
         ▼                       ▼                       ▼
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   Database        │    │   User Interface │    │   Asset Storage  │
│                 │    │                 │    │                 │
│ • Profile Layout │    │ • Widget Renderer│    │ • Widget Assets  │
│ • Widget Config  │    │ • Drag Interface │    │ • Theme Assets  │
│ • User Settings  │    │ • Preview Mode   │    │ • Custom Images │
└─────────────────┘    └─────────────────┘    └─────────────────┘

🎨 Widget Types

Standard Fields (Always Present)

Profile Header

  • Avatar: User profile image with upload capability
  • Display Name: Editable display name
  • Handle: Unique username (@handle)
  • Pronouns: Optional pronoun field
  • Location: Optional city-level location

Widget Catalog

📌 Pinned Posts Widget

Display up to 3 featured posts at the top of profile.

Features:

  • Drag to reorder pinned posts
  • Quick pin/unpin from post menu
  • Automatic thumbnail generation
  • Engagement stats display

Configuration:

class PinnedPostsWidgetConfig {
  final List<String> postIds;
  final bool showEngagementStats;
  final int maxPosts;
}

🎵 Music Widget

Show currently listening or favorite music tracks.

Features:

  • Spotify/Apple Music integration
  • Manual track entry
  • Album artwork display
  • Play preview snippets
  • Music sharing links

Configuration:

class MusicWidgetConfig {
  final String? currentTrack;
  final String? artist;
  final String? album;
  final String? albumArt;
  final String? spotifyUrl;
  final String? appleMusicUrl;
  final bool showCurrentlyListening;
}

📸 Photo Grid Widget

Mini gallery of featured photos.

Features:

  • 3-6 photo grid layout
  • Tap to view full size
  • Photo captions and dates
  • Upload from device or library
  • Photo organization

Configuration:

class PhotoGridWidgetConfig {
  final List<String> photoUrls;
  final int columns;
  final bool showCaptions;
  final bool showDates;
  final PhotoGridStyle style;
}

Icon row for external social links.

Features:

  • 20+ platform icons
  • Custom link labels
  • Verification badges
  • Click tracking analytics
  • Link preview on hover

Configuration:

class SocialLinksWidgetConfig {
  final List<SocialLink> links;
  final SocialLinkStyle style;
  final bool showVerificationBadges;
}

class SocialLink {
  final SocialPlatform platform;
  final String url;
  final String? customLabel;
  final bool isVerified;
}

🏷️ Causes Widget

Tag-style badges for causes and interests.

Features:

  • Pre-defined cause categories
  • Custom cause creation
  • Color-coded categories
  • Click to explore similar users
  • Cause-based recommendations

Configuration:

class CausesWidgetConfig {
  final List<CauseTag> causes;
  final bool showDescription;
  final CauseDisplayStyle style;
}

class CauseTag {
  final String name;
  final CauseCategory category;
  final String? description;
  final Color color;
}

Highlight 3-6 important connections.

Features:

  • Friend selection from followers
  • Mutual friends indicator
  • Online status display
  • Quick message action
  • Relationship type labels

Configuration:

class FeaturedFriendsWidgetConfig {
  final List<String> friendIds;
  final int maxFriends;
  final bool showOnlineStatus;
  final bool showMutualFriends;
  final FriendDisplayStyle style;
}

📊 Stats Widget

Display profile statistics and milestones.

Features:

  • Post count, follower count
  • Member since date
  • Achievement badges
  • Growth charts
  • Privacy controls for sensitive stats

Configuration:

class StatsWidgetConfig {
  final bool showFollowerCount;
  final bool showPostCount;
  final bool showMemberSince;
  final bool showAchievements;
  final bool showGrowthChart;
  final StatPrivacyLevel privacyLevel;
}

💭 Quote Widget

Display a favorite quote or motto.

Features:

  • Rich text formatting
  • Quote attribution
  • Background styling options
  • Font customization
  • Share quote feature

Configuration:

class QuoteWidgetConfig {
  final String text;
  final String? attribution;
  final QuoteStyle style;
  final Color backgroundColor;
  final Color textColor;
  final String fontFamily;
}

📍 Beacon Activity Widget

Show recent community contributions.

Features:

  • Recent beacon posts
  • Vouch/report statistics
  • Community impact score
  • Neighborhood focus
  • Activity timeline

Configuration:

class BeaconActivityWidgetConfig {
  final int maxItems;
  final bool showVouchCount;
  final bool showReportCount;
  final bool showNeighborhood;
  final ActivityTimeframe timeframe;
}

📝 Custom Text Widget

Markdown-rendered freeform content.

Features:

  • Full Markdown support
  • Custom styling options
  • Link and media embedding
  • Character limits
  • Preview mode

Configuration:

class CustomTextWidgetConfig {
  final String markdownContent;
  final TextDisplayStyle style;
  final bool allowHtml;
  final int maxCharacters;
  final bool showWordCount;
}

🎨 Theming System

Color Schemes

  • Default: Platform blue and gray palette
  • Sunset: Warm oranges and purples
  • Ocean: Cool blues and teals
  • Forest: Natural greens and browns
  • Monochrome: Classic black and white
  • Neon: Bright, vibrant colors

Theme Options

class ProfileTheme {
  final String name;
  final Color primaryColor;
  final Color secondaryColor;
  final Color backgroundColor;
  final Color textColor;
  final Color accentColor;
  final String fontFamily;
  final bool darkMode;
}

Customization Controls

  • Accent Color Picker: Choose from palette or custom hex
  • Font Selection: 5 font families with size options
  • Dark/Light Mode: Independent of app theme
  • Banner Image: Upload custom background
  • Widget Borders: Show/hide widget borders
  • Shadow Effects: Adjustable shadow intensity

📱 Implementation Details

Frontend Components

Draggable Widget Grid

File: sojorn_app/lib/widgets/profile/draggable_widget_grid.dart

class DraggableWidgetGrid extends StatefulWidget {
  final List<ProfileWidget> widgets;
  final Function(List<ProfileWidget>)? onLayoutChanged;
  final bool isEditing;
  final ProfileTheme theme;
  
  @override
  _DraggableWidgetGridState createState() => _DraggableWidgetGridState();
}

class _DraggableWidgetGridState extends State<DraggableWidgetGrid> {
  late List<ProfileWidget> _widgets;
  final GlobalKey _gridKey = GlobalKey();
  
  @override
  void initState() {
    super.initState();
    _widgets = List.from(widget.widgets);
  }
  
  @override
  Widget build(BuildContext context) {
    return Container(
      key: _gridKey,
      child: ReorderableGridView.count(
        crossAxisCount: 3,
        mainAxisSpacing: 16,
        crossAxisSpacing: 16,
        onReorder: _onReorder,
        children: _widgets.map((widget) => _buildWidget(widget)).toList(),
      ),
    );
  }
  
  Widget _buildWidget(ProfileWidget widget) {
    return ReorderableWidget(
      key: ValueKey(widget.id),
      reorderable: widget.isEditing,
      child: Container(
        decoration: BoxDecoration(
          color: widget.theme.backgroundColor,
          borderRadius: BorderRadius.circular(12),
          border: widget.showBorder 
            ? Border.all(color: widget.theme.primaryColor)
            : null,
          boxShadow: widget.showShadows 
            ? [
                BoxShadow(
                  color: Colors.black.withOpacity(0.1),
                  blurRadius: 8,
                  offset: Offset(0, 4),
                ),
              ]
            : null,
        ),
        child: WidgetRenderer(
          widget: widget,
          theme: widget.theme,
          isEditing: widget.isEditing,
        ),
      ),
    );
  }
  
  void _onReorder(int oldIndex, int newIndex) {
    setState(() {
      if (newIndex > oldIndex) {
        newIndex -= 1;
      }
      final ProfileWidget widget = _widgets.removeAt(oldIndex);
      _widgets.insert(newIndex, widget);
    });
    
    widget.onLayoutChanged?.call(_widgets);
  }
}

Widget Renderer

File: sojorn_app/lib/widgets/profile/widget_renderer.dart

class WidgetRenderer extends StatelessWidget {
  final ProfileWidget widget;
  final ProfileTheme theme;
  final bool isEditing;
  
  @override
  Widget build(BuildContext context) {
    switch (widget.type) {
      case WidgetType.pinnedPosts:
        return PinnedPostsWidget(
          config: widget.config as PinnedPostsWidgetConfig,
          theme: theme,
          isEditing: isEditing,
        );
      case WidgetType.music:
        return MusicWidget(
          config: widget.config as MusicWidgetConfig,
          theme: theme,
          isEditing: isEditing,
        );
      case WidgetType.photoGrid:
        return PhotoGridWidget(
          config: widget.config as PhotoGridWidgetConfig,
          theme: theme,
          isEditing: isEditing,
        );
      // ... other widget types
      default:
        return Container(
          child: Text('Unknown widget type'),
        );
    }
  }
}

Profile Editor

File: sojorn_app/lib/screens/profile/profile_editor_screen.dart

class ProfileEditorScreen extends ConsumerStatefulWidget {
  @override
  _ProfileEditorScreenState createState() => _ProfileEditorScreenState();
}

class _ProfileEditorScreenState extends ConsumerState<ProfileEditorScreen> {
  List<ProfileWidget> _widgets = [];
  ProfileTheme _theme = ProfileTheme.default();
  bool _isEditing = false;
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Edit Profile'),
        actions: [
          IconButton(
            icon: Icon(_isEditing ? Icons.check : Icons.edit),
            onPressed: _toggleEditing,
          ),
          IconButton(
            icon: Icon(Icons.palette),
            onPressed: _showThemeSelector,
          ),
        ],
      ),
      body: Column(
        children: [
          // Profile header
          _buildProfileHeader(),
          
          // Widget grid
          Expanded(
            child: DraggableWidgetGrid(
              widgets: _widgets,
              onLayoutChanged: _onLayoutChanged,
              isEditing: _isEditing,
              theme: _theme,
            ),
          ),
          
          // Widget catalog (when editing)
          if (_isEditing) _buildWidgetCatalog(),
        ],
      ),
    );
  }
  
  Widget _buildWidgetCatalog() {
    return Container(
      height: 120,
      child: ListView(
        scrollDirection: Axis.horizontal,
        children: WidgetType.values.map((type) => 
          WidgetCatalogItem(
            type: type,
            onTap: () => _addWidget(type),
          ),
        ).toList(),
      ),
    );
  }
  
  void _addWidget(WidgetType type) {
    final newWidget = ProfileWidget.create(type, _theme);
    setState(() {
      _widgets.add(newWidget);
    });
    _saveLayout();
  }
  
  void _saveLayout() {
    final layout = ProfileLayout(
      widgets: _widgets,
      theme: _theme,
      updatedAt: DateTime.now(),
    );
    
    ref.read(profileServiceProvider).saveLayout(layout);
  }
}

Backend Integration

Profile Layout Service

File: go-backend/internal/services/profile_service.go

type ProfileService struct {
    db *pgxpool.Pool
}

type ProfileLayout struct {
    UserID    string        `json:"user_id"`
    Widgets   []ProfileWidget `json:"widgets"`
    Theme     ProfileTheme   `json:"theme"`
    UpdatedAt time.Time      `json:"updated_at"`
}

type ProfileWidget struct {
    ID     string                 `json:"id"`
    Type   string                 `json:"type"`
    Config map[string]interface{} `json:"config"`
    Order  int                    `json:"order"`
}

func (s *ProfileService) SaveLayout(ctx context.Context, userID string, layout ProfileLayout) error {
    // Serialize layout to JSON
    layoutJSON, err := json.Marshal(layout)
    if err != nil {
        return fmt.Errorf("failed to marshal layout: %w", err)
    }
    
    // Save to database
    _, err = s.db.Exec(ctx, `
        INSERT INTO profile_layouts (user_id, layout_data, updated_at)
        VALUES ($1, $2, $3)
        ON CONFLICT (user_id) 
        DO UPDATE SET 
            layout_data = $2,
            updated_at = $3
    `, userID, layoutJSON, time.Now())
    
    return err
}

func (s *ProfileService) GetLayout(ctx context.Context, userID string) (*ProfileLayout, error) {
    var layoutJSON []byte
    err := s.db.QueryRow(ctx, `
        SELECT layout_data 
        FROM profile_layouts 
        WHERE user_id = $1
    `, userID).Scan(&layoutJSON)
    
    if err != nil {
        if err == pgx.ErrNoRows {
            // Return default layout
            return s.getDefaultLayout(userID), nil
        }
        return nil, err
    }
    
    var layout ProfileLayout
    err = json.Unmarshal(layoutJSON, &layout)
    if err != nil {
        return nil, fmt.Errorf("failed to unmarshal layout: %w", err)
    }
    
    return &layout, nil
}

func (s *ProfileService) getDefaultLayout(userID string) *ProfileLayout {
    return &ProfileLayout{
        UserID: userID,
        Widgets: []ProfileWidget{
            {
                ID:   "bio",
                Type: "bio",
                Config: map[string]interface{}{
                    "show_pronouns": true,
                    "show_location": true,
                },
                Order: 0,
            },
            {
                ID:   "social_links",
                Type: "social_links",
                Config: map[string]interface{}{
                    "style": "icons",
                },
                Order: 1,
            },
            {
                ID:   "pinned_posts",
                Type: "pinned_posts",
                Config: map[string]interface{}{
                    "max_posts": 3,
                    "show_engagement": true,
                },
                Order: 2,
            },
        },
        Theme: ProfileTheme{
            Name:         "default",
            PrimaryColor: "#1976D2",
            DarkMode:     false,
        },
        UpdatedAt: time.Now(),
    }
}

🗂️ Data Models

Profile Widget Model

class ProfileWidget {
  final String id;
  final WidgetType type;
  final Map<String, dynamic> config;
  final int order;
  final bool isVisible;
  final DateTime createdAt;
  final DateTime updatedAt;
  
  const ProfileWidget({
    required this.id,
    required this.type,
    required this.config,
    required this.order,
    this.isVisible = true,
    required this.createdAt,
    required this.updatedAt,
  });
  
  factory ProfileWidget.create(WidgetType type, ProfileTheme theme) {
    return ProfileWidget(
      id: const Uuid().v4(),
      type: type,
      config: _getDefaultConfig(type),
      order: 0,
      createdAt: DateTime.now(),
      updatedAt: DateTime.now(),
    );
  }
  
  static Map<String, dynamic> _getDefaultConfig(WidgetType type) {
    switch (type) {
      case WidgetType.pinnedPosts:
        return {
          'max_posts': 3,
          'show_engagement_stats': true,
          'post_ids': <String>[],
        };
      case WidgetType.music:
        return {
          'show_currently_listening': true,
          'current_track': null,
          'artist': null,
          'album': null,
        };
      case WidgetType.photoGrid:
        return {
          'columns': 3,
          'show_captions': true,
          'show_dates': false,
          'photo_urls': <String>[],
        };
      // ... other widget types
      default:
        return {};
    }
  }
}

Profile Layout Model

class ProfileLayout {
  final String userId;
  final List<ProfileWidget> widgets;
  final ProfileTheme theme;
  final DateTime updatedAt;
  
  const ProfileLayout({
    required this.userId,
    required this.widgets,
    required this.theme,
    required this.updatedAt,
  });
  
  factory ProfileLayout.fromJson(Map<String, dynamic> json) {
    return ProfileLayout(
      userId: json['user_id'],
      widgets: (json['widgets'] as List)
          .map((w) => ProfileWidget.fromJson(w))
          .toList(),
      theme: ProfileTheme.fromJson(json['theme']),
      updatedAt: DateTime.parse(json['updated_at']),
    );
  }
  
  Map<String, dynamic> toJson() {
    return {
      'user_id': userId,
      'widgets': widgets.map((w) => w.toJson()).toList(),
      'theme': theme.toJson(),
      'updated_at': updatedAt.toIso8601String(),
    };
  }
}

🗄️ Database Schema

Profile Layout Table

CREATE TABLE IF NOT EXISTS profile_layouts (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    layout_data JSONB NOT NULL,
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW(),
    
    UNIQUE(user_id)
);

-- Index for efficient querying
CREATE INDEX idx_profile_layouts_user_id ON profile_layouts(user_id);
CREATE INDEX idx_profile_layouts_updated_at ON profile_layouts(updated_at);

-- Widget assets table
CREATE TABLE IF NOT EXISTS widget_assets (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    widget_id VARCHAR(100) NOT NULL,
    asset_type VARCHAR(50) NOT NULL, -- 'banner', 'avatar', 'custom'
    asset_url TEXT NOT NULL,
    file_name VARCHAR(255),
    file_size INTEGER,
    mime_type VARCHAR(100),
    created_at TIMESTAMP DEFAULT NOW(),
    
    INDEX idx_widget_assets_user_id ON widget_assets(user_id),
    INDEX idx_widget_assets_widget_id ON widget_assets(widget_id)
);

🔧 Technical Implementation

Widget Constraints

class WidgetConstraints {
  static const double maxWidth = 400;
  static const double maxHeight = 600;
  static const double minWidth = 200;
  static const double minHeight = 150;
  
  static const Map<WidgetType, Size> defaultSizes = {
    WidgetType.pinnedPosts: Size(400, 300),
    WidgetType.music: Size(400, 200),
    WidgetType.photoGrid: Size(400, 400),
    WidgetType.socialLinks: Size(400, 100),
    WidgetType.causes: Size(400, 150),
    WidgetType.featuredFriends: Size(400, 250),
    WidgetType.stats: Size(400, 200),
    WidgetType.quote: Size(400, 150),
    WidgetType.beaconActivity: Size(400, 300),
    WidgetType.customText: Size(400, 250),
  };
  
  static Size getMaxSize(WidgetType type) {
    return defaultSizes[type] ?? Size(maxWidth, maxHeight);
  }
  
  static Size getMinSize(WidgetType type) {
    final defaultSize = defaultSizes[type] ?? Size(maxWidth, maxHeight);
    return Size(minWidth, defaultSize.height * 0.6);
  }
}

Performance Optimization

class WidgetCacheManager {
  static final Map<String, Widget> _widgetCache = {};
  static const Duration _cacheTimeout = Duration(minutes: 5);
  
  static Widget? getCachedWidget(String widgetId) {
    return _widgetCache[widgetId];
  }
  
  static void cacheWidget(String widgetId, Widget widget) {
    _widgetCache[widgetId] = widget;
    
    // Auto-remove after timeout
    Timer(_cacheTimeout, () {
      _widgetCache.remove(widgetId);
    });
  }
  
  static void clearCache() {
    _widgetCache.clear();
  }
}

State Management

class ProfileLayoutNotifier extends StateNotifier<ProfileLayout> {
  ProfileLayoutNotifier(this._profileService) : super(ProfileLayout.empty());
  
  final ProfileService _profileService;
  
  Future<void> loadLayout(String userId) async {
    try {
      final layout = await _profileService.getLayout(userId);
      state = layout;
    } catch (e) {
      // Handle error
      state = ProfileLayout.defaultForUser(userId);
    }
  }
  
  Future<void> saveLayout() async {
    try {
      await _profileService.saveLayout(state);
    } catch (e) {
      // Handle error
    }
  }
  
  void addWidget(WidgetType type) {
    final newWidget = ProfileWidget.create(type, state.theme);
    state = state.copyWith(
      widgets: [...state.widgets, newWidget],
      updatedAt: DateTime.now(),
    );
    saveLayout();
  }
  
  void removeWidget(String widgetId) {
    state = state.copyWith(
      widgets: state.widgets.where((w) => w.id != widgetId).toList(),
      updatedAt: DateTime.now(),
    );
    saveLayout();
  }
  
  void updateWidget(String widgetId, Map<String, dynamic> config) {
    state = state.copyWith(
      widgets: state.widgets.map((w) {
        if (w.id == widgetId) {
          return w.copyWith(
            config: config,
            updatedAt: DateTime.now(),
          );
        }
        return w;
      }).toList(),
      updatedAt: DateTime.now(),
    );
    saveLayout();
  }
  
  void reorderWidgets(int oldIndex, int newIndex) {
    final widgets = List.from(state.widgets);
    if (newIndex > oldIndex) {
      newIndex -= 1;
    }
    final widget = widgets.removeAt(oldIndex);
    widgets.insert(newIndex, widget);
    
    state = state.copyWith(
      widgets: widgets,
      updatedAt: DateTime.now(),
    );
    saveLayout();
  }
  
  void updateTheme(ProfileTheme theme) {
    state = state.copyWith(
      theme: theme,
      updatedAt: DateTime.now(),
    );
    saveLayout();
  }
}

📱 User Interface

Widget Catalog

  • Visual Preview: Thumbnail previews of each widget type
  • Drag & Drop: Drag widgets from catalog to grid
  • Search: Filter widget types by name or category
  • Categories: Organized by function (social, content, media, etc.)

Editing Interface

  • Live Preview: See changes in real-time
  • Undo/Redo: Revert changes if needed
  • Save Status: Visual indication of save state
  • Help Tooltips: Contextual help for each widget

Theme Customization

  • Color Picker: Visual color selection with hex input
  • Font Selector: Preview fonts before applying
  • Banner Upload: Drag and drop banner images
  • Reset Options: Reset to default theme or layout

🔒 Security & Privacy

Content Validation

  • XSS Prevention: Sanitize all user-generated content
  • HTML Filtering: Remove dangerous HTML tags and attributes
  • Link Validation: Verify and sanitize external links
  • Image Upload: Scan uploads for malicious content

Privacy Controls

  • Widget Privacy: Individual widget visibility settings
  • Data Minimization: Only store necessary widget data
  • User Consent: Clear consent for data collection
  • Access Control: Proper authorization for profile access

Content Moderation

  • Widget Content: Moderate custom text and images
  • Link Safety: Check external links for safety
  • User Reporting: Report inappropriate profile content
  • Automated Filtering: AI-powered content analysis

📊 Analytics & Metrics

Widget Usage

  • Popular Widgets: Track most used widget types
  • Customization Trends: Analyze theme preferences
  • Engagement Metrics: Measure widget interaction rates
  • User Behavior: Track profile editing patterns

Performance Metrics

  • Load Times: Profile page load performance
  • Widget Rendering: Individual widget render times
  • Cache Hit Rates: Widget cache effectiveness
  • Error Rates: Widget failure rates and types

🚀 Deployment

Environment Configuration

# Widget system settings
PROFILE_WIDGETS_ENABLED=true
PROFILE_WIDGETS_MAX_PER_USER=10
PROFILE_WIDGETS_CACHE_TTL=300

# Asset storage
WIDGET_ASSETS_BUCKET=sojorn-widget-assets
WIDGET_ASSETS_MAX_SIZE=10485760  # 10MB

# Theme settings
PROFILE_THEMES_ENABLED=true
PROFILE_CUSTOM_THEMES=true
PROFILE_BANNER_MAX_SIZE=2097152  # 2MB

Health Checks

func (s *ProfileService) HealthCheck() HealthStatus {
    // Check widget cache
    if s.widgetCache == nil {
        return HealthStatus{
            Status: "degraded",
            Message: "Widget cache not available",
        }
    }
    
    // Check asset storage
    if _, err := s.assetService.HealthCheck(); err != nil {
        return HealthStatus{
            Status: "degraded",
            Message: "Asset storage not accessible",
        }
    }
    
    return HealthStatus{
        Status: "healthy",
        Message: "Profile widgets system ready",
    }
}

📚 Troubleshooting

Common Issues

Widget Not Rendering

// Check widget configuration
if (widget.config.isEmpty) {
  return ErrorWidget(
    message: "Widget configuration is missing",
    action: "Reset to default",
  );
}

// Check theme compatibility
if (!theme.isCompatible(widget.type)) {
  return ErrorWidget(
    message: "Theme not compatible with widget",
    action: "Use default theme",
  );
}

Layout Not Saving

try {
  await profileService.saveLayout(layout);
} catch (e) {
  // Show user-friendly error
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text('Failed to save layout: $e')),
  );
  
  // Log detailed error
  logger.error('Layout save failed', error: e);
}

Performance Issues

// Implement lazy loading for widgets
class LazyWidget extends StatelessWidget {
  final WidgetType type;
  
  @override
  Widget build(BuildContext context) {
    return FutureBuilder<Widget>(
      future: _loadWidget(type),
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          return snapshot.data!;
        }
        return CircularProgressIndicator();
      },
    );
  }
}

📝 Future Enhancements

Version 3.1 (Planned)

  • Widget Templates: Pre-designed widget combinations
  • Collaborative Profiles: Multiple users editing same profile
  • Advanced Analytics: Detailed widget performance metrics
  • Widget Marketplace: Community-created widgets

Version 4.0 (Long-term)

  • AI Widget Suggestions: AI-powered widget recommendations
  • Interactive Widgets: Widgets with real-time data
  • 3D Widgets: 3D visualization widgets
  • Voice Commands: Voice-controlled profile editing

📞 Support & Documentation

User Guides

  • Getting Started: Quick start guide for profile customization
  • Widget Catalog: Complete widget reference and examples
  • Theme Guide: Theme customization and design principles
  • Troubleshooting: Common issues and solutions

Developer Resources

  • Widget Development: Guide to creating custom widgets
  • API Documentation: Complete API reference
  • Design System: UI/UX guidelines and components
  • Code Examples: Sample implementations and patterns

🎨 The Profile Widgets system provides users with powerful yet constrained customization options, allowing for personal expression while maintaining platform consistency and performance.